removal of unnecessary file, and composer update

This commit is contained in:
2019-01-09 23:31:31 -06:00
parent a90688deb6
commit e4380f2ffe
604 changed files with 32070 additions and 26824 deletions

View File

@@ -1,6 +1,6 @@
## How does Bootstrap's test suite work?
Bootstrap uses [QUnit](https://qunitjs.com/) and [Sinon](http://sinonjs.org/). Each plugin has a file dedicated to its tests in `unit/<plugin-name>.js`.
Bootstrap uses [QUnit](https://qunitjs.com/) and [Sinon](https://sinonjs.org/). Each plugin has a file dedicated to its tests in `unit/<plugin-name>.js`.
* `unit/` contains the unit test files for each Bootstrap plugin.
* `vendor/` contains third-party testing-related code (QUnit, jQuery and Sinon).

View File

@@ -0,0 +1,82 @@
/* eslint-env node */
/* eslint-disable camelcase */
const browsers = {
safariMac: {
base: 'BrowserStack',
os: 'OS X',
os_version: 'High Sierra',
browser: 'Safari',
browser_version: 'latest'
},
chromeMac: {
base: 'BrowserStack',
os: 'OS X',
os_version: 'High Sierra',
browser : 'Chrome',
browser_version : 'latest'
},
firefoxMac: {
base: 'BrowserStack',
os: 'OS X',
os_version: 'High Sierra',
browser: 'Firefox',
browser_version: 'latest'
},
edgeWin10: {
base: 'BrowserStack',
os: 'Windows',
os_version: '10',
browser: 'Edge',
browser_version: 'latest'
},
ie11Win10: {
base: 'BrowserStack',
os: 'Windows',
os_version: '10',
browser: 'IE',
browser_version: '11.0'
},
chromeWin10: {
base: 'BrowserStack',
os: 'Windows',
os_version: '10',
browser: 'Chrome',
browser_version: 'latest'
},
firefoxWin10: {
base: 'BrowserStack',
os: 'Windows',
os_version: '10',
browser: 'Firefox',
browser_version: 'latest'
},
ie10Win8: {
base: 'BrowserStack',
os: 'Windows',
os_version: '8',
browser: 'IE',
browser_version: '10.0'
},
iphoneX: {
base: 'BrowserStack',
os: 'ios',
os_version: '11.0',
device: 'iPhone X',
real_mobile: true
},
pixel2: {
base: 'BrowserStack',
os: 'android',
os_version: '8.0',
device: 'Google Pixel 2',
real_mobile: true
}
}
const browsersKeys = Object.keys(browsers)
module.exports = {
browsers,
browsersKeys
}

View File

@@ -8,7 +8,7 @@
<!-- jQuery -->
<script>
(function () {
var path = '../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js'
var path = '../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js'
// get jquery param from the query string.
var jQueryVersion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/)
@@ -19,7 +19,7 @@
document.write('<script src="' + path + '"><\/script>')
}())
</script>
<script src="../../site/docs/4.1/assets/js/vendor/popper.min.js"></script>
<script src="../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<!-- QUnit -->
<link rel="stylesheet" href="../../node_modules/qunit/qunit/qunit.css" media="screen">
@@ -28,6 +28,9 @@
<!-- Sinon -->
<script src="../../node_modules/sinon/pkg/sinon-no-sourcemaps.js"></script>
<!-- Hammer simulator -->
<script src="../../node_modules/hammer-simulator/index.js"></script>
<script>
// Disable jQuery event aliases to ensure we don't accidentally use any of them
[
@@ -117,6 +120,7 @@
<script src="../dist/tab.js"></script>
<script src="../dist/tooltip.js"></script>
<script src="../dist/popover.js"></script>
<script src="../dist/toast.js"></script>
<!-- Unit Tests -->
<script src="unit/alert.js"></script>
@@ -130,6 +134,7 @@
<script src="unit/tooltip.js"></script>
<script src="unit/popover.js"></script>
<script src="unit/util.js"></script>
<script src="unit/toast.js"></script>
</head>
<body>
<div id="qunit-container">

View File

@@ -0,0 +1,8 @@
import 'popper.js'
import $ from 'jquery'
import bootstrap from '../../../dist/js/bootstrap'
$(() => {
$('#resultUID').text(bootstrap.Util.getUID('bs'))
$('[data-toggle="tooltip"]').tooltip()
})

View File

@@ -0,0 +1,66 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../../../dist/css/bootstrap.min.css">
<title>Hello, world!</title>
</head>
<body>
<div class="container">
<h1>Hello, world!</h1>
<div class="col-12">
<div class="mt-5 mb-3">
<span>Util.getUID: </span>
<span id="resultUID"></span>
</div>
<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top">
Tooltip on top
</button>
<div id="carouselExampleCaptions" class="carousel slide mt-2" data-ride="carousel">
<ol class="carousel-indicators">
<li data-target="#carouselExampleCaptions" data-slide-to="0"></li>
<li data-target="#carouselExampleCaptions" data-slide-to="1" class="active"></li>
<li data-target="#carouselExampleCaptions" data-slide-to="2"></li>
</ol>
<div class="carousel-inner">
<div class="carousel-item">
<img class="d-block w-100" data-src="holder.js/800x400?auto=yes&amp;bg=777&amp;fg=555&amp;text=First slide" alt="First slide [800x400]" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22400%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20400%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_167a6f826cb%20text%20%7B%20fill%3A%23555%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A40pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_167a6f826cb%22%3E%3Crect%20width%3D%22800%22%20height%3D%22400%22%20fill%3D%22%23777%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22285.921875%22%20y%3D%22217.7%22%3EFirst%20slide%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" data-holder-rendered="true">
<div class="carousel-caption d-none d-md-block">
<h5>First slide label</h5>
<p>Nulla vitae elit libero, a pharetra augue mollis interdum.</p>
</div>
</div>
<div class="carousel-item active">
<img class="d-block w-100" data-src="holder.js/800x400?auto=yes&amp;bg=666&amp;fg=444&amp;text=Second slide" alt="Second slide [800x400]" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22400%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20400%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_167a6f826ce%20text%20%7B%20fill%3A%23444%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A40pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_167a6f826ce%22%3E%3Crect%20width%3D%22800%22%20height%3D%22400%22%20fill%3D%22%23666%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22247.3203125%22%20y%3D%22217.7%22%3ESecond%20slide%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" data-holder-rendered="true">
<div class="carousel-caption d-none d-md-block">
<h5>Second slide label</h5>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
</div>
<div class="carousel-item">
<img class="d-block w-100" data-src="holder.js/800x400?auto=yes&amp;bg=555&amp;fg=333&amp;text=Third slide" alt="Third slide [800x400]" src="data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22400%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20800%20400%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_167a6f826cf%20text%20%7B%20fill%3A%23333%3Bfont-weight%3Anormal%3Bfont-family%3AHelvetica%2C%20monospace%3Bfont-size%3A40pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_167a6f826cf%22%3E%3Crect%20width%3D%22800%22%20height%3D%22400%22%20fill%3D%22%23555%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%22277%22%20y%3D%22217.7%22%3EThird%20slide%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" data-holder-rendered="true">
<div class="carousel-caption d-none d-md-block">
<h5>Third slide label</h5>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur.</p>
</div>
</div>
</div>
<a class="carousel-control-prev" href="#carouselExampleCaptions" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleCaptions" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
</div>
<script src="../../coverage/bundle.js"></script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
/* eslint-env node */
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const babel = require('rollup-plugin-babel')
module.exports = {
input: 'js/tests/integration/bundle.js',
output: {
file: 'js/coverage/bundle.js',
format: 'iife'
},
plugins: [
resolve(),
commonjs(),
babel({
exclude: 'node_modules/**'
})
]
}

View File

@@ -1,53 +0,0 @@
/* eslint-env node */
/* eslint no-process-env: 0 */
module.exports = (config) => {
const jqueryFile = process.env.USE_OLD_JQUERY ? 'https://code.jquery.com/jquery-1.9.1.min.js' : 'site/docs/4.1/assets/js/vendor/jquery-slim.min.js'
config.set({
basePath: '../..',
frameworks: ['qunit', 'sinon', 'detectBrowsers'],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-qunit',
'karma-sinon',
'karma-detect-browsers'
],
// list of files / patterns to load in the browser
files: [
jqueryFile,
'site/docs/4.1/assets/js/vendor/popper.min.js',
'dist/js/bootstrap.js',
'js/tests/unit/*.js'
],
reporters: ['dots'],
port: 9876,
colors: true,
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_ERROR || config.LOG_WARN,
autoWatch: false,
customLaunchers: {
FirefoxHeadless: {
base: 'Firefox',
flags: ['-headless']
}
},
singleRun: true,
concurrency: Infinity,
detectBrowsers: {
usePhantomJS: false,
postDetection(availableBrowser) {
if (typeof process.env.TRAVIS_JOB_ID !== 'undefined' || availableBrowser.includes('Chrome')) {
return ['ChromeHeadless']
}
if (availableBrowser.includes('Firefox')) {
return ['FirefoxHeadless']
}
throw new Error('Please install Firefox or Chrome')
}
}
})
}

View File

@@ -1,76 +1,145 @@
/* eslint-env node */
/* eslint no-process-env: 0 */
const ip = require('ip')
const pkg = require('../../package.json')
const {
browsers,
browsersKeys
} = require('./browsers')
const path = require('path')
const jsCoveragePath = path.resolve(__dirname, '../coverage')
module.exports = (config) => {
const jqueryFile = process.env.USE_OLD_JQUERY ? 'https://code.jquery.com/jquery-1.9.1.min.js' : 'site/docs/4.1/assets/js/vendor/jquery-slim.min.js'
const jqueryFile = process.env.USE_OLD_JQUERY ? 'https://code.jquery.com/jquery-1.9.1.min.js' : `site/docs/${pkg.version_short}/assets/js/vendor/jquery-slim.min.js`
const bundle = process.env.BUNDLE === 'true'
const browserStack = process.env.BROWSER === 'true'
config.set({
basePath: '../..',
frameworks: ['qunit', 'sinon', 'detectBrowsers'],
plugins: [
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-qunit',
'karma-sinon',
'karma-detect-browsers',
'karma-coverage-istanbul-reporter'
],
// list of files / patterns to load in the browser
files: [
jqueryFile,
'site/docs/4.1/assets/js/vendor/popper.min.js',
'js/coverage/dist/util.js',
'js/coverage/dist/tooltip.js',
'js/coverage/dist/!(util|index|tooltip).js', // include all of our js/dist files except util.js, index.js and tooltip.js
'js/tests/unit/*.js'
],
reporters: ['dots', 'coverage-istanbul'],
port: 9876,
colors: true,
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_ERROR || config.LOG_WARN,
autoWatch: false,
customLaunchers: {
FirefoxHeadless: {
base: 'Firefox',
flags: ['-headless']
}
},
singleRun: true,
concurrency: Infinity,
detectBrowsers: {
usePhantomJS: false,
postDetection(availableBrowser) {
if (typeof process.env.TRAVIS_JOB_ID !== 'undefined' || availableBrowser.includes('Chrome')) {
return ['ChromeHeadless']
}
const frameworks = [
'qunit',
'sinon'
]
if (availableBrowser.includes('Firefox')) {
return ['FirefoxHeadless']
}
const plugins = [
'karma-qunit',
'karma-sinon'
]
throw new Error('Please install Firefox or Chrome')
}
},
coverageIstanbulReporter: {
dir: jsCoveragePath,
reports: ['lcov', 'text-summary'],
thresholds: {
emitWarning: false,
global: {
statements: 90,
branches: 84,
functions: 87,
lines: 90
}
}
},
client: {
qunit: {
showUI: true
const reporters = ['dots']
const detectBrowsers = {
usePhantomJS: false,
postDetection(availableBrowser) {
if (typeof process.env.TRAVIS_JOB_ID !== 'undefined' || availableBrowser.includes('Chrome')) {
return ['ChromeHeadless']
}
if (availableBrowser.includes('Firefox')) {
return ['FirefoxHeadless']
}
throw new Error('Please install Firefox or Chrome')
}
}
const customLaunchers = {
FirefoxHeadless: {
base: 'Firefox',
flags: ['-headless']
}
}
let files = [
'node_modules/popper.js/dist/umd/popper.min.js',
'node_modules/hammer-simulator/index.js'
]
const conf = {
basePath: '../..',
port: 9876,
colors: true,
autoWatch: false,
singleRun: true,
concurrency: Infinity,
client: {
qunit: {
showUI: true
}
}
}
if (bundle) {
frameworks.push('detectBrowsers')
plugins.push(
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-detect-browsers'
)
conf.customLaunchers = customLaunchers
conf.detectBrowsers = detectBrowsers
files = files.concat([
jqueryFile,
'dist/js/bootstrap.js'
])
} else if (browserStack) {
conf.hostname = ip.address()
conf.browserStack = {
username: process.env.BROWSER_STACK_USERNAME,
accessKey: process.env.BROWSER_STACK_ACCESS_KEY,
build: `bootstrap-${new Date().toISOString()}`,
project: 'Bootstrap',
retryLimit: 2
}
plugins.push('karma-browserstack-launcher')
conf.customLaunchers = browsers
conf.browsers = browsersKeys
reporters.push('BrowserStack')
files = files.concat([
`site/docs/${pkg.version_short}/assets/js/vendor/jquery-slim.min.js`,
'js/dist/util.js',
'js/dist/tooltip.js',
'js/dist/!(util|index|tooltip).js' // include all of our js/dist files except util.js, index.js and tooltip.js
])
} else {
frameworks.push('detectBrowsers')
plugins.push(
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-detect-browsers',
'karma-coverage-istanbul-reporter'
)
files = files.concat([
jqueryFile,
'js/coverage/dist/util.js',
'js/coverage/dist/tooltip.js',
'js/coverage/dist/!(util|index|tooltip).js' // include all of our js/dist files except util.js, index.js and tooltip.js
])
reporters.push('coverage-istanbul')
conf.customLaunchers = customLaunchers
conf.detectBrowsers = detectBrowsers
conf.coverageIstanbulReporter = {
dir: jsCoveragePath,
reports: ['lcov', 'text-summary'],
thresholds: {
emitWarning: false,
global: {
statements: 90,
branches: 86,
functions: 89,
lines: 90
}
}
})
}
}
files.push('js/tests/unit/*.js')
conf.frameworks = frameworks
conf.plugins = plugins
conf.reporters = reporters
conf.files = files
module.exports = (karmaConfig) => {
// possible values: karmaConfig.LOG_DISABLE || karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN || karmaConfig.LOG_INFO || karmaConfig.LOG_DEBUG
conf.logLevel = karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN
karmaConfig.set(conf)
}

View File

@@ -9,7 +9,10 @@
"sinon": false,
"Util": false,
"Alert": false,
"Button": false
"Button": false,
"Carousel": false,
"Simulator": false,
"Toast": false
},
"parserOptions": {
"ecmaVersion": 5,
@@ -17,7 +20,7 @@
},
"extends": "../../../.eslintrc.json",
"rules": {
"no-console": "off",
"no-console": "error",
// Best Practices
"consistent-return": "off",
"no-magic-numbers": "off",

View File

@@ -139,6 +139,29 @@ $(function () {
assert.ok($btn2.find('input').prop('checked'), 'btn2 is checked')
})
QUnit.test('should only toggle selectable inputs', function (assert) {
assert.expect(6)
var groupHTML = '<div class="btn-group" data-toggle="buttons">' +
'<label class="btn btn-primary active">' +
'<input type="hidden" name="option1" id="option1-default" value="false">' +
'<input type="checkbox" name="option1" id="option1" checked="true"> Option 1' +
'</label>' +
'</div>'
var $group = $(groupHTML).appendTo('#qunit-fixture')
var $btn = $group.children().eq(0)
var $hidden = $btn.find('input#option1-default')
var $cb = $btn.find('input#option1')
assert.ok($btn.hasClass('active'), 'btn has active class')
assert.ok($cb.prop('checked'), 'btn is checked')
assert.ok(!$hidden.prop('checked'), 'hidden is not checked')
$btn.trigger('click')
assert.ok(!$btn.hasClass('active'), 'btn does not have active class')
assert.ok(!$cb.prop('checked'), 'btn is not checked')
assert.ok(!$hidden.prop('checked'), 'hidden is not checked') // should not be changed
})
QUnit.test('should not add aria-pressed on labels for radio/checkbox inputs in a data-toggle="buttons" group', function (assert) {
assert.expect(2)
var groupHTML = '<div class="btn-group" data-toggle="buttons">' +

View File

@@ -1,6 +1,26 @@
$(function () {
'use strict'
window.Carousel = typeof bootstrap !== 'undefined' ? bootstrap.Carousel : Carousel
var originWinPointerEvent = window.PointerEvent
window.MSPointerEvent = null
var supportPointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)
function clearPointerEvents() {
window.PointerEvent = null
}
function restorePointerEvents() {
window.PointerEvent = originWinPointerEvent
}
var stylesCarousel = [
'<style>',
' .carousel.pointer-event { -ms-touch-action: none; touch-action: none; }',
'</style>'
].join('')
QUnit.module('carousel plugin')
QUnit.test('should be defined on jQuery object', function (assert) {
@@ -25,6 +45,20 @@ $(function () {
assert.strictEqual(typeof $.fn.carousel, 'undefined', 'carousel was set back to undefined (orig value)')
})
QUnit.test('should return version', function (assert) {
assert.expect(1)
assert.strictEqual(typeof Carousel.VERSION, 'string')
})
QUnit.test('should return default parameters', function (assert) {
assert.expect(1)
var defaultConfig = Carousel.Default
assert.strictEqual(defaultConfig.touch, true)
})
QUnit.test('should throw explicit error on undefined method', function (assert) {
assert.expect(1)
var $el = $('<div/>')
@@ -445,6 +479,54 @@ $(function () {
$carousel.remove()
})
QUnit.test('should set interval from data attribute on individual carousel-item', function (assert) {
assert.expect(2)
var templateHTML = '<div id="myCarousel" class="carousel slide" data-interval="1814">' +
'<div class="carousel-inner">' +
'<div class="carousel-item active" data-interval="2814">' +
'<img alt="">' +
'<div class="carousel-caption">' +
'<h4>First Thumbnail label</h4>' +
'<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec ' +
'id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ' +
'ultricies vehicula ut id elit.</p>' +
'</div>' +
'</div>' +
'<div class="carousel-item" data-interval="3814">' +
'<img alt="">' +
'<div class="carousel-caption">' +
'<h4>Second Thumbnail label</h4>' +
'<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec ' +
'id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ' +
'ultricies vehicula ut id elit.</p>' +
'</div>' +
'</div>' +
'<div class="carousel-item">' +
'<img alt="">' +
'<div class="carousel-caption">' +
'<h4>Third Thumbnail label</h4>' +
'<p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec ' +
'id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ' +
'ultricies vehicula ut id elit.</p>' +
'</div>' +
'</div>' +
'</div>' +
'<a class="left carousel-control" href="#myCarousel" data-slide="prev">&lsaquo;</a>' +
'<a class="right carousel-control" href="#myCarousel" data-slide="next">&rsaquo;</a>' +
'</div>'
var $carousel = $(templateHTML)
$carousel.appendTo('body')
$carousel.bootstrapCarousel(1)
assert.strictEqual($carousel.data('bs.carousel')._config.interval, 3814)
$carousel.remove()
$carousel.appendTo('body')
$carousel.bootstrapCarousel(2)
assert.strictEqual($carousel.data('bs.carousel')._config.interval, 1814, 'reverts to default interval if no data-interval is set')
$carousel.remove()
})
QUnit.test('should skip over non-items when using item indices', function (assert) {
assert.expect(2)
var templateHTML = '<div id="myCarousel" class="carousel" data-interval="1814">' +
@@ -941,4 +1023,251 @@ $(function () {
}, 80)
}, 80)
})
QUnit.test('should allow swiperight and call prev with pointer events', function (assert) {
if (!supportPointerEvent) {
assert.expect(0)
return
}
document.documentElement.ontouchstart = $.noop
Simulator.setType('pointer')
assert.expect(3)
var $styles = $(stylesCarousel).appendTo('head')
var done = assert.async()
var carouselHTML =
'<div class="carousel" data-interval="false">' +
' <div class="carousel-inner">' +
' <div id="item" class="carousel-item">' +
' <img alt="">' +
' </div>' +
' <div class="carousel-item active">' +
' <img alt="">' +
' </div>' +
' </div>' +
'</div>'
var $carousel = $(carouselHTML).appendTo('#qunit-fixture')
var $item = $('#item')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
var spy = sinon.spy(carousel, 'prev')
$carousel.one('slid.bs.carousel', function () {
assert.ok(true, 'slid event fired')
assert.ok($item.hasClass('active'))
assert.ok(spy.called)
$styles.remove()
delete document.documentElement.ontouchstart
done()
})
Simulator.gestures.swipe($carousel[0], {
deltaX: 300,
deltaY: 0
})
})
QUnit.test('should allow swiperight and call prev with touch events', function (assert) {
Simulator.setType('touch')
clearPointerEvents()
assert.expect(3)
var done = assert.async()
document.documentElement.ontouchstart = $.noop
var carouselHTML =
'<div class="carousel" data-interval="false">' +
' <div class="carousel-inner">' +
' <div id="item" class="carousel-item">' +
' <img alt="">' +
' </div>' +
' <div class="carousel-item active">' +
' <img alt="">' +
' </div>' +
' </div>' +
'</div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
var $item = $('#item')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
var spy = sinon.spy(carousel, 'prev')
$carousel.one('slid.bs.carousel', function () {
assert.ok(true, 'slid event fired')
assert.ok($item.hasClass('active'))
assert.ok(spy.called)
delete document.documentElement.ontouchstart
restorePointerEvents()
done()
})
Simulator.gestures.swipe($carousel[0], {
deltaX: 300,
deltaY: 0
})
})
QUnit.test('should allow swipeleft and call next with pointer events', function (assert) {
if (!supportPointerEvent) {
assert.expect(0)
return
}
document.documentElement.ontouchstart = $.noop
assert.expect(3)
Simulator.setType('pointer')
var $styles = $(stylesCarousel).appendTo('head')
var done = assert.async()
var carouselHTML =
'<div class="carousel" data-interval="false">' +
' <div class="carousel-inner">' +
' <div id="item" class="carousel-item active">' +
' <img alt="">' +
' </div>' +
' <div class="carousel-item">' +
' <img alt="">' +
' </div>' +
' </div>' +
'</div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
var $item = $('#item')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
var spy = sinon.spy(carousel, 'next')
$carousel.one('slid.bs.carousel', function () {
assert.ok(true, 'slid event fired')
assert.ok(!$item.hasClass('active'))
assert.ok(spy.called)
$styles.remove()
delete document.documentElement.ontouchstart
done()
})
Simulator.gestures.swipe($carousel[0], {
pos: [300, 10],
deltaX: -300,
deltaY: 0
})
})
QUnit.test('should allow swipeleft and call next with touch events', function (assert) {
assert.expect(3)
clearPointerEvents()
Simulator.setType('touch')
document.documentElement.ontouchstart = $.noop
var done = assert.async()
var carouselHTML =
'<div class="carousel" data-interval="false">' +
' <div class="carousel-inner">' +
' <div id="item" class="carousel-item active">' +
' <img alt="">' +
' </div>' +
' <div class="carousel-item">' +
' <img alt="">' +
' </div>' +
' </div>' +
'</div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
var $item = $('#item')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
var spy = sinon.spy(carousel, 'next')
$carousel.one('slid.bs.carousel', function () {
assert.ok(true, 'slid event fired')
assert.ok(!$item.hasClass('active'))
assert.ok(spy.called)
restorePointerEvents()
delete document.documentElement.ontouchstart
done()
})
Simulator.gestures.swipe($carousel[0], {
pos: [300, 10],
deltaX: -300,
deltaY: 0
})
})
QUnit.test('should not allow pinch with touch events', function (assert) {
assert.expect(0)
clearPointerEvents()
Simulator.setType('touch')
var done = assert.async()
document.documentElement.ontouchstart = $.noop
var carouselHTML = '<div class="carousel" data-interval="false"></div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
$carousel.bootstrapCarousel()
Simulator.gestures.swipe($carousel[0], {
pos: [300, 10],
deltaX: -300,
deltaY: 0,
touches: 2
}, function () {
restorePointerEvents()
delete document.documentElement.ontouchstart
done()
})
})
QUnit.test('should not call _slide if the carousel is sliding', function (assert) {
assert.expect(1)
var carouselHTML = '<div class="carousel" data-interval="false"></div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
var spy = sinon.spy(carousel, '_slide')
carousel._isSliding = true
carousel.next()
assert.strictEqual(spy.called, false)
})
QUnit.test('should call next when the page is visible', function (assert) {
assert.expect(1)
var carouselHTML = '<div class="carousel" data-interval="false"></div>'
var $carousel = $(carouselHTML)
$carousel.appendTo('#qunit-fixture')
$carousel.bootstrapCarousel()
var carousel = $carousel.data('bs.carousel')
var spy = sinon.spy(carousel, 'next')
var sandbox = sinon.createSandbox()
sandbox.replaceGetter(document, 'hidden', function () {
return false
})
sandbox.stub($carousel, 'is').returns(true)
sandbox.stub($carousel, 'css').returns('block')
carousel.nextWhenVisible()
assert.strictEqual(spy.called, true)
sandbox.restore()
})
})

View File

@@ -855,4 +855,38 @@ $(function () {
assert.ok(false, 'collapse not created')
}
})
QUnit.test('should find collapse children if they have collapse class too not only data-parent', function (assert) {
assert.expect(2)
var done = assert.async()
var html =
'<div class="my-collapse">' +
' <div class="item">' +
' <a data-toggle="collapse" href="#">Toggle item 1</a>' +
' <div id="collapse1" class="collapse show">Lorem ipsum 1</div>' +
' </div>' +
' <div class="item">' +
' <a id="triggerCollapse2" data-toggle="collapse" href="#">Toggle item 2</a>' +
' <div id="collapse2" class="collapse">Lorem ipsum 2</div>' +
' </div>' +
'</div>'
$(html).appendTo('#qunit-fixture')
var $parent = $('.my-collapse')
var $collapse2 = $('#collapse2')
$parent.find('.collapse').bootstrapCollapse({
parent: $parent,
toggle: false
})
$collapse2.on('shown.bs.collapse', function () {
assert.ok($collapse2.hasClass('show'))
assert.ok(!$('#collapse1').hasClass('show'))
done()
})
$collapse2.bootstrapCollapse('toggle')
})
})

View File

@@ -216,30 +216,6 @@ $(function () {
$dropdown.trigger('click')
})
QUnit.test('should test if element has a # before assuming it\'s a selector', function (assert) {
assert.expect(1)
var done = assert.async()
var dropdownHTML = '<div class="tabs">' +
'<div class="dropdown">' +
'<a href="/foo/" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
'<div class="dropdown-menu">' +
'<a class="dropdown-item" href="#">Secondary link</a>' +
'<a class="dropdown-item" href="#">Something else here</a>' +
'<div class="divider"/>' +
'<a class="dropdown-item" href="#">Another link</a>' +
'</div>' +
'</div>' +
'</div>'
var $dropdown = $(dropdownHTML).find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown
.parent('.dropdown')
.on('shown.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), '"show" class added on click')
done()
})
$dropdown.trigger('click')
})
QUnit.test('should remove "show" class if body is clicked', function (assert) {
assert.expect(2)
var done = assert.async()
@@ -1121,4 +1097,268 @@ $(function () {
assert.ok(dropdown._menu === null)
assert.ok(dropdown._element === null)
})
QUnit.test('should show dropdown', function (assert) {
assert.expect(2)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
var done = assert.async()
$dropdown
.parent('.dropdown')
.on('show.bs.dropdown', function () {
assert.ok(true, 'show was fired')
})
.on('shown.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
done()
})
dropdown.show()
})
QUnit.test('should hide dropdown', function (assert) {
assert.expect(2)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
var done = assert.async()
$dropdown.trigger('click')
$dropdown
.parent('.dropdown')
.on('hide.bs.dropdown', function () {
assert.ok(true, 'hide was fired')
})
.on('hidden.bs.dropdown', function () {
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is hidden')
done()
})
dropdown.hide()
})
QUnit.test('should not hide dropdown', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
$dropdown.trigger('click')
dropdown.show()
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is still shown')
})
QUnit.test('should not show dropdown', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
dropdown.hide()
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is still hidden')
})
QUnit.test('should show dropdown', function (assert) {
assert.expect(2)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
var done = assert.async()
$dropdown
.parent('.dropdown')
.on('show.bs.dropdown', function () {
assert.ok(true, 'show was fired')
})
.on('shown.bs.dropdown', function () {
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
done()
})
dropdown.show()
})
QUnit.test('should prevent default event on show method call', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
var done = assert.async()
$dropdown
.parent('.dropdown')
.on('show.bs.dropdown', function (event) {
event.preventDefault()
done()
})
dropdown.show()
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is hidden')
})
QUnit.test('should prevent default event on hide method call', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown">' +
' <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
var $dropdown = $(dropdownHTML)
.appendTo('#qunit-fixture')
.find('[data-toggle="dropdown"]')
.bootstrapDropdown()
var dropdown = $dropdown.data('bs.dropdown')
var done = assert.async()
$dropdown.trigger('click')
$dropdown
.parent('.dropdown')
.on('hide.bs.dropdown', function (event) {
event.preventDefault()
done()
})
dropdown.hide()
assert.ok($dropdown.parent('.dropdown').hasClass('show'), 'dropdown menu is shown')
})
QUnit.test('should not open dropdown via show method if target is disabled via attribute', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown">' +
' <button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
$(dropdownHTML).appendTo('#qunit-fixture')
var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown.show()
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'))
})
QUnit.test('should not open dropdown via show method if target is disabled via class', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown">' +
' <button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
$(dropdownHTML).appendTo('#qunit-fixture')
var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown.show()
assert.ok(!$dropdown.parent('.dropdown').hasClass('show'))
})
QUnit.test('should not hide dropdown via hide method if target is disabled via attribute', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown show">' +
' <button disabled href="#" class="btn dropdown-toggle" data-toggle="dropdown">Dropdown</button>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
$(dropdownHTML).appendTo('#qunit-fixture')
var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown.hide()
assert.ok($dropdown.parent('.dropdown').hasClass('show'))
})
QUnit.test('should not hide dropdown via hide method if target is disabled via class', function (assert) {
assert.expect(1)
var dropdownHTML =
'<div class="dropdown show">' +
' <button href="#" class="btn dropdown-toggle disabled" data-toggle="dropdown">Dropdown</button>' +
' <div class="dropdown-menu">' +
' <a class="dropdown-item" href="#">Another link</a>' +
' </div>' +
'</div>'
$(dropdownHTML).appendTo('#qunit-fixture')
var $dropdown = $('#qunit-fixture').find('[data-toggle="dropdown"]').bootstrapDropdown()
$dropdown.hide()
assert.ok($dropdown.parent('.dropdown').hasClass('show'))
})
})

View File

@@ -1,6 +1,8 @@
$(function () {
'use strict'
window.Util = typeof bootstrap !== 'undefined' ? bootstrap.Util : Util
QUnit.module('modal plugin')
QUnit.test('should be defined on jquery object', function (assert) {
@@ -13,14 +15,7 @@ $(function () {
// Enable the scrollbar measurer
$('<style type="text/css"> .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } </style>').appendTo('head')
// Function to calculate the scrollbar width which is then compared to the padding or margin changes
$.fn.getScrollbarWidth = function () {
var scrollDiv = document.createElement('div')
scrollDiv.className = 'modal-scrollbar-measure'
document.body.appendChild(scrollDiv)
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
document.body.removeChild(scrollDiv)
return scrollbarWidth
}
$.fn.getScrollbarWidth = $.fn.modal.Constructor.prototype._getScrollbarWidth
// Simulate scrollbars
$('html').css('padding-right', '16px')
@@ -285,6 +280,23 @@ $(function () {
.bootstrapModal('show')
})
QUnit.test('should add aria-modal attribute when shown, remove it again when hidden', function (assert) {
assert.expect(3)
var done = assert.async()
$('<div id="modal-test"/>')
.on('shown.bs.modal', function () {
assert.ok($('#modal-test').is('[aria-modal]'), 'aria-modal attribute added')
assert.strictEqual($('#modal-test').attr('aria-modal'), 'true', 'correct aria-modal="true" added')
$(this).bootstrapModal('hide')
})
.on('hidden.bs.modal', function () {
assert.notOk($('#modal-test').is('[aria-modal]'), 'aria-modal attribute removed')
done()
})
.bootstrapModal('show')
})
QUnit.test('should close reopened modal with [data-dismiss="modal"] click', function (assert) {
assert.expect(2)
var done = assert.async()
@@ -607,36 +619,40 @@ $(function () {
assert.expect(1)
var done = assert.async()
var $toggleBtn = $('<button data-toggle="modal" data-target="&lt;div id=&quot;modal-test&quot;&gt;&lt;div class=&quot;contents&quot;&lt;div&lt;div id=&quot;close&quot; data-dismiss=&quot;modal&quot;/&gt;&lt;/div&gt;&lt;/div&gt;"/>')
.appendTo('#qunit-fixture')
try {
var $toggleBtn = $('<button data-toggle="modal" data-target="&lt;div id=&quot;modal-test&quot;&gt;&lt;div class=&quot;contents&quot;&lt;div&lt;div id=&quot;close&quot; data-dismiss=&quot;modal&quot;/&gt;&lt;/div&gt;&lt;/div&gt;"/>')
.appendTo('#qunit-fixture')
$toggleBtn.trigger('click')
setTimeout(function () {
$toggleBtn.trigger('click')
} catch (e) {
assert.strictEqual($('#modal-test').length, 0, 'target has not been parsed and added to the document')
done()
}, 1)
}
})
QUnit.test('should not execute js from target', function (assert) {
assert.expect(0)
var done = assert.async()
// This toggle button contains XSS payload in its data-target
// Note: it uses the onerror handler of an img element to execute the js, because a simple script element does not work here
// a script element works in manual tests though, so here it is likely blocked by the qunit framework
var $toggleBtn = $('<button data-toggle="modal" data-target="&lt;div&gt;&lt;image src=&quot;missing.png&quot; onerror=&quot;$(&apos;#qunit-fixture button.control&apos;).trigger(&apos;click&apos;)&quot;&gt;&lt;/div&gt;"/>')
.appendTo('#qunit-fixture')
// The XSS payload above does not have a closure over this function and cannot access the assert object directly
// However, it can send a click event to the following control button, which will then fail the assert
$('<button>')
.addClass('control')
.on('click', function () {
assert.notOk(true, 'XSS payload is not executed as js')
})
.appendTo('#qunit-fixture')
try {
// This toggle button contains XSS payload in its data-target
// Note: it uses the onerror handler of an img element to execute the js, because a simple script element does not work here
// a script element works in manual tests though, so here it is likely blocked by the qunit framework
var $toggleBtn = $('<button data-toggle="modal" data-target="&lt;div&gt;&lt;image src=&quot;missing.png&quot; onerror=&quot;$(&apos;#qunit-fixture button.control&apos;).trigger(&apos;click&apos;)&quot;&gt;&lt;/div&gt;"/>')
.appendTo('#qunit-fixture')
// The XSS payload above does not have a closure over this function and cannot access the assert object directly
// However, it can send a click event to the following control button, which will then fail the assert
$('<button>')
.addClass('control')
.on('click', function () {
assert.notOk(true, 'XSS payload is not executed as js')
})
.appendTo('#qunit-fixture')
$toggleBtn.trigger('click')
setTimeout(done, 500)
$toggleBtn.trigger('click')
} catch (e) {
done()
}
})
QUnit.test('should not try to open a modal which is already visible', function (assert) {
@@ -654,4 +670,127 @@ $(function () {
.bootstrapModal('show')
.bootstrapModal('hide')
})
QUnit.test('transition duration should be the modal-dialog duration before triggering shown event', function (assert) {
assert.expect(2)
var done = assert.async()
var style = [
'<style>',
' .modal.fade .modal-dialog {',
' transition: -webkit-transform .3s ease-out;',
' transition: transform .3s ease-out;',
' transition: transform .3s ease-out,-webkit-transform .3s ease-out;',
' -webkit-transform: translate(0,-50px);',
' transform: translate(0,-50px);',
' }',
'</style>'
].join('')
var $style = $(style).appendTo('head')
var modalHTML = [
'<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">',
' <div class="modal-dialog" role="document">',
' <div class="modal-content">',
' <div class="modal-body">...</div>',
' </div>',
' </div>',
'</div>'
].join('')
var beginTimestamp = 0
var $modal = $(modalHTML).appendTo('#qunit-fixture')
var $modalDialog = $('.modal-dialog')
var transitionDuration = Util.getTransitionDurationFromElement($modalDialog[0])
assert.strictEqual(transitionDuration, 300)
$modal.on('shown.bs.modal', function () {
var diff = Date.now() - beginTimestamp
assert.ok(diff < 400)
$style.remove()
done()
})
.bootstrapModal('show')
beginTimestamp = Date.now()
})
QUnit.test('should dispose modal', function (assert) {
assert.expect(3)
var done = assert.async()
var $modal = $([
'<div id="modal-test">',
' <div class="modal-dialog">',
' <div class="modal-content">',
' <div class="modal-body" />',
' </div>',
' </div>',
'</div>'
].join('')).appendTo('#qunit-fixture')
$modal.on('shown.bs.modal', function () {
var spy = sinon.spy($.fn, 'off')
$(this).bootstrapModal('dispose')
var modalDataApiEvent = []
$._data(document, 'events').click
.forEach(function (e) {
if (e.namespace === 'bs.data-api.modal') {
modalDataApiEvent.push(e)
}
})
assert.ok(typeof $(this).data('bs.modal') === 'undefined', 'modal data object was disposed')
assert.ok(spy.callCount === 4, '`jQuery.off` was called')
assert.ok(modalDataApiEvent.length === 1, '`Event.CLICK_DATA_API` on `document` was not removed')
$.fn.off.restore()
done()
}).bootstrapModal('show')
})
QUnit.test('should enforce focus', function (assert) {
assert.expect(4)
var done = assert.async()
var $modal = $([
'<div id="modal-test" data-show="false">',
' <div class="modal-dialog">',
' <div class="modal-content">',
' <div class="modal-body" />',
' </div>',
' </div>',
'</div>'
].join(''))
.bootstrapModal()
.appendTo('#qunit-fixture')
var modal = $modal.data('bs.modal')
var spy = sinon.spy(modal, '_enforceFocus')
var spyDocOff = sinon.spy($(document), 'off')
var spyDocOn = sinon.spy($(document), 'on')
$modal.one('shown.bs.modal', function () {
assert.ok(spy.called, '_enforceFocus called')
assert.ok(spyDocOff.withArgs('focusin.bs.modal'))
assert.ok(spyDocOn.withArgs('focusin.bs.modal'))
var spyFocus = sinon.spy(modal._element, 'focus')
var event = $.Event('focusin', {
target: $('#qunit-fixture')[0]
})
$(document).one('focusin', function () {
assert.ok(spyFocus.called)
done()
})
$(document).trigger(event)
})
.bootstrapModal('show')
})
})

View File

@@ -186,8 +186,8 @@ $(function () {
'<ul class="drop nav">' +
' <li class="dropdown"><a data-toggle="dropdown" href="#">1</a>' +
' <ul class="dropdown-menu nav">' +
' <li><a href="#1-1" data-toggle="tab">1-1</a></li>' +
' <li><a href="#1-2" data-toggle="tab">1-2</a></li>' +
' <li><a href="#a1-1" data-toggle="tab">1-1</a></li>' +
' <li><a href="#a1-2" data-toggle="tab">1-2</a></li>' +
' </ul>' +
' </li>' +
'</ul>'
@@ -198,10 +198,10 @@ $(function () {
.end()
.find('ul > li:last-child a')
.on('show.bs.tab', function (e) {
assert.strictEqual(e.relatedTarget.hash, '#1-1', 'references correct element as relatedTarget')
assert.strictEqual(e.relatedTarget.hash, '#a1-1', 'references correct element as relatedTarget')
})
.on('shown.bs.tab', function (e) {
assert.strictEqual(e.relatedTarget.hash, '#1-1', 'references correct element as relatedTarget')
assert.strictEqual(e.relatedTarget.hash, '#a1-1', 'references correct element as relatedTarget')
done()
})
.bootstrapTab('show')
@@ -414,4 +414,49 @@ $(function () {
})
.trigger($.Event('click'))
})
QUnit.test('should handle removed tabs', function (assert) {
assert.expect(1)
var done = assert.async()
var html = [
'<ul class="nav nav-tabs" role="tablist">',
' <li class="nav-item">',
' <a class="nav-link nav-tab" href="#profile" role="tab" data-toggle="tab">',
' <button class="close"><span aria-hidden="true">&times;</span></button>',
' </a>',
' </li>',
' <li class="nav-item">',
' <a id="secondNav" class="nav-link nav-tab" href="#buzz" role="tab" data-toggle="tab">',
' <button class="close"><span aria-hidden="true">&times;</span></button>',
' </a>',
' </li>',
' <li class="nav-item">',
' <a class="nav-link nav-tab" href="#references" role="tab" data-toggle="tab">',
' <button id="btnClose" class="close"><span aria-hidden="true">&times;</span></button>',
' </a>',
' </li>',
'</ul>',
'<div class="tab-content">',
' <div role="tabpanel" class="tab-pane fade show active" id="profile">test 1</div>',
' <div role="tabpanel" class="tab-pane fade" id="buzz">test 2</div>',
' <div role="tabpanel" class="tab-pane fade" id="references">test 3</div>',
'</div>'
].join('')
$(html).appendTo('#qunit-fixture')
$('#secondNav').on('shown.bs.tab', function () {
assert.strictEqual($('.nav-tab').length, 2)
done()
})
$('#btnClose').one('click', function () {
var tabId = $(this).parents('a').attr('href')
$(this).parents('li').remove()
$(tabId).remove()
$('.nav-tabs a:last').bootstrapTab('show')
})
.trigger($.Event('click'))
})
})

View File

@@ -0,0 +1,239 @@
$(function () {
'use strict'
if (typeof bootstrap !== 'undefined') {
window.Toast = bootstrap.Toast
}
QUnit.module('toast plugin')
QUnit.test('should be defined on jquery object', function (assert) {
assert.expect(1)
assert.ok($(document.body).toast, 'toast method is defined')
})
QUnit.module('toast', {
beforeEach: function () {
// Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
$.fn.bootstrapToast = $.fn.toast.noConflict()
},
afterEach: function () {
$.fn.toast = $.fn.bootstrapToast
delete $.fn.bootstrapToast
$('#qunit-fixture').html('')
}
})
QUnit.test('should provide no conflict', function (assert) {
assert.expect(1)
assert.strictEqual(typeof $.fn.toast, 'undefined', 'toast was set back to undefined (org value)')
})
QUnit.test('should return the current version', function (assert) {
assert.expect(1)
assert.strictEqual(typeof Toast.VERSION, 'string')
})
QUnit.test('should throw explicit error on undefined method', function (assert) {
assert.expect(1)
var $el = $('<div/>')
$el.bootstrapToast()
try {
$el.bootstrapToast('noMethod')
} catch (err) {
assert.strictEqual(err.message, 'No method named "noMethod"')
}
})
QUnit.test('should return jquery collection containing the element', function (assert) {
assert.expect(2)
var $el = $('<div/>')
var $toast = $el.bootstrapToast()
assert.ok($toast instanceof $, 'returns jquery collection')
assert.strictEqual($toast[0], $el[0], 'collection contains element')
})
QUnit.test('should auto hide', function (assert) {
assert.expect(1)
var done = assert.async()
var toastHtml =
'<div class="toast" data-delay="1">' +
'<div class="toast-body">' +
'a simple toast' +
'</div>' +
'</div>'
var $toast = $(toastHtml)
.bootstrapToast()
.appendTo($('#qunit-fixture'))
$toast.on('hidden.bs.toast', function () {
assert.strictEqual($toast.hasClass('show'), false)
done()
})
.bootstrapToast('show')
})
QUnit.test('should not add fade class', function (assert) {
assert.expect(1)
var done = assert.async()
var toastHtml =
'<div class="toast" data-delay="1" data-animation="false">' +
'<div class="toast-body">' +
'a simple toast' +
'</div>' +
'</div>'
var $toast = $(toastHtml)
.bootstrapToast()
.appendTo($('#qunit-fixture'))
$toast.on('shown.bs.toast', function () {
assert.strictEqual($toast.hasClass('fade'), false)
done()
})
.bootstrapToast('show')
})
QUnit.test('should allow to hide toast manually', function (assert) {
assert.expect(1)
var done = assert.async()
var toastHtml =
'<div class="toast" data-delay="1" data-autohide="false">' +
'<div class="toast-body">' +
'a simple toast' +
'</div>' +
'</div>'
var $toast = $(toastHtml)
.bootstrapToast()
.appendTo($('#qunit-fixture'))
$toast
.on('shown.bs.toast', function () {
$toast.bootstrapToast('hide')
})
.on('hidden.bs.toast', function () {
assert.strictEqual($toast.hasClass('show'), false)
done()
})
.bootstrapToast('show')
})
QUnit.test('should do nothing when we call hide on a non shown toast', function (assert) {
assert.expect(1)
var $toast = $('<div />')
.bootstrapToast()
.appendTo($('#qunit-fixture'))
var spy = sinon.spy($toast[0].classList, 'contains')
$toast.bootstrapToast('hide')
assert.strictEqual(spy.called, true)
})
QUnit.test('should allow to destroy toast', function (assert) {
assert.expect(2)
var $toast = $('<div />')
.bootstrapToast()
.appendTo($('#qunit-fixture'))
assert.ok(typeof $toast.data('bs.toast') !== 'undefined')
$toast.bootstrapToast('dispose')
assert.ok(typeof $toast.data('bs.toast') === 'undefined')
})
QUnit.test('should allow to destroy toast and hide it before that', function (assert) {
assert.expect(4)
var done = assert.async()
var toastHtml =
'<div class="toast" data-delay="0" data-autohide="false">' +
'<div class="toast-body">' +
'a simple toast' +
'</div>' +
'</div>'
var $toast = $(toastHtml)
.bootstrapToast()
.appendTo($('#qunit-fixture'))
$toast.one('shown.bs.toast', function () {
setTimeout(function () {
assert.ok($toast.hasClass('show'))
assert.ok(typeof $toast.data('bs.toast') !== 'undefined')
$toast.bootstrapToast('dispose')
assert.ok(typeof $toast.data('bs.toast') === 'undefined')
assert.ok($toast.hasClass('show') === false)
done()
}, 1)
})
.bootstrapToast('show')
})
QUnit.test('should allow to config in js', function (assert) {
assert.expect(1)
var done = assert.async()
var toastHtml =
'<div class="toast">' +
'<div class="toast-body">' +
'a simple toast' +
'</div>' +
'</div>'
var $toast = $(toastHtml)
.bootstrapToast({
delay: 1
})
.appendTo($('#qunit-fixture'))
$toast.on('shown.bs.toast', function () {
assert.strictEqual($toast.hasClass('show'), true)
done()
})
.bootstrapToast('show')
})
QUnit.test('should close toast when close element with data-dismiss attribute is set', function (assert) {
assert.expect(2)
var done = assert.async()
var toastHtml =
'<div class="toast" data-delay="1" data-autohide="false" data-animation="false">' +
'<button type="button" class="ml-2 mb-1 close" data-dismiss="toast">' +
'close' +
'</button>' +
'</div>'
var $toast = $(toastHtml)
.bootstrapToast()
.appendTo($('#qunit-fixture'))
$toast
.on('shown.bs.toast', function () {
assert.strictEqual($toast.hasClass('show'), true)
var button = $toast.find('.close')
button.trigger('click')
})
.on('hidden.bs.toast', function () {
assert.strictEqual($toast.hasClass('show'), false)
done()
})
.bootstrapToast('show')
})
})

View File

@@ -414,6 +414,52 @@ $(function () {
.bootstrapTooltip('show')
})
QUnit.test('should place tooltips inside a specific container when container is an element', function (assert) {
assert.expect(3)
var done = assert.async()
var $container = $('<div></div>').appendTo('#qunit-fixture')
var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>')
.appendTo('#qunit-fixture')
.bootstrapTooltip({
container: $container[0]
})
$tooltip
.one('shown.bs.tooltip', function () {
assert.strictEqual($container.find('.tooltip').length, 1)
assert.strictEqual($('#qunit-fixture > .tooltip').length, 0, 'tooltip is not in parent')
$tooltip.bootstrapTooltip('hide')
})
.one('hidden.bs.tooltip', function () {
assert.strictEqual($container.find('.tooltip').length, 0, 'tooltip was removed from dom')
done()
})
.bootstrapTooltip('show')
})
QUnit.test('should place tooltips inside a specific container when container is a selector', function (assert) {
assert.expect(3)
var done = assert.async()
var $container = $('<div id="container"></div>').appendTo('#qunit-fixture')
var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>')
.appendTo('#qunit-fixture')
.bootstrapTooltip({
container: '#container'
})
$tooltip
.one('shown.bs.tooltip', function () {
assert.strictEqual($container.find('.tooltip').length, 1)
assert.strictEqual($('#qunit-fixture > .tooltip').length, 0, 'tooltip is not in parent')
$tooltip.bootstrapTooltip('hide')
})
.one('hidden.bs.tooltip', function () {
assert.strictEqual($container.find('.tooltip').length, 0, 'tooltip was removed from dom')
done()
})
.bootstrapTooltip('show')
})
QUnit.test('should add position class before positioning so that position-specific styles are taken into account', function (assert) {
assert.expect(2)
var done = assert.async()
@@ -517,7 +563,6 @@ $(function () {
$tooltip.bootstrapTooltip('show')
} catch (err) {
passed = false
console.log(err)
}
assert.ok(passed, '.tooltip(\'show\') should not throw an error if element no longer is in dom')
@@ -862,6 +907,44 @@ $(function () {
.modal('show')
})
QUnit.test('should allow to close modal if the tooltip element is detached', function (assert) {
assert.expect(1)
var done = assert.async()
var templateHTML = [
'<div id="modal-test" class="modal">',
' <div class="modal-dialog" role="document">',
' <div class="modal-content">',
' <div class="modal-body">',
' <a id="tooltipTest" href="#" data-toggle="tooltip" title="Some tooltip text!">Tooltip</a>',
' </div>',
' </div>',
' </div>',
'</div>'
].join('')
$(templateHTML).appendTo('#qunit-fixture')
var $tooltip = $('#tooltipTest')
var $modal = $('#modal-test')
$tooltip.on('shown.bs.tooltip', function () {
$tooltip.detach()
$tooltip.bootstrapTooltip('dispose')
$modal.modal('hide')
})
$modal.on('shown.bs.modal', function () {
$tooltip.bootstrapTooltip({
trigger: 'manuel'
})
.bootstrapTooltip('show')
})
.on('hidden.bs.modal', function () {
assert.ok(true, 'modal hidden')
done()
})
.modal('show')
})
QUnit.test('should reset tip classes when hidden event triggered', function (assert) {
assert.expect(2)
var done = assert.async()
@@ -966,4 +1049,24 @@ $(function () {
assert.ok(tooltip.tip === $tipTest[0])
})
QUnit.test('should toggle enabled', function (assert) {
assert.expect(3)
var $tooltip = $('<a href="#" rel="tooltip" data-trigger="click" title="Another tooltip"/>')
.appendTo('#qunit-fixture')
.bootstrapTooltip()
var tooltip = $tooltip.data('bs.tooltip')
assert.strictEqual(tooltip._isEnabled, true)
tooltip.toggleEnabled()
assert.strictEqual(tooltip._isEnabled, false)
tooltip.toggleEnabled()
assert.strictEqual(tooltip._isEnabled, true)
})
})

View File

@@ -20,6 +20,19 @@ $(function () {
assert.strictEqual(Util.getSelectorFromElement($el2[0]), null)
})
QUnit.test('Util.getSelectorFromElement should throw error when there is a bad selector', function (assert) {
assert.expect(2)
var $el = $('<div data-target="#1"></div>').appendTo($('#qunit-fixture'))
try {
assert.ok(true, 'trying to use a bad selector')
Util.getSelectorFromElement($el[0])
} catch (e) {
assert.ok(e instanceof DOMException)
}
})
QUnit.test('Util.typeCheckConfig should thrown an error when a bad config is passed', function (assert) {
assert.expect(1)
var namePlugin = 'collapse'
@@ -62,6 +75,16 @@ $(function () {
assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 400)
})
QUnit.test('Util.getTransitionDurationFromElement should return the addition of transition-delay and transition-duration', function (assert) {
assert.expect(2)
var $fixture = $('#qunit-fixture')
var $div = $('<div style="transition: all 0s 150ms ease-out;"></div>').appendTo($fixture)
var $div2 = $('<div style="transition: all .25s 30ms ease-out;"></div>').appendTo($fixture)
assert.strictEqual(Util.getTransitionDurationFromElement($div[0]), 150)
assert.strictEqual(Util.getTransitionDurationFromElement($div2[0]), 280)
})
QUnit.test('Util.getTransitionDurationFromElement should get the first transition duration if multiple transition durations are defined', function (assert) {
assert.expect(1)
var $div = $('<div style="transition: transform .3s ease-out, opacity .2s;"></div>').appendTo($('#qunit-fixture'))
@@ -101,4 +124,41 @@ $(function () {
assert.expect(1)
assert.ok(Util.supportsTransitionEnd())
})
QUnit.test('Util.findShadowRoot should find the shadow DOM root', function (assert) {
// Only for newer browsers
if (!document.documentElement.attachShadow) {
assert.expect(0)
return
}
assert.expect(2)
var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
var shadowRoot = $div[0].attachShadow({
mode: 'open'
})
assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot))
shadowRoot.innerHTML = '<button>Shadow Button</button>'
assert.equal(shadowRoot, Util.findShadowRoot(shadowRoot.firstChild))
})
QUnit.test('Util.findShadowRoot should return null when attachShadow is not available', function (assert) {
assert.expect(1)
var $div = $('<div id="test"></div>').appendTo($('#qunit-fixture'))
if (!document.documentElement.attachShadow) {
assert.equal(null, Util.findShadowRoot($div[0]))
} else {
var sandbox = sinon.createSandbox()
sandbox.replace(document.documentElement, 'attachShadow', function () {
// to avoid empty function
return $div
})
assert.equal(null, Util.findShadowRoot($div[0]))
sandbox.restore()
}
})
})

View File

@@ -51,7 +51,7 @@
</div>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/alert.js"></script>
</body>

View File

@@ -44,7 +44,7 @@
</div>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/button.js"></script>
</body>

View File

@@ -45,7 +45,7 @@
</div>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/carousel.js"></script>
<script>

View File

@@ -71,7 +71,7 @@
</div>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/collapse.js"></script>
</body>

View File

@@ -50,7 +50,7 @@
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="https://example.com" id="dropdown2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<a class="nav-link dropdown-toggle" href="#" id="dropdown2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu" aria-labelledby="dropdown2">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
@@ -203,8 +203,8 @@
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.1/assets/js/vendor/popper.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/dropdown.js"></script>
<script src="../../dist/collapse.js"></script>

View File

@@ -205,8 +205,8 @@
</button>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.1/assets/js/vendor/popper.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/modal.js"></script>
<script src="../../dist/collapse.js"></script>

View File

@@ -31,8 +31,8 @@
</button>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.1/assets/js/vendor/popper.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/tooltip.js"></script>
<script src="../../dist/popover.js"></script>

View File

@@ -86,7 +86,7 @@
<p>Ad leggings keytar, brunch id art party dolor labore.</p>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/scrollspy.js"></script>
<script src="../../dist/dropdown.js"></script>

View File

@@ -225,8 +225,8 @@
</div>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.1/assets/js/vendor/popper.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/tab.js"></script>
<script src="../../dist/dropdown.js"></script>

View File

@@ -0,0 +1,72 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="../../../dist/css/bootstrap.min.css">
<title>Toast</title>
<style>
.notifications {
position: absolute;
top: 10px;
right: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>Toast <small>Bootstrap Visual Test</small></h1>
<div class="row mt-3">
<div class="col-md-12">
<button id="btnShowToast" class="btn btn-primary">Show toast</button>
<button id="btnHideToast" class="btn btn-primary">Hide toast</button>
</div>
</div>
</div>
<div class="notifications">
<div id="toastAutoHide" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-delay="2000">
<div class="toast-header">
<span class="d-block bg-primary rounded mr-2" style="width: 20px; height: 20px;"></span>
<strong class="mr-auto">Bootstrap</strong>
<small>11 mins ago</small>
</div>
<div class="toast-body">
Hello, world! This is a toast message with <strong>autohide</strong> in 2 seconds
</div>
</div>
<div class="toast" data-autohide="false" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<span class="d-block bg-primary rounded mr-2" style="width: 20px; height: 20px;"></span>
<strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">2 seconds ago</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Heads up, toasts will stack automatically
</div>
</div>
</div>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/toast.js"></script>
<script>
$(function () {
$('.toast').toast()
$('#btnShowToast').on('click', function () {
$('.toast').toast('show')
})
$('#btnHideToast').on('click', function () {
$('.toast').toast('hide')
})
})
</script>
</body>
</html>

View File

@@ -51,25 +51,51 @@
<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="left" title="Tooltip with XSS" data-container="<img src=1 onerror=alert(123) />">
Tooltip with XSS
</button>
<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="left" title="Tooltip with container" data-container="#customContainer">
Tooltip with container
<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="left" title="Tooltip with container (selector)" data-container="#customContainer">
Tooltip with container (selector)
</button>
<button id="tooltipElement" type="button" class="btn btn-secondary" data-placement="left" title="Tooltip with container (element)">
Tooltip with container (element)
</button>
<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-html="true" title="<em>Tooltip</em> <u>with</u> <b>HTML</b>">
Tooltip with HTML
</button>
</p>
</div>
<div id="target" title="Test tooltip on transformed element"></div>
<div class="row">
<div class="col-sm-3">
<div id="target" title="Test tooltip on transformed element"></div>
</div>
<div id="shadow" class="pt-5"></div>
</div>
<div id="customContainer"></div>
</div>
<script src="../../../site/docs/4.1/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../site/docs/4.1/assets/js/vendor/popper.min.js"></script>
<script src="../../../site/docs/4.2/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../../node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/tooltip.js"></script>
<script>
$(function () {
if (typeof document.body.attachShadow === 'function') {
var shadowRoot = $('#shadow')[0].attachShadow({ mode: 'open' })
shadowRoot.innerHTML =
'<button type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top in a shadow dom">' +
' Tooltip on top in a shadow dom' +
'</button>' +
'<button id="secondTooltip" type="button" class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top in a shadow dom with container option">' +
' Tooltip on top in a shadow dom' +
'</button>'
$(shadowRoot.firstChild).tooltip()
$(shadowRoot.getElementById('secondTooltip')).tooltip({
container: shadowRoot
})
}
$('[data-toggle="tooltip"]').tooltip()
$('#tooltipElement').tooltip({
container: $('#customContainer')[0]
})
$('#target').tooltip({
placement : 'top',
trigger : 'manual'