aboutsummaryrefslogtreecommitdiff
path: root/js/tests
diff options
context:
space:
mode:
authorPierre Vanduynslager <[email protected]>2017-04-08 18:43:25 -0400
committerGitHub <[email protected]>2017-04-08 18:43:25 -0400
commitfe72daf2b34263d3cfc9bc77e9998cd22adfa34d (patch)
tree15dc9fc6fcb513362ba112d52ab01b568b423709 /js/tests
parentf5cc59145642d78d7abbdf38fee1905786da5367 (diff)
parentfeb35b94a61c4d6016be8d1773a79a6bbe57d856 (diff)
downloadbootstrap-fe72daf2b34263d3cfc9bc77e9998cd22adfa34d.tar.xz
bootstrap-fe72daf2b34263d3cfc9bc77e9998cd22adfa34d.zip
Merge branch 'v4-dev' into dropdown-keyboard
Diffstat (limited to 'js/tests')
-rw-r--r--js/tests/unit/carousel.js43
-rw-r--r--js/tests/unit/collapse.js84
-rw-r--r--js/tests/unit/modal.js135
-rw-r--r--js/tests/unit/popover.js18
-rw-r--r--js/tests/unit/scrollspy.js160
-rw-r--r--js/tests/unit/tab.js32
-rw-r--r--js/tests/unit/tooltip.js33
-rw-r--r--js/tests/vendor/qunit.css9
-rw-r--r--js/tests/vendor/qunit.js823
-rw-r--r--js/tests/visual/carousel.html24
-rw-r--r--js/tests/visual/collapse.html37
-rw-r--r--js/tests/visual/dropdown.html6
-rw-r--r--js/tests/visual/modal.html23
-rw-r--r--js/tests/visual/scrollspy.html23
-rw-r--r--js/tests/visual/tab.html33
-rw-r--r--js/tests/visual/tooltip.html19
16 files changed, 1155 insertions, 347 deletions
diff --git a/js/tests/unit/carousel.js b/js/tests/unit/carousel.js
index dbdea921a..00b438bb2 100644
--- a/js/tests/unit/carousel.js
+++ b/js/tests/unit/carousel.js
@@ -341,6 +341,49 @@ $(function () {
.bootstrapCarousel('next')
})
+ QUnit.test('should fire slid and slide events with from and to', function (assert) {
+ assert.expect(4)
+ var template = '<div id="myCarousel" class="carousel slide">'
+ + '<div class="carousel-inner">'
+ + '<div class="carousel-item active">'
+ + '<img alt="">'
+ + '<div class="carousel-caption">'
+ + '<h4>First Thumbnail label</h4>'
+ + '</div>'
+ + '</div>'
+ + '<div class="carousel-item">'
+ + '<img alt="">'
+ + '<div class="carousel-caption">'
+ + '<h4>Second Thumbnail label</h4>'
+ + '</div>'
+ + '</div>'
+ + '<div class="carousel-item">'
+ + '<img alt="">'
+ + '<div class="carousel-caption">'
+ + '<h4>Third Thumbnail label</h4>'
+ + '</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 done = assert.async()
+ $(template)
+ .on('slid.bs.carousel', function (e) {
+ assert.ok(e.from !== undefined, 'from present')
+ assert.ok(e.to !== undefined, 'to present')
+ $(this).off()
+ done()
+ })
+ .on('slide.bs.carousel', function (e) {
+ assert.ok(e.from !== undefined, 'from present')
+ assert.ok(e.to !== undefined, 'to present')
+ $(this).off('slide.bs.carousel')
+ })
+ .bootstrapCarousel('next')
+ })
+
QUnit.test('should set interval from data attribute', function (assert) {
assert.expect(4)
var templateHTML = '<div id="myCarousel" class="carousel slide">'
diff --git a/js/tests/unit/collapse.js b/js/tests/unit/collapse.js
index 713930433..e7083f56d 100644
--- a/js/tests/unit/collapse.js
+++ b/js/tests/unit/collapse.js
@@ -265,17 +265,17 @@ $(function () {
+ '</div>'
var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.card')
- var $target1 = $('<a role="button" data-toggle="collapse" href="#body1" data-parent="#accordion"/>').appendTo($groups.eq(0))
+ var $target1 = $('<a role="button" data-toggle="collapse" href="#body1" />').appendTo($groups.eq(0))
- $('<div id="body1" class="show"/>').appendTo($groups.eq(0))
+ $('<div id="body1" class="show" data-parent="#accordion"/>').appendTo($groups.eq(0))
- var $target2 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body2" data-parent="#accordion"/>').appendTo($groups.eq(1))
+ var $target2 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body2" />').appendTo($groups.eq(1))
- $('<div id="body2"/>').appendTo($groups.eq(1))
+ $('<div id="body2" data-parent="#accordion"/>').appendTo($groups.eq(1))
- var $target3 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body3" data-parent="#accordion"/>').appendTo($groups.eq(2))
+ var $target3 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body3" />').appendTo($groups.eq(2))
- $('<div id="body3"/>')
+ $('<div id="body3" data-parent="#accordion"/>')
.appendTo($groups.eq(2))
.on('shown.bs.collapse', function () {
assert.ok($target1.hasClass('collapsed'), 'inactive target 1 does have class "collapsed"')
@@ -299,17 +299,17 @@ $(function () {
+ '</div>'
var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.card')
- var $target1 = $('<a role="button" data-toggle="collapse" href="#body1" data-parent=".accordion"/>').appendTo($groups.eq(0))
+ var $target1 = $('<a role="button" data-toggle="collapse" href="#body1"/>').appendTo($groups.eq(0))
- $('<div id="body1" class="show"/>').appendTo($groups.eq(0))
+ $('<div id="body1" class="show" data-parent=".accordion"/>').appendTo($groups.eq(0))
- var $target2 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body2" data-parent=".accordion"/>').appendTo($groups.eq(1))
+ var $target2 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body2"/>').appendTo($groups.eq(1))
- $('<div id="body2"/>').appendTo($groups.eq(1))
+ $('<div id="body2" data-parent=".accordion"/>').appendTo($groups.eq(1))
- var $target3 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body3" data-parent=".accordion"/>').appendTo($groups.eq(2))
+ var $target3 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body3"/>').appendTo($groups.eq(2))
- $('<div id="body3"/>')
+ $('<div id="body3" data-parent=".accordion"/>')
.appendTo($groups.eq(2))
.on('shown.bs.collapse', function () {
assert.ok($target1.hasClass('collapsed'), 'inactive target 1 does have class "collapsed"')
@@ -401,17 +401,17 @@ $(function () {
+ '</div>'
var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.card')
- var $target1 = $('<a role="button" data-toggle="collapse" href="#body1" data-parent="#accordion"/>').appendTo($groups.eq(0))
+ var $target1 = $('<a role="button" data-toggle="collapse" href="#body1"/>').appendTo($groups.eq(0))
- $('<div id="body1" aria-expanded="true" class="show"/>').appendTo($groups.eq(0))
+ $('<div id="body1" aria-expanded="true" class="show" data-parent="#accordion"/>').appendTo($groups.eq(0))
- var $target2 = $('<a role="button" data-toggle="collapse" href="#body2" data-parent="#accordion" class="collapsed" />').appendTo($groups.eq(1))
+ var $target2 = $('<a role="button" data-toggle="collapse" href="#body2" class="collapsed" aria-expanded="false" />').appendTo($groups.eq(1))
- $('<div id="body2" aria-expanded="false"/>').appendTo($groups.eq(1))
+ $('<div id="body2" aria-expanded="false" data-parent="#accordion"/>').appendTo($groups.eq(1))
- var $target3 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body3" data-parent="#accordion"/>').appendTo($groups.eq(2))
+ var $target3 = $('<a class="collapsed" data-toggle="collapse" role="button" href="#body3"/>').appendTo($groups.eq(2))
- $('<div id="body3" aria-expanded="false"/>')
+ $('<div id="body3" aria-expanded="false" data-parent="#accordion"/>')
.appendTo($groups.eq(2))
.on('shown.bs.collapse', function () {
assert.strictEqual($target1.attr('aria-expanded'), 'false', 'inactive target 1 has aria-expanded="false"')
@@ -435,16 +435,16 @@ $(function () {
var showFired = false
var $groups = $(accordionHTML).appendTo('#qunit-fixture').find('.card')
- var $target1 = $('<a role="button" data-toggle="collapse" href="#body1" data-parent="#accordion"/>').appendTo($groups.eq(0))
+ var $target1 = $('<a role="button" data-toggle="collapse" href="#body1"/>').appendTo($groups.eq(0))
- $('<div id="body1" class="collapse"/>')
+ $('<div id="body1" class="collapse" data-parent="#accordion"/>')
.appendTo($groups.eq(0))
.on('show.bs.collapse', function () {
showFired = true
})
- var $target2 = $('<a role="button" data-toggle="collapse" href="#body2" data-parent="#accordion"/>').appendTo($groups.eq(1))
- var $body2 = $('<div id="body2" class="collapse"/>').appendTo($groups.eq(1))
+ var $target2 = $('<a role="button" data-toggle="collapse" href="#body2"/>').appendTo($groups.eq(1))
+ var $body2 = $('<div id="body2" class="collapse" data-parent="#accordion"/>').appendTo($groups.eq(1))
$target2.trigger('click')
@@ -490,4 +490,44 @@ $(function () {
.bootstrapCollapse('show')
})
+ QUnit.test('should allow accordion to use children other than card', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var accordionHTML = '<div id="accordion" data-children=".item">'
+ + '<div class="item">'
+ + '<a id="linkTrigger" data-parent="#accordion" data-toggle="collapse" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"></a>'
+ + '<div id="collapseOne" class="collapse" role="tabpanel" aria-labelledby="headingThree"></div>'
+ + '</div>'
+ + '<div class="item">'
+ + '<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo"></a>'
+ + '<div id="collapseTwo" class="collapse show" role="tabpanel" aria-labelledby="headingTwo"></div>'
+ + '</div>'
+ + '</div>'
+
+ $(accordionHTML).appendTo('#qunit-fixture')
+ var $target = $('#linkTrigger')
+ $('#collapseOne').on('shown.bs.collapse', function () {
+ assert.ok($(this).hasClass('show'))
+ assert.ok(!$('#collapseTwo').hasClass('show'))
+ done()
+ })
+ $target.trigger($.Event('click'))
+ })
+
+ QUnit.test('should not prevent event for input', function (assert) {
+ assert.expect(3)
+ var done = assert.async()
+ var $target = $('<input type="checkbox" data-toggle="collapse" data-target="#collapsediv1" />').appendTo('#qunit-fixture')
+
+ $('<div id="collapsediv1"/>')
+ .appendTo('#qunit-fixture')
+ .on('shown.bs.collapse', function () {
+ assert.ok($(this).hasClass('show'))
+ assert.ok($target.attr('aria-expanded') === 'true')
+ assert.ok($target.prop('checked'))
+ done()
+ })
+
+ $target.trigger($.Event('click'))
+ })
})
diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js
index 84492cec2..fc6c4f38c 100644
--- a/js/tests/unit/modal.js
+++ b/js/tests/unit/modal.js
@@ -9,6 +9,19 @@ $(function () {
})
QUnit.module('modal', {
+ before: 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
+ }
+ },
beforeEach: function () {
// Run all tests in noConflict mode -- it's the only way to ensure that the plugin works in noConflict mode
$.fn.bootstrapModal = $.fn.modal.noConflict()
@@ -336,81 +349,147 @@ $(function () {
$toggleBtn.trigger('click')
})
- QUnit.test('should restore inline body padding after closing', function (assert) {
+ QUnit.test('should adjust the inline body padding when opening and restore when closing', function (assert) {
assert.expect(2)
var done = assert.async()
- var originalBodyPad = 0
var $body = $(document.body)
-
- $body.css('padding-right', originalBodyPad)
+ var originalPadding = $body.css('padding-right')
$('<div id="modal-test"/>')
.on('hidden.bs.modal', function () {
- var currentBodyPad = parseInt($body.css('padding-right'), 10)
- assert.notStrictEqual($body.attr('style'), '', 'body has non-empty style attribute')
- assert.strictEqual(currentBodyPad, originalBodyPad, 'original body padding was not changed')
+ var currentPadding = $body.css('padding-right')
+ assert.strictEqual(currentPadding, originalPadding, 'body padding should be reset after closing')
$body.removeAttr('style')
done()
})
.on('shown.bs.modal', function () {
+ var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
+ var currentPadding = $body.css('padding-right')
+ assert.strictEqual(currentPadding, expectedPadding, 'body padding should be adjusted while opening')
$(this).bootstrapModal('hide')
})
.bootstrapModal('show')
})
- QUnit.test('should ignore values set via CSS when trying to restore body padding after closing', function (assert) {
- assert.expect(1)
+ QUnit.test('should store the original body padding in data-padding-right before showing', function (assert) {
+ assert.expect(2)
var done = assert.async()
var $body = $(document.body)
- var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head')
+ var originalPadding = '0px'
+ $body.css('padding-right', originalPadding)
$('<div id="modal-test"/>')
.on('hidden.bs.modal', function () {
- assert.ok(!$body.attr('style'), 'body does not have inline padding set')
- $style.remove()
+ assert.strictEqual($body.data('padding-right'), undefined, 'data-padding-right should be cleared after closing')
+ $body.removeAttr('style')
done()
})
.on('shown.bs.modal', function () {
+ assert.strictEqual($body.data('padding-right'), originalPadding, 'original body padding should be stored in data-padding-right')
$(this).bootstrapModal('hide')
})
.bootstrapModal('show')
})
- QUnit.test('should have a paddingRight when the modal is taller than the viewport', function (assert) {
+ QUnit.test('should adjust the inline padding of fixed elements when opening and restore when closing', function (assert) {
assert.expect(2)
var done = assert.async()
- $('<div class="fixed-top fixed-bottom sticky-top is-fixed">@Johann-S</div>').appendTo('#qunit-fixture')
- $('.fixed-top, .fixed-bottom, .is-fixed, .sticky-top').css('padding-right', '10px')
+ var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
+ var originalPadding = $element.css('padding-right')
$('<div id="modal-test"/>')
+ .on('hidden.bs.modal', function () {
+ var currentPadding = $element.css('padding-right')
+ assert.strictEqual(currentPadding, originalPadding, 'fixed element padding should be reset after closing')
+ $element.remove()
+ done()
+ })
.on('shown.bs.modal', function () {
- var paddingRight = parseInt($(document.body).css('padding-right'), 10)
- assert.strictEqual(isNaN(paddingRight), false)
- assert.strictEqual(paddingRight !== 0, true)
- $(document.body).css('padding-right', '') // Because test case "should ignore other inline styles when trying to restore body padding after closing" fail if not
+ var expectedPadding = parseFloat(originalPadding) + $(this).getScrollbarWidth() + 'px'
+ var currentPadding = $element.css('padding-right')
+ assert.strictEqual(currentPadding, expectedPadding, 'fixed element padding should be adjusted while opening')
+ $(this).bootstrapModal('hide')
+ })
+ .bootstrapModal('show')
+ })
+
+ QUnit.test('should store the original padding of fixed elements in data-padding-right before showing', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var $element = $('<div class="fixed-top"></div>').appendTo('#qunit-fixture')
+ var originalPadding = '0px'
+ $element.css('padding-right', originalPadding)
+
+ $('<div id="modal-test"/>')
+ .on('hidden.bs.modal', function () {
+ assert.strictEqual($element.data('padding-right'), undefined, 'data-padding-right should be cleared after closing')
+ $element.remove()
done()
})
+ .on('shown.bs.modal', function () {
+ assert.strictEqual($element.data('padding-right'), originalPadding, 'original fixed element padding should be stored in data-padding-right')
+ $(this).bootstrapModal('hide')
+ })
.bootstrapModal('show')
})
- QUnit.test('should remove padding-right on modal after closing', function (assert) {
- assert.expect(3)
+ QUnit.test('should adjust the inline margin of the navbar-toggler when opening and restore when closing', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var $element = $('<div class="navbar-toggler"></div>').appendTo('#qunit-fixture')
+ var originalMargin = $element.css('margin-right')
+
+ $('<div id="modal-test"/>')
+ .on('hidden.bs.modal', function () {
+ var currentMargin = $element.css('margin-right')
+ assert.strictEqual(currentMargin, originalMargin, 'navbar-toggler margin should be reset after closing')
+ $element.remove()
+ done()
+ })
+ .on('shown.bs.modal', function () {
+ var expectedMargin = parseFloat(originalMargin) + $(this).getScrollbarWidth() + 'px'
+ var currentMargin = $element.css('margin-right')
+ assert.strictEqual(currentMargin, expectedMargin, 'navbar-toggler margin should be adjusted while opening')
+ $(this).bootstrapModal('hide')
+ })
+ .bootstrapModal('show')
+ })
+
+ QUnit.test('should store the original margin of the navbar-toggler in data-margin-right before showing', function (assert) {
+ assert.expect(2)
var done = assert.async()
- $('<div class="fixed-top fixed-bottom is-fixed sticky-top">@Johann-S</div>').appendTo('#qunit-fixture')
- $('.fixed-top, .fixed-bottom, .is-fixed, .sticky-top').css('padding-right', '10px')
+ var $element = $('<div class="navbar-toggler"></div>').appendTo('#qunit-fixture')
+ var originalMargin = '0px'
+ $element.css('margin-right', originalMargin)
$('<div id="modal-test"/>')
+ .on('hidden.bs.modal', function () {
+ assert.strictEqual($element.data('margin-right'), undefined, 'data-margin-right should be cleared after closing')
+ $element.remove()
+ done()
+ })
.on('shown.bs.modal', function () {
- var paddingRight = parseInt($(document.body).css('padding-right'), 10)
- assert.strictEqual(isNaN(paddingRight), false)
- assert.strictEqual(paddingRight !== 0, true)
+ assert.strictEqual($element.data('margin-right'), originalMargin, 'original navbar-toggler margin should be stored in data-margin-right')
$(this).bootstrapModal('hide')
})
+ .bootstrapModal('show')
+ })
+
+ QUnit.test('should ignore values set via CSS when trying to restore body padding after closing', function (assert) {
+ assert.expect(1)
+ var done = assert.async()
+ var $body = $(document.body)
+ var $style = $('<style>body { padding-right: 42px; }</style>').appendTo('head')
+
+ $('<div id="modal-test"/>')
.on('hidden.bs.modal', function () {
- var paddingRight = parseInt($(document.body).css('padding-right'), 10)
- assert.strictEqual(paddingRight, 0)
+ assert.ok(!$body.attr('style'), 'body does not have inline padding set')
+ $style.remove()
done()
})
+ .on('shown.bs.modal', function () {
+ $(this).bootstrapModal('hide')
+ })
.bootstrapModal('show')
})
diff --git a/js/tests/unit/popover.js b/js/tests/unit/popover.js
index 64c8c556a..eaa9fa0c0 100644
--- a/js/tests/unit/popover.js
+++ b/js/tests/unit/popover.js
@@ -364,4 +364,22 @@ $(function () {
})
.modal('show')
})
+
+ QUnit.test('should convert number to string without error for content and title', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var $popover = $('<a href="#">@mdo</a>')
+ .appendTo('#qunit-fixture')
+ .bootstrapPopover({
+ title: 5,
+ content: 7
+ })
+ .on('shown.bs.popover', function () {
+ assert.strictEqual($('.popover .popover-title').text(), '5')
+ assert.strictEqual($('.popover .popover-content').text(), '7')
+ done()
+ })
+
+ $popover.bootstrapPopover('show')
+ })
})
diff --git a/js/tests/unit/scrollspy.js b/js/tests/unit/scrollspy.js
index 877ec67a2..ed84cc794 100644
--- a/js/tests/unit/scrollspy.js
+++ b/js/tests/unit/scrollspy.js
@@ -26,7 +26,7 @@ $(function () {
QUnit.test('should throw explicit error on undefined method', function (assert) {
assert.expect(1)
- var $el = $('<div/>')
+ var $el = $('<div/>').appendTo('#qunit-fixture')
$el.bootstrapScrollspy()
try {
$el.bootstrapScrollspy('noMethod')
@@ -38,7 +38,7 @@ $(function () {
QUnit.test('should return jquery collection containing the element', function (assert) {
assert.expect(2)
- var $el = $('<div/>')
+ var $el = $('<div/>').appendTo('#qunit-fixture')
var $scrollspy = $el.bootstrapScrollspy()
assert.ok($scrollspy instanceof $, 'returns jquery collection')
assert.strictEqual($scrollspy[0], $el[0], 'collection contains element')
@@ -205,6 +205,80 @@ $(function () {
.then(function () { done() })
})
+ QUnit.test('should add the active class to the correct element (nav markup)', function (assert) {
+ assert.expect(2)
+ var navbarHtml =
+ '<nav class="navbar">'
+ + '<nav class="nav">'
+ + '<a class="nav-link" id="a-1" href="#div-1">div 1</a>'
+ + '<a class="nav-link" id="a-2" href="#div-2">div 2</a>'
+ + '</nav>'
+ + '</nav>'
+ var contentHtml =
+ '<div class="content" style="overflow: auto; height: 50px">'
+ + '<div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>'
+ + '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>'
+ + '</div>'
+
+ $(navbarHtml).appendTo('#qunit-fixture')
+ var $content = $(contentHtml)
+ .appendTo('#qunit-fixture')
+ .bootstrapScrollspy({ offset: 0, target: '.navbar' })
+
+ var done = assert.async()
+ var testElementIsActiveAfterScroll = function (element, target) {
+ var deferred = $.Deferred()
+ var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
+ $content.one('scroll', function () {
+ assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
+ deferred.resolve()
+ })
+ $content.scrollTop(scrollHeight)
+ return deferred.promise()
+ }
+
+ $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
+ .then(function () { return testElementIsActiveAfterScroll('#a-2', '#div-2') })
+ .then(function () { done() })
+ })
+
+ QUnit.test('should add the active class to the correct element (list-group markup)', function (assert) {
+ assert.expect(2)
+ var navbarHtml =
+ '<nav class="navbar">'
+ + '<div class="list-group">'
+ + '<a class="list-group-item" id="a-1" href="#div-1">div 1</a>'
+ + '<a class="list-group-item" id="a-2" href="#div-2">div 2</a>'
+ + '</div>'
+ + '</nav>'
+ var contentHtml =
+ '<div class="content" style="overflow: auto; height: 50px">'
+ + '<div id="div-1" style="height: 100px; padding: 0; margin: 0">div 1</div>'
+ + '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>'
+ + '</div>'
+
+ $(navbarHtml).appendTo('#qunit-fixture')
+ var $content = $(contentHtml)
+ .appendTo('#qunit-fixture')
+ .bootstrapScrollspy({ offset: 0, target: '.navbar' })
+
+ var done = assert.async()
+ var testElementIsActiveAfterScroll = function (element, target) {
+ var deferred = $.Deferred()
+ var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top)
+ $content.one('scroll', function () {
+ assert.ok($(element).hasClass('active'), 'target:' + target + ', element' + element)
+ deferred.resolve()
+ })
+ $content.scrollTop(scrollHeight)
+ return deferred.promise()
+ }
+
+ $.when(testElementIsActiveAfterScroll('#a-1', '#div-1'))
+ .then(function () { return testElementIsActiveAfterScroll('#a-2', '#div-2') })
+ .then(function () { done() })
+ })
+
QUnit.test('should add the active class correctly when there are nested elements at 0 scroll offset', function (assert) {
assert.expect(6)
var times = 0
@@ -212,7 +286,7 @@ $(function () {
var navbarHtml = '<nav id="navigation" class="navbar">'
+ '<ul class="nav">'
+ '<li><a id="a-1" class="nav-link" href="#div-1">div 1</a>'
- + '<ul>'
+ + '<ul class="nav">'
+ '<li><a id="a-2" class="nav-link" href="#div-2">div 2</a></li>'
+ '</ul>'
+ '</li>'
@@ -246,6 +320,86 @@ $(function () {
testActiveElements()
})
+ QUnit.test('should add the active class correctly when there are nested elements (nav markup)', function (assert) {
+ assert.expect(6)
+ var times = 0
+ var done = assert.async()
+ var navbarHtml = '<nav id="navigation" class="navbar">'
+ + '<nav class="nav">'
+ + '<a id="a-1" class="nav-link" href="#div-1">div 1</a>'
+ + '<nav class="nav">'
+ + '<a id="a-2" class="nav-link" href="#div-2">div 2</a>'
+ + '</nav>'
+ + '</nav>'
+ + '</nav>'
+
+ var contentHtml = '<div class="content" style="position: absolute; top: 0px; overflow: auto; height: 50px">'
+ + '<div id="div-1" style="padding: 0; margin: 0">'
+ + '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>'
+ + '</div>'
+ + '</div>'
+
+ $(navbarHtml).appendTo('#qunit-fixture')
+
+ var $content = $(contentHtml)
+ .appendTo('#qunit-fixture')
+ .bootstrapScrollspy({ offset: 0, target: '#navigation' })
+
+ function testActiveElements() {
+ if (++times > 3) { return done() }
+
+ $content.one('scroll', function () {
+ assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
+ assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
+ testActiveElements()
+ })
+
+ $content.scrollTop($content.scrollTop() + 10)
+ }
+
+ testActiveElements()
+ })
+
+ QUnit.test('should add the active class correctly when there are nested elements (list-group markup)', function (assert) {
+ assert.expect(6)
+ var times = 0
+ var done = assert.async()
+ var navbarHtml = '<nav id="navigation" class="navbar">'
+ + '<div class="list-group">'
+ + '<a id="a-1" class="list-group-item" href="#div-1">div 1</a>'
+ + '<div class="list-group">'
+ + '<a id="a-2" class="list-group-item" href="#div-2">div 2</a>'
+ + '</div>'
+ + '</div>'
+ + '</nav>'
+
+ var contentHtml = '<div class="content" style="position: absolute; top: 0px; overflow: auto; height: 50px">'
+ + '<div id="div-1" style="padding: 0; margin: 0">'
+ + '<div id="div-2" style="height: 200px; padding: 0; margin: 0">div 2</div>'
+ + '</div>'
+ + '</div>'
+
+ $(navbarHtml).appendTo('#qunit-fixture')
+
+ var $content = $(contentHtml)
+ .appendTo('#qunit-fixture')
+ .bootstrapScrollspy({ offset: 0, target: '#navigation' })
+
+ function testActiveElements() {
+ if (++times > 3) { return done() }
+
+ $content.one('scroll', function () {
+ assert.ok($('#a-1').hasClass('active'), 'nav item for outer element has "active" class')
+ assert.ok($('#a-2').hasClass('active'), 'nav item for inner element has "active" class')
+ testActiveElements()
+ })
+
+ $content.scrollTop($content.scrollTop() + 10)
+ }
+
+ testActiveElements()
+ })
+
QUnit.test('should clear selection if above the first section', function (assert) {
assert.expect(3)
var done = assert.async()
diff --git a/js/tests/unit/tab.js b/js/tests/unit/tab.js
index 734648e9e..1e2b66c04 100644
--- a/js/tests/unit/tab.js
+++ b/js/tests/unit/tab.js
@@ -46,7 +46,7 @@ $(function () {
QUnit.test('should activate element by tab id', function (assert) {
assert.expect(2)
- var tabsHTML = '<ul class="tabs">'
+ var tabsHTML = '<ul class="nav">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
@@ -62,7 +62,7 @@ $(function () {
QUnit.test('should activate element by tab id', function (assert) {
assert.expect(2)
- var pillsHTML = '<ul class="pills">'
+ var pillsHTML = '<ul class="nav nav-pills">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
@@ -78,7 +78,7 @@ $(function () {
QUnit.test('should activate element by tab id in ordered list', function (assert) {
assert.expect(2)
- var pillsHTML = '<ol class="pills">'
+ var pillsHTML = '<ol class="nav nav-pills">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ol>'
@@ -108,11 +108,27 @@ $(function () {
assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
})
+ QUnit.test('should activate element by tab id in list group', function (assert) {
+ assert.expect(2)
+ var tabsHTML = '<div class="list-group">' +
+ '<a href="#home">Home</a>' +
+ '<a href="#profile">Profile</a>' +
+ '</div>'
+
+ $('<nav><div id="home"></div><div id="profile"></div></nav>').appendTo('#qunit-fixture')
+
+ $(tabsHTML).find('a:last').bootstrapTab('show')
+ assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'profile')
+
+ $(tabsHTML).find('a:first').bootstrapTab('show')
+ assert.strictEqual($('#qunit-fixture').find('.active').attr('id'), 'home')
+ })
+
QUnit.test('should not fire shown when show is prevented', function (assert) {
assert.expect(1)
var done = assert.async()
- $('<div class="tab"/>')
+ $('<div class="nav"/>')
.on('show.bs.tab', function (e) {
e.preventDefault()
assert.ok(true, 'show event fired')
@@ -166,7 +182,7 @@ $(function () {
assert.expect(2)
var done = assert.async()
- var dropHTML = '<ul class="drop">'
+ var dropHTML = '<ul class="drop nav">'
+ '<li class="dropdown"><a data-toggle="dropdown" href="#">1</a>'
+ '<ul class="dropdown-menu">'
+ '<li><a href="#1-1" data-toggle="tab">1-1</a></li>'
@@ -194,7 +210,7 @@ $(function () {
assert.expect(2)
var done = assert.async()
- var tabsHTML = '<ul class="tabs">'
+ var tabsHTML = '<ul class="nav">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
@@ -225,7 +241,7 @@ $(function () {
assert.expect(1)
var done = assert.async()
- var tabsHTML = '<ul class="tabs">'
+ var tabsHTML = '<ul class="nav">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
@@ -250,7 +266,7 @@ $(function () {
assert.expect(2)
var done = assert.async()
- var tabsHTML = '<ul class="tabs">'
+ var tabsHTML = '<ul class="nav">'
+ '<li><a href="#home">Home</a></li>'
+ '<li><a href="#profile">Profile</a></li>'
+ '</ul>'
diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js
index e1aec5551..8cb1a6fdf 100644
--- a/js/tests/unit/tooltip.js
+++ b/js/tests/unit/tooltip.js
@@ -869,4 +869,37 @@ $(function () {
})
.modal('show')
})
+
+ QUnit.test('should reset tip classes when hidden event triggered', function (assert) {
+ assert.expect(2)
+ var done = assert.async()
+ var $el = $('<a href="#" rel="tooltip" title="Test tooltip"/>')
+ .appendTo('#qunit-fixture')
+ .bootstrapTooltip('show')
+ .on('hidden.bs.tooltip', function () {
+ var tooltip = $el.data('bs.tooltip')
+ var $tooltip = $(tooltip.getTipElement())
+ assert.ok($tooltip.hasClass('tooltip'))
+ assert.ok($tooltip.hasClass('fade'))
+ done()
+ })
+
+ $el.bootstrapTooltip('hide')
+ })
+
+ QUnit.test('should convert number in title to string', function (assert) {
+ assert.expect(1)
+ var done = assert.async()
+ var $el = $('<a href="#" rel="tooltip" title="7"/>')
+ .appendTo('#qunit-fixture')
+ .bootstrapTooltip('show')
+ .on('shown.bs.tooltip', function () {
+ var tooltip = $el.data('bs.tooltip')
+ var $tooltip = $(tooltip.getTipElement())
+ assert.strictEqual($tooltip.children().text(), '7')
+ done()
+ })
+
+ $el.bootstrapTooltip('show')
+ })
})
diff --git a/js/tests/vendor/qunit.css b/js/tests/vendor/qunit.css
index 50fe5c290..90e1269f1 100644
--- a/js/tests/vendor/qunit.css
+++ b/js/tests/vendor/qunit.css
@@ -1,12 +1,12 @@
/*!
- * QUnit 2.1.1
+ * QUnit 2.2.0
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
- * Date: 2017-01-06T01:52Z
+ * Date: 2017-03-11T16:19Z
*/
/** Font Family and Sizes */
@@ -384,6 +384,7 @@
background-color: #EBECE9;
}
+#qunit-tests .qunit-todo-label,
#qunit-tests .qunit-skipped-label {
background-color: #F4FF77;
display: inline-block;
@@ -394,6 +395,10 @@
margin: -0.4em 0.4em -0.4em 0;
}
+#qunit-tests .qunit-todo-label {
+ background-color: #EEE;
+}
+
/** Result */
#qunit-testresult {
diff --git a/js/tests/vendor/qunit.js b/js/tests/vendor/qunit.js
index e90caadfc..b399f417c 100644
--- a/js/tests/vendor/qunit.js
+++ b/js/tests/vendor/qunit.js
@@ -1,12 +1,12 @@
/*!
- * QUnit 2.1.1
+ * QUnit 2.2.0
* https://qunitjs.com/
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
- * Date: 2017-01-06T01:52Z
+ * Date: 2017-03-11T16:19Z
*/
(function (global$1) {
'use strict';
@@ -20,7 +20,17 @@
var document = window && window.document;
var navigator = window && window.navigator;
- var sessionStorage = window && window.sessionStorage;
+
+ var sessionStorage = function () {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem(x, x);
+ sessionStorage.removeItem(x);
+ return sessionStorage;
+ } catch (e) {
+ return undefined;
+ }
+ }();
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
@@ -141,19 +151,16 @@
return result;
}
- // From jquery.js
+ /**
+ * Determines whether an element exists in a given array or not.
+ *
+ * @method inArray
+ * @param {Any} elem
+ * @param {Array} array
+ * @return {Boolean}
+ */
function inArray(elem, array) {
- if (array.indexOf) {
- return array.indexOf(elem);
- }
-
- for (var i = 0, length = array.length; i < length; i++) {
- if (array[i] === elem) {
- return i;
- }
- }
-
- return -1;
+ return array.indexOf(elem) !== -1;
}
/**
@@ -232,25 +239,25 @@
}
// Test for equality any JavaScript type.
- // Author: Philippe Rathé <[email protected]>
+ // Authors: Philippe Rathé <[email protected]>, David Chan <[email protected]>
var equiv = (function () {
- // Stack to decide between skip/abort functions
- var callers = [];
-
- // Stack to avoiding loops from circular referencing
- var parents = [];
- var parentsB = [];
+ // Value pairs queued for comparison. Used for breadth-first processing order, recursion
+ // detection and avoiding repeated comparison (see below for details).
+ // Elements are { a: val, b: val }.
+ var pairs = [];
var getProto = Object.getPrototypeOf || function (obj) {
return obj.__proto__;
};
- function useStrictEquality(b, a) {
+ function useStrictEquality(a, b) {
- // To catch short annotation VS 'new' annotation of a declaration. e.g.:
+ // This only gets called if a and b are not strict equal, and is used to compare on
+ // the primitive values inside object wrappers. For example:
// `var i = 1;`
// `var j = new Number(1);`
+ // Neither a nor b can be null, as a !== b and they have the same type.
if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") {
a = a.valueOf();
}
@@ -293,6 +300,31 @@
return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
}
+ function isContainer(val) {
+ return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
+ }
+
+ function breadthFirstCompareChild(a, b) {
+
+ // If a is a container not reference-equal to b, postpone the comparison to the
+ // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
+ // over the pair.
+ if (a === b) {
+ return true;
+ }
+ if (!isContainer(a)) {
+ return typeEquiv(a, b);
+ }
+ if (pairs.every(function (pair) {
+ return pair.a !== a || pair.b !== b;
+ })) {
+
+ // Not yet started comparing this pair
+ pairs.push({ a: a, b: b });
+ }
+ return true;
+ }
+
var callbacks = {
"string": useStrictEquality,
"boolean": useStrictEquality,
@@ -306,24 +338,20 @@
return true;
},
- "regexp": function regexp(b, a) {
+ "regexp": function regexp(a, b) {
return a.source === b.source &&
// Include flags in the comparison
getRegExpFlags(a) === getRegExpFlags(b);
},
- // - skip when the property is a method of an instance (OOP)
- // - abort otherwise,
- // initial === would have catch identical references anyway
- "function": function _function(b, a) {
-
- var caller = callers[callers.length - 1];
- return caller !== Object && typeof caller !== "undefined" && a.toString() === b.toString();
+ // abort (identical references / instance methods were skipped earlier)
+ "function": function _function() {
+ return false;
},
- "array": function array(b, a) {
- var i, j, len, loop, aCircular, bCircular;
+ "array": function array(a, b) {
+ var i, len;
len = a.length;
if (len !== b.length) {
@@ -332,50 +360,63 @@
return false;
}
- // Track reference to avoid circular references
- parents.push(a);
- parentsB.push(b);
for (i = 0; i < len; i++) {
- loop = false;
- for (j = 0; j < parents.length; j++) {
- aCircular = parents[j] === a[i];
- bCircular = parentsB[j] === b[i];
- if (aCircular || bCircular) {
- if (a[i] === b[i] || aCircular && bCircular) {
- loop = true;
- } else {
- parents.pop();
- parentsB.pop();
- return false;
- }
- }
- }
- if (!loop && !innerEquiv(a[i], b[i])) {
- parents.pop();
- parentsB.pop();
+
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
return false;
}
}
- parents.pop();
- parentsB.pop();
return true;
},
- "set": function set$$1(b, a) {
+ // Define sets a and b to be equivalent if for each element aVal in a, there
+ // is some element bVal in b such that aVal and bVal are equivalent. Element
+ // repetitions are not counted, so these are equivalent:
+ // a = new Set( [ {}, [], [] ] );
+ // b = new Set( [ {}, {}, [] ] );
+ "set": function set$$1(a, b) {
var innerEq,
outerEq = true;
if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) element to two equivalent sets can
+ // make them non-equivalent.
return false;
}
a.forEach(function (aVal) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Set is unused)
+ if (!outerEq) {
+ return;
+ }
+
innerEq = false;
b.forEach(function (bVal) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
if (innerEquiv(bVal, aVal)) {
innerEq = true;
}
+
+ // Replace the global pairs list
+ pairs = parentPairs;
});
if (!innerEq) {
@@ -386,21 +427,54 @@
return outerEq;
},
- "map": function map(b, a) {
+ // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
+ // in a, there is some key-value pair (bKey, bVal) in b such that
+ // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
+ // counted, so these are equivalent:
+ // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
+ // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
+ "map": function map(a, b) {
var innerEq,
outerEq = true;
if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) key-value pair to two equivalent maps
+ // can make them non-equivalent.
return false;
}
a.forEach(function (aVal, aKey) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Map is unused)
+ if (!outerEq) {
+ return;
+ }
+
innerEq = false;
b.forEach(function (bVal, bKey) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
if (innerEquiv([bVal, bKey], [aVal, aKey])) {
innerEq = true;
}
+
+ // Replace the global pairs list
+ pairs = parentPairs;
});
if (!innerEq) {
@@ -411,52 +485,31 @@
return outerEq;
},
- "object": function object(b, a) {
- var i, j, loop, aCircular, bCircular;
-
- // Default to true
- var eq = true;
- var aProperties = [];
- var bProperties = [];
+ "object": function object(a, b) {
+ var i,
+ aProperties = [],
+ bProperties = [];
if (compareConstructors(a, b) === false) {
return false;
}
- // Stack constructor before traversing properties
- callers.push(a.constructor);
-
- // Track reference to avoid circular references
- parents.push(a);
- parentsB.push(b);
-
// Be strict: don't ensure hasOwnProperty and go deep
for (i in a) {
- loop = false;
- for (j = 0; j < parents.length; j++) {
- aCircular = parents[j] === a[i];
- bCircular = parentsB[j] === b[i];
- if (aCircular || bCircular) {
- if (a[i] === b[i] || aCircular && bCircular) {
- loop = true;
- } else {
- eq = false;
- break;
- }
- }
- }
+
+ // Collect a's properties
aProperties.push(i);
- if (!loop && !innerEquiv(a[i], b[i])) {
- eq = false;
- break;
- }
- }
- parents.pop();
- parentsB.pop();
+ // Skip OOP methods that look the same
+ if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) {
+ continue;
+ }
- // Unstack, we are done
- callers.pop();
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
+ return false;
+ }
+ }
for (i in b) {
@@ -465,28 +518,52 @@
}
// Ensures identical properties name
- return eq && innerEquiv(aProperties.sort(), bProperties.sort());
+ return typeEquiv(aProperties.sort(), bProperties.sort());
}
};
function typeEquiv(a, b) {
var type = objectType(a);
- return objectType(b) === type && callbacks[type](b, a);
+
+ // Callbacks for containers will append to the pairs queue to achieve breadth-first
+ // search order. The pairs queue is also used to avoid reprocessing any pair of
+ // containers that are reference-equal to a previously visited pair (a special case
+ // this being recursion detection).
+ //
+ // Because of this approach, once typeEquiv returns a false value, it should not be
+ // called again without clearing the pair queue else it may wrongly report a visited
+ // pair as being equivalent.
+ return objectType(b) === type && callbacks[type](a, b);
}
- // The real equiv function
function innerEquiv(a, b) {
+ var i, pair;
// We're done when there's nothing more to compare
if (arguments.length < 2) {
return true;
}
- // Require type-specific equality
- return (a === b || typeEquiv(a, b)) && (
+ // Clear the global pair queue and add the top-level values being compared
+ pairs = [{ a: a, b: b }];
+
+ for (i = 0; i < pairs.length; i++) {
+ pair = pairs[i];
+
+ // Perform type-specific comparison on any pairs that are not strictly
+ // equal. For container types, that comparison will postpone comparison
+ // of any sub-container pair to the end of the pair queue. This gives
+ // breadth-first search order. It also avoids the reprocessing of
+ // reference-equal siblings, cousins etc, which can have a significant speed
+ // impact when comparing a container of small objects each of which has a
+ // reference to the same (singleton) large object.
+ if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) {
+ return false;
+ }
+ }
// ...across all consecutive argument pairs
- arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1)));
+ return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
}
return innerEquiv;
@@ -616,10 +693,10 @@
var res,
parser,
parserType,
- inStack = inArray(obj, stack);
+ objIndex = stack.indexOf(obj);
- if (inStack !== -1) {
- return "recursion(" + (inStack - stack.length) + ")";
+ if (objIndex !== -1) {
+ return "recursion(" + (objIndex - stack.length) + ")";
}
objType = objType || this.typeOf(obj);
@@ -749,7 +826,7 @@
nonEnumerableProperties = ["message", "name"];
for (i in nonEnumerableProperties) {
key = nonEnumerableProperties[i];
- if (key in map && inArray(key, keys) < 0) {
+ if (key in map && !inArray(key, keys)) {
keys.push(key);
}
}
@@ -843,6 +920,64 @@
return dump;
})();
+ var LISTENERS = Object.create(null);
+ var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
+
+ /**
+ * Emits an event with the specified data to all currently registered listeners.
+ * Callbacks will fire in the order in which they are registered (FIFO). This
+ * function is not exposed publicly; it is used by QUnit internals to emit
+ * logging events.
+ *
+ * @private
+ * @method emit
+ * @param {String} eventName
+ * @param {Object} data
+ * @return {Void}
+ */
+ function emit(eventName, data) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when emitting an event");
+ }
+
+ // Clone the callbacks in case one of them registers a new callback
+ var originalCallbacks = LISTENERS[eventName];
+ var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
+
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i](data);
+ }
+ }
+
+ /**
+ * Registers a callback as a listener to the specified event.
+ *
+ * @public
+ * @method on
+ * @param {String} eventName
+ * @param {Function} callback
+ * @return {Void}
+ */
+ function on(eventName, callback) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when registering a listener");
+ } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
+ var events = SUPPORTED_EVENTS.join(", ");
+ throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
+ } else if (objectType(callback) !== "function") {
+ throw new TypeError("callback must be a function when registering a listener");
+ }
+
+ if (!LISTENERS[eventName]) {
+ LISTENERS[eventName] = [];
+ }
+
+ // Don't register the same callback more than once
+ if (!inArray(callback, LISTENERS[eventName])) {
+ LISTENERS[eventName].push(callback);
+ }
+ }
+
// Register logging callbacks
function registerLoggingCallbacks(obj) {
var i,
@@ -929,6 +1064,95 @@
return extractStacktrace(error, offset);
}
+ var TestReport = function () {
+ function TestReport(name, suite, options) {
+ classCallCheck(this, TestReport);
+
+ this.name = name;
+ this.suiteName = suite.name;
+ this.fullName = suite.fullName.concat(name);
+ this.runtime = 0;
+ this.assertions = [];
+
+ this.skipped = !!options.skip;
+ this.todo = !!options.todo;
+
+ this._startTime = 0;
+ this._endTime = 0;
+
+ suite.pushTest(this);
+ }
+
+ createClass(TestReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ suiteName: this.suiteName,
+ fullName: this.fullName.slice()
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = Date.now();
+ }
+
+ return extend(this.start(), {
+ runtime: this.getRuntime(),
+ status: this.getStatus(),
+ errors: this.getFailedAssertions(),
+ assertions: this.getAssertions()
+ });
+ }
+ }, {
+ key: "pushAssertion",
+ value: function pushAssertion(assertion) {
+ this.assertions.push(assertion);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ if (this.skipped) {
+ return "skipped";
+ }
+
+ var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
+
+ if (!testPassed) {
+ return "failed";
+ } else if (this.todo) {
+ return "todo";
+ } else {
+ return "passed";
+ }
+ }
+ }, {
+ key: "getFailedAssertions",
+ value: function getFailedAssertions() {
+ return this.assertions.filter(function (assertion) {
+ return !assertion.passed;
+ });
+ }
+ }, {
+ key: "getAssertions",
+ value: function getAssertions() {
+ return this.assertions.slice();
+ }
+ }]);
+ return TestReport;
+ }();
+
var unitSampler;
var focused = false;
var priorityCount = 0;
@@ -945,6 +1169,12 @@
this.usedAsync = false;
this.module = config.currentModule;
this.stack = sourceFromStacktrace(3);
+ this.steps = [];
+
+ this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
+ todo: settings.todo,
+ skip: settings.skip
+ });
// Register unique strings
for (i = 0, l = this.module.tests; i < l.length; i++) {
@@ -995,6 +1225,7 @@
for (i = notStartedModules.length - 1; i >= 0; i--) {
startModule = notStartedModules[i];
startModule.stats = { all: 0, bad: 0, started: now() };
+ emit("suiteStart", startModule.suiteReport.start(true));
runLoggingCallbacks("moduleStart", {
name: startModule.name,
tests: startModule.tests
@@ -1012,6 +1243,7 @@
this.testEnvironment = extend({}, module.testEnvironment);
this.started = now();
+ emit("testStart", this.testReport.start(true));
runLoggingCallbacks("testStart", {
name: this.testName,
module: module.name,
@@ -1129,6 +1361,7 @@
moduleName = module.name,
testName = this.testName,
skipped = !!this.skip,
+ todo = !!this.todo,
bad = 0,
storage = config.storage;
@@ -1156,10 +1389,12 @@
}
}
+ emit("testEnd", this.testReport.end(true));
runLoggingCallbacks("testDone", {
name: testName,
module: moduleName,
skipped: skipped,
+ todo: todo,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length,
@@ -1174,6 +1409,7 @@
});
if (module.testsRun === numberOfTests(module)) {
+ emit("suiteEnd", module.suiteReport.end(true));
runLoggingCallbacks("moduleDone", {
name: module.name,
tests: module.tests,
@@ -1242,18 +1478,19 @@
expected: resultInfo.expected,
testId: this.testId,
negative: resultInfo.negative || false,
- runtime: now() - this.started
+ runtime: now() - this.started,
+ todo: !!this.todo
};
if (!resultInfo.result) {
- source = sourceFromStacktrace();
+ source = resultInfo.source || sourceFromStacktrace();
if (source) {
details.source = source;
}
}
- runLoggingCallbacks("log", details);
+ this.logAssertion(details);
this.assertions.push({
result: !!resultInfo.result,
@@ -1266,28 +1503,37 @@
throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
}
- var details = {
- module: this.module.name,
- name: this.testName,
+ this.assert.pushResult({
result: false,
message: message || "error",
actual: actual || null,
- testId: this.testId,
- runtime: now() - this.started
- };
-
- if (source) {
- details.source = source;
- }
+ expected: null,
+ source: source
+ });
+ },
+ /**
+ * Log assertion details using both the old QUnit.log interface and
+ * QUnit.on( "assertion" ) interface.
+ *
+ * @private
+ */
+ logAssertion: function logAssertion(details) {
runLoggingCallbacks("log", details);
- this.assertions.push({
- result: false,
- message: message
- });
+ var assertion = {
+ passed: details.result,
+ actual: details.actual,
+ expected: details.expected,
+ message: details.message,
+ stack: details.source,
+ todo: details.todo
+ };
+ this.testReport.pushAssertion(assertion);
+ emit("assertion", assertion);
},
+
resolvePromise: function resolvePromise(promise, phase) {
var then,
resume,
@@ -1331,7 +1577,7 @@
}
function moduleChainIdMatch(testModule) {
- return inArray(testModule.moduleId, config.moduleId) > -1 || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
+ return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
}
// Internally-generated tests are always valid
@@ -1344,7 +1590,7 @@
return false;
}
- if (config.testId && config.testId.length > 0 && inArray(this.testId, config.testId) < 0) {
+ if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
return false;
}
@@ -1511,9 +1757,7 @@
return;
}
- var newTest;
-
- newTest = new Test({
+ var newTest = new Test({
testName: testName,
callback: callback
});
@@ -1521,6 +1765,20 @@
newTest.queue();
}
+ function todo(testName, callback) {
+ if (focused) {
+ return;
+ }
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback,
+ todo: true
+ });
+
+ newTest.queue();
+ }
+
// Will be exposed as QUnit.skip
function skip(testName) {
if (focused) {
@@ -1537,8 +1795,6 @@
// Will be exposed as QUnit.only
function only(testName, callback) {
- var newTest;
-
if (focused) {
return;
}
@@ -1546,7 +1802,7 @@
config.queue.length = 0;
focused = true;
- newTest = new Test({
+ var newTest = new Test({
testName: testName,
callback: callback
});
@@ -1653,6 +1909,24 @@
}
}
+ /**
+ * Returns a function that proxies to the given method name on the globals
+ * console object. The proxy will also detect if the console doesn't exist and
+ * will appropriately no-op. This allows support for IE9, which doesn't have a
+ * console if the developer tools are not open.
+ */
+ function consoleProxy(method) {
+ return function () {
+ if (console) {
+ console[method].apply(console, arguments);
+ }
+ };
+ }
+
+ var Logger = {
+ warn: consoleProxy("warn")
+ };
+
var Assert = function () {
function Assert(testContext) {
classCallCheck(this, Assert);
@@ -1662,11 +1936,34 @@
// Assert helpers
- // Specify the number of expected assertions to guarantee that failed test
- // (no assertions are run at all) don't slip through.
+ // Documents a "step", which is a string value, in a test as a passing assertion
createClass(Assert, [{
+ key: "step",
+ value: function step(message) {
+ var result = !!message;
+
+ this.test.steps.push(message);
+
+ return this.pushResult({
+ result: result,
+ message: message || "You must provide a message to assert.step"
+ });
+ }
+
+ // Verifies the steps in a test match a given array of string values
+
+ }, {
+ key: "verifySteps",
+ value: function verifySteps(steps, message) {
+ this.deepEqual(this.test.steps, steps, message);
+ }
+
+ // Specify the number of expected assertions to guarantee that failed test
+ // (no assertions are run at all) don't slip through.
+
+ }, {
key: "expect",
value: function expect(asserts) {
if (arguments.length === 1) {
@@ -1714,7 +2011,7 @@
}, {
key: "push",
value: function push(result, actual, expected, message, negative) {
- console.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/).");
+ Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (http://api.qunitjs.com/pushResult/).");
var currentAssert = this instanceof Assert ? this : config.current.assert;
return currentAssert.pushResult({
@@ -2011,45 +2308,148 @@
}
}
- (function () {
- if (!defined.document) {
- return;
- }
+ var SuiteReport = function () {
+ function SuiteReport(name, parentSuite) {
+ classCallCheck(this, SuiteReport);
- // `onErrorFnPrev` initialized at top of scope
- // Preserve other handlers
- var onErrorFnPrev = window.onerror;
+ this.name = name;
+ this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
- // Cover uncaught exceptions
- // Returning true will suppress the default browser handler,
- // returning false will let it run.
- window.onerror = function (error, filePath, linerNr) {
- var ret = false;
- if (onErrorFnPrev) {
- ret = onErrorFnPrev(error, filePath, linerNr);
+ this.tests = [];
+ this.childSuites = [];
+
+ if (parentSuite) {
+ parentSuite.pushChildSuite(this);
}
+ }
- // Treat return value as window.onerror itself does,
- // Only do our handling if not suppressed.
- if (ret !== true) {
- if (config.current) {
- if (config.current.ignoreGlobalErrors) {
- return true;
+ createClass(SuiteReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.start();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.start();
+ }),
+ testCounts: {
+ total: this.getTestCounts().total
}
- pushFailure(error, filePath + ":" + linerNr);
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = Date.now();
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.end();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.end();
+ }),
+ testCounts: this.getTestCounts(),
+ runtime: this.getRuntime(),
+ status: this.getStatus()
+ };
+ }
+ }, {
+ key: "pushChildSuite",
+ value: function pushChildSuite(suite) {
+ this.childSuites.push(suite);
+ }
+ }, {
+ key: "pushTest",
+ value: function pushTest(test) {
+ this.tests.push(test);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getTestCounts",
+ value: function getTestCounts() {
+ var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
+
+ counts = this.tests.reduce(function (counts, test) {
+ counts[test.getStatus()]++;
+ counts.total++;
+ return counts;
+ }, counts);
+
+ return this.childSuites.reduce(function (counts, suite) {
+ return suite.getTestCounts(counts);
+ }, counts);
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ var _getTestCounts = this.getTestCounts(),
+ total = _getTestCounts.total,
+ failed = _getTestCounts.failed,
+ skipped = _getTestCounts.skipped,
+ todo = _getTestCounts.todo;
+
+ if (failed) {
+ return "failed";
} else {
- test("global failure", extend(function () {
- pushFailure(error, filePath + ":" + linerNr);
- }, { validTest: true }));
+ if (skipped === total) {
+ return "skipped";
+ } else if (todo === total) {
+ return "todo";
+ } else {
+ return "passed";
+ }
}
- return false;
}
+ }]);
+ return SuiteReport;
+ }();
- return ret;
- };
- })();
+ // Handle an unhandled exception. By convention, returns true if further
+ // error handling should be suppressed and false otherwise.
+ // In this case, we will only suppress further error handling if the
+ // "ignoreGlobalErrors" configuration option is enabled.
+ function onError(error) {
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ if (config.current) {
+ if (config.current.ignoreGlobalErrors) {
+ return true;
+ }
+ pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+ } else {
+ test("global failure", extend(function () {
+ pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+ }, { validTest: true }));
+ }
+
+ return false;
+ }
var QUnit = {};
+ var globalSuite = new SuiteReport();
+
+ // The initial "currentModule" represents the global (or top-level) module that
+ // is not explicitly defined by the user, therefore we add the "globalSuite" to
+ // it since each module has a suiteReport associated with it.
+ config.currentModule.suiteReport = globalSuite;
var globalStartCalled = false;
var runStarted = false;
@@ -2062,9 +2462,10 @@
QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
// Expose the current QUnit version
- QUnit.version = "2.1.1";
+ QUnit.version = "2.2.0";
extend(QUnit, {
+ on: on,
// Call on start of module test to prepend name to all tests
module: function module(name, testEnvironment, executeNow) {
@@ -2100,13 +2501,16 @@
function createModule() {
var parentModule = config.moduleStack.length ? config.moduleStack.slice(-1)[0] : null;
var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
+ var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
+
var module = {
name: moduleName,
parentModule: parentModule,
tests: [],
moduleId: generateHash(moduleName),
testsRun: 0,
- childModules: []
+ childModules: [],
+ suiteReport: new SuiteReport(name, parentSuite)
};
var env = {};
@@ -2130,6 +2534,8 @@
test: test,
+ todo: todo,
+
skip: skip,
only: only,
@@ -2146,14 +2552,18 @@
throw new Error("Called start() outside of a test context too many times");
} else if (config.autostart) {
throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
- } else if (!defined.document && !config.pageLoaded) {
-
- // Starts from Node even if .load was not previously called
- QUnit.load();
} else if (!config.pageLoaded) {
- // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+ // The page isn't completely loaded yet, so we set autostart and then
+ // load if we're in Node or wait for the browser's load event.
config.autostart = true;
+
+ // Starts from Node even if .load was not previously called. We still return
+ // early otherwise we'll wind up "beginning" twice.
+ if (!defined.document) {
+ QUnit.load();
+ }
+
return;
}
} else {
@@ -2195,7 +2605,9 @@
stack: function stack(offset) {
offset = (offset || 0) + 2;
return sourceFromStacktrace(offset);
- }
+ },
+
+ onError: onError
});
QUnit.pushFailure = pushFailure;
@@ -2244,6 +2656,7 @@
}
// The test run is officially beginning now
+ emit("runStart", globalSuite.start(true));
runLoggingCallbacks("begin", {
totalTests: Test.count,
modules: modulesLog
@@ -2292,6 +2705,7 @@
runtime = now() - config.started;
passed = config.stats.all - config.stats.bad;
+ emit("runEnd", globalSuite.end(true));
runLoggingCallbacks("done", {
failed: config.stats.bad,
passed: passed,
@@ -2456,6 +2870,13 @@
}
})();
+ var stats = {
+ passedTests: 0,
+ failedTests: 0,
+ skippedTests: 0,
+ todoTests: 0
+ };
+
// Escape text for attribute or text content.
function escapeText(s) {
if (!s) {
@@ -3046,7 +3467,8 @@
var banner = id("qunit-banner"),
tests = id("qunit-tests"),
abortButton = id("qunit-abort-tests-button"),
- html = ["Tests completed in ", details.runtime, " milliseconds.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", details.total, "</span> passed, <span class='failed'>", details.failed, "</span> failed."].join(""),
+ totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests,
+ html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", details.total, "</span> passed, <span class='failed'>", details.failed, "</span> failed."].join(""),
test,
assertLi,
assertList;
@@ -3069,7 +3491,7 @@
}
if (banner && (!abortButton || abortButton.disabled === false)) {
- banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+ banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
}
if (abortButton) {
@@ -3084,7 +3506,7 @@
// Show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset
- document$$1.title = [details.failed ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
+ document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
}
// Scroll back to top to show results
@@ -3224,7 +3646,10 @@
good = details.passed;
bad = details.failed;
- if (bad === 0) {
+ // This test passed if it has no unexpected failed assertions
+ var testPassed = details.failed > 0 ? details.todo : !details.todo;
+
+ if (testPassed) {
// Collapse the passing tests
addClass(assertList, "qunit-collapsed");
@@ -3248,6 +3673,8 @@
testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>";
if (details.skipped) {
+ stats.skippedTests++;
+
testItem.className = "skipped";
skipped = document$$1.createElement("em");
skipped.className = "qunit-skipped-label";
@@ -3258,12 +3685,27 @@
toggleClass(assertList, "qunit-collapsed");
});
- testItem.className = bad ? "fail" : "pass";
+ testItem.className = testPassed ? "pass" : "fail";
+
+ if (details.todo) {
+ var todoLabel = document$$1.createElement("em");
+ todoLabel.className = "qunit-todo-label";
+ todoLabel.innerHTML = "todo";
+ testItem.insertBefore(todoLabel, testTitle);
+ }
time = document$$1.createElement("span");
time.className = "runtime";
time.innerHTML = details.runtime + " ms";
testItem.insertBefore(time, assertList);
+
+ if (!testPassed) {
+ stats.failedTests++;
+ } else if (details.todo) {
+ stats.todoTests++;
+ } else {
+ stats.passedTests++;
+ }
}
// Show the source of the test when showing assertions
@@ -3271,7 +3713,7 @@
sourceName = document$$1.createElement("p");
sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
addClass(sourceName, "qunit-source");
- if (bad === 0) {
+ if (testPassed) {
addClass(sourceName, "qunit-collapsed");
}
addEvent(testTitle, "click", function () {
@@ -3292,6 +3734,39 @@
} else {
addEvent(window, "load", QUnit.load);
}
+
+ // Wrap window.onerror. We will call the original window.onerror to see if
+ // the existing handler fully handles the error; if not, we will call the
+ // QUnit.onError function.
+ var originalWindowOnError = window.onerror;
+
+ // Cover uncaught exceptions
+ // Returning true will suppress the default browser handler,
+ // returning false will let it run.
+ window.onerror = function (message, fileName, lineNumber) {
+ var ret = false;
+ if (originalWindowOnError) {
+ for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
+ args[_key - 3] = arguments[_key];
+ }
+
+ ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not suppressed.
+ if (ret !== true) {
+ var error = {
+ message: message,
+ fileName: fileName,
+ lineNumber: lineNumber
+ };
+
+ ret = QUnit.onError(error);
+ }
+
+ return ret;
+ };
})();
/*
diff --git a/js/tests/visual/carousel.html b/js/tests/visual/carousel.html
index b02d28a05..ad249d874 100644
--- a/js/tests/visual/carousel.html
+++ b/js/tests/visual/carousel.html
@@ -45,31 +45,11 @@
<script src="../../dist/carousel.js"></script>
<script>
- // Should throw an error because carousel is in transition
- function testCarouselTransitionError() {
- var err = false
- var $carousel = $('#carousel-example-generic')
- $carousel.on('slid.bs.carousel', function () {
- $carousel.off('slid.bs.carousel')
- if (!err) {
- alert('No error thrown for : testCarouselTransitionError')
- }
- })
- try {
- $carousel.carousel('next').carousel('prev')
- }
- catch (e) {
- err = true
- console.error(e.message)
- }
- }
-
- $(function () {
+ $(function() {
// Test to show that the carousel doesn't slide when the current tab isn't visible
- $('#carousel-example-generic').on('slid.bs.carousel', function (event) {
+ $('#carousel-example-generic').on('slid.bs.carousel', function(event) {
console.log('slid at ', event.timeStamp)
})
- testCarouselTransitionError()
})
</script>
</body>
diff --git a/js/tests/visual/collapse.html b/js/tests/visual/collapse.html
index 21b11a257..fe45c1804 100644
--- a/js/tests/visual/collapse.html
+++ b/js/tests/visual/collapse.html
@@ -14,13 +14,13 @@
<div class="card">
<div class="card-header" role="tab" id="headingOne">
<h5 class="mb-0">
- <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
+ <a data-toggle="collapse" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Collapsible Group Item #1
</a>
</h5>
</div>
- <div id="collapseOne" class="collapse show" role="tabpanel" aria-labelledby="headingOne">
+ <div id="collapseOne" class="collapse show" data-parent="#accordion" role="tabpanel" aria-labelledby="headingOne">
<div class="card-block">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</div>
@@ -29,12 +29,12 @@
<div class="card">
<div class="card-header" role="tab" id="headingTwo">
<h5 class="mb-0">
- <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
+ <a class="collapsed" data-toggle="collapse" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Collapsible Group Item #2
</a>
</h5>
</div>
- <div id="collapseTwo" class="collapse" role="tabpanel" aria-labelledby="headingTwo">
+ <div id="collapseTwo" class="collapse" data-parent="#accordion" role="tabpanel" aria-labelledby="headingTwo">
<div class="card-block">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</div>
@@ -43,12 +43,12 @@
<div class="card">
<div class="card-header" role="tab" id="headingThree">
<h5 class="mb-0">
- <a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
+ <a class="collapsed" data-toggle="collapse" href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
Collapsible Group Item #3
</a>
</h5>
</div>
- <div id="collapseThree" class="collapse" role="tabpanel" aria-labelledby="headingThree">
+ <div id="collapseThree" class="collapse" data-parent="#accordion" role="tabpanel" aria-labelledby="headingThree">
<div class="card-block">
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</div>
@@ -60,30 +60,5 @@
<script src="../../../docs/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/collapse.js"></script>
- <script>
- // JavaScript Test
- $(function () {
- testCollapseTransitionError()
- });
-
- // Should throw an error because carousel is in transition
- function testCollapseTransitionError() {
- var err = false
- $('#collapseOne').on('hidden.bs.collapse', function (e) {
- $(this).off('hidden.bs.collapse')
- if (!err) {
- alert('No error thrown for : testCollapseTransitionError')
- }
- })
-
- try {
- $('#collapseOne').collapse('hide').collapse('show')
- }
- catch (e) {
- err = true
- console.error(e.message)
- }
- }
- </script>
</body>
</html>
diff --git a/js/tests/visual/dropdown.html b/js/tests/visual/dropdown.html
index 6888cdb15..cc8a6e709 100644
--- a/js/tests/visual/dropdown.html
+++ b/js/tests/visual/dropdown.html
@@ -10,13 +10,13 @@
<div class="container">
<h1>Dropdown <small>Bootstrap Visual Test</small></h1>
- <nav class="navbar navbar-toggleable-md navbar-light bg-faded">
- <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
+ <nav class="navbar navbar-expand-md navbar-light bg-faded">
+ <a class="navbar-brand" href="#">Navbar</a>
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
- <a class="navbar-brand" href="#">Navbar</a>
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
diff --git a/js/tests/visual/modal.html b/js/tests/visual/modal.html
index 3aca0c96f..e999514f7 100644
--- a/js/tests/visual/modal.html
+++ b/js/tests/visual/modal.html
@@ -15,7 +15,7 @@
<body>
<nav class="navbar navbar-full navbar-dark bg-inverse">
<button class="navbar-toggler hidden-lg-up" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"></button>
- <div class="collapse navbar-toggleable-md" id="navbarResponsive">
+ <div class="collapse navbar-expand-md" id="navbarResponsive">
<a class="navbar-brand" href="#">This shouldn't jump!</a>
<ul class="nav navbar-nav">
<li class="nav-item active">
@@ -187,26 +187,6 @@
}
}
- // Should throw an error because modal is in transition
- function testModalTransitionError() {
- var err = false
- // Close #myModal
- $('#myModal').on('shown.bs.modal', function () {
- $('#myModal').modal('hide').off('shown.bs.modal')
- if (!err) {
- alert('No error thrown for : testModalTransitionError')
- }
- })
-
- try {
- $('#myModal').modal('show').modal('hide')
- }
- catch (e) {
- err = true
- console.error(e.message)
- }
- }
-
$(function () {
$('[data-toggle="popover"]').popover()
$('[data-toggle="tooltip"]').tooltip()
@@ -219,7 +199,6 @@
$('#firefoxModal').on('focus', reportFirefoxTestResult.bind(false))
$('#ff-bug-input').on('focus', reportFirefoxTestResult.bind(true))
})
- testModalTransitionError()
})
</script>
</body>
diff --git a/js/tests/visual/scrollspy.html b/js/tests/visual/scrollspy.html
index 15b7f75f8..c0c3dc749 100644
--- a/js/tests/visual/scrollspy.html
+++ b/js/tests/visual/scrollspy.html
@@ -10,10 +10,13 @@
</style>
</head>
<body data-spy="scroll" data-target=".navbar" data-offset="70">
- <div class="container">
- <nav class="navbar fixed-top navbar-dark bg-inverse">
- <a class="navbar-brand" href="#">Scrollspy test</a>
- <ul class="nav navbar-nav">
+ <nav class="navbar navbar-expand-md navbar-inverse bg-inverse fixed-top">
+ <a class="navbar-brand" href="#">Scrollspy test</a>
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+ <div class="navbar-collapse collapse" id="navbarSupportedContent">
+ <ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="#fat">@fat</a>
</li>
@@ -23,18 +26,18 @@
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu" aria-labelledby="dropdown">
- <a class="dropdown-item" href="#one">one</a>
- <a class="dropdown-item" href="#two">two</a>
- <div class="dropdown-divider"></div>
- <a class="dropdown-item" href="#three">three</a>
+ <a class="dropdown-item" href="#one">One</a>
+ <a class="dropdown-item" href="#two">Two</a>
+ <a class="dropdown-item" href="#three">Three</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="#final">Final</a>
</li>
</ul>
- </nav>
-
+ </div>
+ </nav>
+ <div class="container">
<h2 id="fat">@fat</h2>
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
diff --git a/js/tests/visual/tab.html b/js/tests/visual/tab.html
index c676cc7be..e747302db 100644
--- a/js/tests/visual/tab.html
+++ b/js/tests/visual/tab.html
@@ -71,7 +71,7 @@
</ul>
<div class="tab-content" role="tablist">
- <div class="tab-pane fade in active" id="home2" role="tabpanel">
+ <div class="tab-pane fade show active" id="home2" role="tabpanel">
<p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>
<p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>
</div>
@@ -178,7 +178,7 @@
</nav>
<div class="tab-content" role="tabpanel">
- <div role="tabpanel" class="tab-pane fade active" id="home5">
+ <div role="tabpanel" class="tab-pane fade show active" id="home5">
<p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>
<p>Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.</p>
</div>
@@ -195,8 +195,35 @@
<p>Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.</p>
</div>
</div>
- </div>
+ <h4>Tabs with list-group (with fade)</h4>
+ <div class="row">
+ <div class="col-4">
+ <div class="list-group" id="list-tab" role="tablist">
+ <a class="list-group-item list-group-item-action active" id="list-home-list" data-toggle="tab" href="#list-home" role="tab" aria-controls="home">Home</a>
+ <a class="list-group-item list-group-item-action" id="list-profile-list" data-toggle="tab" href="#list-profile" role="tab" aria-controls="profile">Profile</a>
+ <a class="list-group-item list-group-item-action" id="list-messages-list" data-toggle="tab" href="#list-messages" role="tab" aria-controls="messages">Messages</a>
+ <a class="list-group-item list-group-item-action" id="list-settings-list" data-toggle="tab" href="#list-settings" role="tab" aria-controls="settings">Settings</a>
+ </div>
+ </div>
+ <div class="col-8">
+ <div class="tab-content" id="nav-tabContent">
+ <div class="tab-pane fade show active" id="list-home" role="tabpanel" aria-labelledby="list-home-list">
+ <p>Velit aute mollit ipsum ad dolor consectetur nulla officia culpa adipisicing exercitation fugiat tempor. Voluptate deserunt sit sunt nisi aliqua fugiat proident ea ut. Mollit voluptate reprehenderit occaecat nisi ad non minim tempor sunt voluptate consectetur exercitation id ut nulla. Ea et fugiat aliquip nostrud sunt incididunt consectetur culpa aliquip eiusmod dolor. Anim ad Lorem aliqua in cupidatat nisi enim eu nostrud do aliquip veniam minim.</p>
+ </div>
+ <div class="tab-pane fade" id="list-profile" role="tabpanel" aria-labelledby="list-profile-list">
+ <p>Cupidatat quis ad sint excepteur laborum in esse qui. Et excepteur consectetur ex nisi eu do cillum ad laborum. Mollit et eu officia dolore sunt Lorem culpa qui commodo velit ex amet id ex. Officia anim incididunt laboris deserunt anim aute dolor incididunt veniam aute dolore do exercitation. Dolor nisi culpa ex ad irure in elit eu dolore. Ad laboris ipsum reprehenderit irure non commodo enim culpa commodo veniam incididunt veniam ad.</p>
+ </div>
+ <div class="tab-pane fade" id="list-messages" role="tabpanel" aria-labelledby="list-messages-list">
+ <p>Ut ut do pariatur aliquip aliqua aliquip exercitation do nostrud commodo reprehenderit aute ipsum voluptate. Irure Lorem et laboris nostrud amet cupidatat cupidatat anim do ut velit mollit consequat enim tempor. Consectetur est minim nostrud nostrud consectetur irure labore voluptate irure. Ipsum id Lorem sit sint voluptate est pariatur eu ad cupidatat et deserunt culpa sit eiusmod deserunt. Consectetur et fugiat anim do eiusmod aliquip nulla laborum elit adipisicing pariatur cillum.</p>
+ </div>
+ <div class="tab-pane fade" id="list-settings" role="tabpanel" aria-labelledby="list-settings-list">
+ <p>Irure enim occaecat labore sit qui aliquip reprehenderit amet velit. Deserunt ullamco ex elit nostrud ut dolore nisi officia magna sit occaecat laboris sunt dolor. Nisi eu minim cillum occaecat aute est cupidatat aliqua labore aute occaecat ea aliquip sunt amet. Aute mollit dolor ut exercitation irure commodo non amet consectetur quis amet culpa. Quis ullamco nisi amet qui aute irure eu. Magna labore dolor quis ex labore id nostrud deserunt dolor eiusmod eu pariatur culpa mollit in irure.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
<script src="../../../docs/assets/js/vendor/jquery-slim.min.js"></script>
<script src="../../dist/util.js"></script>
<script src="../../dist/tab.js"></script>
diff --git a/js/tests/visual/tooltip.html b/js/tests/visual/tooltip.html
index ada6d8b79..f447a533b 100644
--- a/js/tests/visual/tooltip.html
+++ b/js/tests/visual/tooltip.html
@@ -41,26 +41,7 @@
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
- testTooltipTransitionError()
})
-
- // Should throw an error because tooltip is in transition
- function testTooltipTransitionError() {
- var err = false
- $('#btnOne').on('shown.bs.tooltip', function () {
- $('#btnOne').tooltip('hide').off('shown.bs.tooltip')
- if (!err) {
- alert('No error thrown for : testTooltipTransitionError')
- }
- })
- try {
- $('#btnOne').tooltip('show').tooltip('hide')
- }
- catch (e) {
- err = true
- console.error(e.message)
- }
- }
</script>
</body>
</html>