diff options
| author | Chris Rebert <[email protected]> | 2015-01-21 13:00:12 -0800 |
|---|---|---|
| committer | Chris Rebert <[email protected]> | 2015-01-21 13:00:12 -0800 |
| commit | 9238337cbbad3bc45fa036f5f1ed63dec86d2968 (patch) | |
| tree | a1fa418cb91eb698d97db9ed0745f86488383f99 /js | |
| parent | 9e0c4ff786217d585f427de5c7a3f87ac86f3648 (diff) | |
| parent | 1ce502dc4de0accf27a6c37c21d26f4739d09241 (diff) | |
| download | bootstrap-9238337cbbad3bc45fa036f5f1ed63dec86d2968.tar.xz bootstrap-9238337cbbad3bc45fa036f5f1ed63dec86d2968.zip | |
Merge branch 'master' into v4
Diffstat (limited to 'js')
| -rw-r--r-- | js/tests/unit/affix.js | 12 | ||||
| -rw-r--r-- | js/tests/unit/alert.js | 6 | ||||
| -rw-r--r-- | js/tests/unit/button.js | 26 | ||||
| -rw-r--r-- | js/tests/unit/carousel.js | 54 | ||||
| -rw-r--r-- | js/tests/unit/collapse.js | 84 | ||||
| -rw-r--r-- | js/tests/unit/dropdown.js | 18 | ||||
| -rw-r--r-- | js/tests/unit/modal.js | 93 | ||||
| -rw-r--r-- | js/tests/unit/popover.js | 6 | ||||
| -rw-r--r-- | js/tests/unit/scrollspy.js | 24 | ||||
| -rw-r--r-- | js/tests/unit/tab.js | 30 | ||||
| -rw-r--r-- | js/tests/unit/tooltip.js | 108 | ||||
| -rw-r--r-- | js/tests/vendor/qunit.css | 51 | ||||
| -rw-r--r-- | js/tests/vendor/qunit.js | 1470 |
13 files changed, 1205 insertions, 777 deletions
diff --git a/js/tests/unit/affix.js b/js/tests/unit/affix.js index 3152d8d2e..040fe9803 100644 --- a/js/tests/unit/affix.js +++ b/js/tests/unit/affix.js @@ -35,8 +35,8 @@ $(function () { ok(!$affix.hasClass('affix'), 'affix class was not added') }) - test('should trigger affixed event after affix', function () { - stop() + test('should trigger affixed event after affix', function (assert) { + var done = assert.async() var templateHTML = '<div id="affixTarget">' + '<ul>' @@ -57,7 +57,7 @@ $(function () { }).on('affixed.bs.affix', function () { ok(true, 'affixed event fired') $('#affixTarget, #affixAfter').remove() - start() + done() }) setTimeout(function () { @@ -69,8 +69,8 @@ $(function () { }, 0) }) - test('should affix-top when scrolling up to offset when parent has padding', function () { - stop() + test('should affix-top when scrolling up to offset when parent has padding', function (assert) { + var done = assert.async() var templateHTML = '<div id="padding-offset" style="padding-top: 20px;">' + '<div id="affixTopTarget">' @@ -87,7 +87,7 @@ $(function () { .on('affixed-top.bs.affix', function () { ok($('#affixTopTarget').hasClass('affix-top'), 'affix-top class applied') $('#padding-offset').remove() - start() + done() }) setTimeout(function () { diff --git a/js/tests/unit/alert.js b/js/tests/unit/alert.js index bc4eed676..140a14838 100644 --- a/js/tests/unit/alert.js +++ b/js/tests/unit/alert.js @@ -55,13 +55,13 @@ $(function () { equal($('#qunit-fixture').find('.alert').length, 0, 'element removed from dom') }) - test('should not fire closed when close is prevented', function () { - stop() + test('should not fire closed when close is prevented', function (assert) { + var done = assert.async() $('<div class="alert"/>') .on('close.bs.alert', function (e) { e.preventDefault() ok(true, 'close event fired') - start() + done() }) .on('closed.bs.alert', function () { ok(false, 'closed event fired') diff --git a/js/tests/unit/button.js b/js/tests/unit/button.js index 320996483..cb51d40b9 100644 --- a/js/tests/unit/button.js +++ b/js/tests/unit/button.js @@ -29,57 +29,57 @@ $(function () { strictEqual($button[0], $el[0], 'collection contains element') }) - test('should return set state to loading', function () { + test('should return set state to loading', function (assert) { var $btn = $('<button class="btn" data-loading-text="fat">mdo</button>') equal($btn.html(), 'mdo', 'btn text equals mdo') $btn.bootstrapButton('loading') - stop() + var done = assert.async() setTimeout(function () { equal($btn.html(), 'fat', 'btn text equals fat') ok($btn[0].hasAttribute('disabled'), 'btn is disabled') ok($btn.hasClass('disabled'), 'btn has disabled class') - start() + done() }, 0) }) - test('should return reset state', function () { + test('should return reset state', function (assert) { var $btn = $('<button class="btn" data-loading-text="fat">mdo</button>') equal($btn.html(), 'mdo', 'btn text equals mdo') $btn.bootstrapButton('loading') - stop() + var doneOne = assert.async() setTimeout(function () { equal($btn.html(), 'fat', 'btn text equals fat') ok($btn[0].hasAttribute('disabled'), 'btn is disabled') ok($btn.hasClass('disabled'), 'btn has disabled class') - start() - stop() + doneOne() + var doneTwo = assert.async() $btn.bootstrapButton('reset') setTimeout(function () { equal($btn.html(), 'mdo', 'btn text equals mdo') ok(!$btn[0].hasAttribute('disabled'), 'btn is not disabled') ok(!$btn.hasClass('disabled'), 'btn does not have disabled class') - start() + doneTwo() }, 0) }, 0) }) - test('should work with an empty string as reset state', function () { + test('should work with an empty string as reset state', function (assert) { var $btn = $('<button class="btn" data-loading-text="fat"/>') equal($btn.html(), '', 'btn text equals ""') $btn.bootstrapButton('loading') - stop() + var doneOne = assert.async() setTimeout(function () { equal($btn.html(), 'fat', 'btn text equals fat') ok($btn[0].hasAttribute('disabled'), 'btn is disabled') ok($btn.hasClass('disabled'), 'btn has disabled class') - start() - stop() + doneOne() + var doneTwo = assert.async() $btn.bootstrapButton('reset') setTimeout(function () { equal($btn.html(), '', 'btn text equals ""') ok(!$btn[0].hasAttribute('disabled'), 'btn is not disabled') ok(!$btn.hasClass('disabled'), 'btn does not have disabled class') - start() + doneTwo() }, 0) }, 0) }) diff --git a/js/tests/unit/carousel.js b/js/tests/unit/carousel.js index 1598787f1..6da932d2c 100644 --- a/js/tests/unit/carousel.js +++ b/js/tests/unit/carousel.js @@ -29,13 +29,13 @@ $(function () { strictEqual($carousel[0], $el[0], 'collection contains element') }) - test('should not fire slid when slide is prevented', function () { - stop() + test('should not fire slid when slide is prevented', function (assert) { + var done = assert.async() $('<div class="carousel"/>') .on('slide.bs.carousel', function (e) { e.preventDefault() ok(true, 'slide event fired') - start() + done() }) .on('slid.bs.carousel', function () { ok(false, 'slid event fired') @@ -43,7 +43,7 @@ $(function () { .bootstrapCarousel('next') }) - test('should reset when slide is prevented', function () { + test('should reset when slide is prevented', function (assert) { var carouselHTML = '<div id="carousel-example-generic" class="carousel slide">' + '<ol class="carousel-indicators">' + '<li data-target="#carousel-example-generic" data-slide-to="0" class="active"/>' @@ -66,7 +66,7 @@ $(function () { + '</div>' var $carousel = $(carouselHTML) - stop() + var done = assert.async() $carousel .one('slide.bs.carousel', function (e) { e.preventDefault() @@ -82,13 +82,13 @@ $(function () { ok(!$carousel.find('.carousel-indicators li:eq(0)').is('.active'), 'first indicator still active') ok($carousel.find('.carousel-item:eq(1)').is('.active'), 'second item active') ok($carousel.find('.carousel-indicators li:eq(1)').is('.active'), 'second indicator active') - start() + done() }, 0) }) .bootstrapCarousel('next') }) - test('should fire slide event with direction', function () { + test('should fire slide event with direction', function (assert) { var carouselHTML = '<div id="myCarousel" class="carousel slide">' + '<div class="carousel-inner">' + '<div class="carousel-item active">' @@ -124,7 +124,7 @@ $(function () { + '</div>' var $carousel = $(carouselHTML) - stop() + var done = assert.async() $carousel .one('slide.bs.carousel', function (e) { @@ -135,14 +135,14 @@ $(function () { .one('slide.bs.carousel', function (e) { ok(e.direction, 'direction present on prev') strictEqual(e.direction, 'right', 'direction is right on prev') - start() + done() }) .bootstrapCarousel('prev') }) .bootstrapCarousel('next') }) - test('should fire slid event with direction', function () { + test('should fire slid event with direction', function (assert) { var carouselHTML = '<div id="myCarousel" class="carousel slide">' + '<div class="carousel-inner">' + '<div class="carousel-item active">' @@ -178,7 +178,7 @@ $(function () { + '</div>' var $carousel = $(carouselHTML) - stop() + var done = assert.async() $carousel .one('slid.bs.carousel', function (e) { @@ -189,14 +189,14 @@ $(function () { .one('slid.bs.carousel', function (e) { ok(e.direction, 'direction present on prev') strictEqual(e.direction, 'right', 'direction is right on prev') - start() + done() }) .bootstrapCarousel('prev') }) .bootstrapCarousel('next') }) - test('should fire slide event with relatedTarget', function () { + test('should fire slide event with relatedTarget', function (assert) { var template = '<div id="myCarousel" class="carousel slide">' + '<div class="carousel-inner">' + '<div class="carousel-item active">' @@ -231,18 +231,18 @@ $(function () { + '<a class="right carousel-control" href="#myCarousel" data-slide="next">›</a>' + '</div>' - stop() + var done = assert.async() $(template) .on('slide.bs.carousel', function (e) { ok(e.relatedTarget, 'relatedTarget present') ok($(e.relatedTarget).hasClass('carousel-item'), 'relatedTarget has class "carousel-item"') - start() + done() }) .bootstrapCarousel('next') }) - test('should fire slid event with relatedTarget', function () { + test('should fire slid event with relatedTarget', function (assert) { var template = '<div id="myCarousel" class="carousel slide">' + '<div class="carousel-inner">' + '<div class="carousel-item active">' @@ -277,13 +277,13 @@ $(function () { + '<a class="right carousel-control" href="#myCarousel" data-slide="next">›</a>' + '</div>' - stop() + var done = assert.async() $(template) .on('slid.bs.carousel', function (e) { ok(e.relatedTarget, 'relatedTarget present') ok($(e.relatedTarget).hasClass('carousel-item'), 'relatedTarget has class "carousel-item"') - start() + done() }) .bootstrapCarousel('next') }) @@ -542,7 +542,7 @@ $(function () { }) }) - test('should wrap around from end to start when wrap option is true', function () { + test('should wrap around from end to start when wrap option is true', function (assert) { var carouselHTML = '<div id="carousel-example-generic" class="carousel slide" data-wrap="true">' + '<ol class="carousel-indicators">' + '<li data-target="#carousel-example-generic" data-slide-to="0" class="active"/>' @@ -566,7 +566,7 @@ $(function () { var $carousel = $(carouselHTML) var getActiveId = function () { return $carousel.find('.carousel-item.active').attr('id') } - stop() + var done = assert.async() $carousel .one('slid.bs.carousel', function () { @@ -577,7 +577,7 @@ $(function () { $carousel .one('slid.bs.carousel', function () { strictEqual(getActiveId(), 'one', 'carousel wrapped around and slid from 3rd to 1st slide') - start() + done() }) .bootstrapCarousel('next') }) @@ -586,7 +586,7 @@ $(function () { .bootstrapCarousel('next') }) - test('should wrap around from start to end when wrap option is true', function () { + test('should wrap around from start to end when wrap option is true', function (assert) { var carouselHTML = '<div id="carousel-example-generic" class="carousel slide" data-wrap="true">' + '<ol class="carousel-indicators">' + '<li data-target="#carousel-example-generic" data-slide-to="0" class="active"/>' @@ -609,17 +609,17 @@ $(function () { + '</div>' var $carousel = $(carouselHTML) - stop() + var done = assert.async() $carousel .on('slid.bs.carousel', function () { strictEqual($carousel.find('.carousel-item.active').attr('id'), 'three', 'carousel wrapped around and slid from 1st to 3rd slide') - start() + done() }) .bootstrapCarousel('prev') }) - test('should stay at the end when the next method is called and wrap is false', function () { + test('should stay at the end when the next method is called and wrap is false', function (assert) { var carouselHTML = '<div id="carousel-example-generic" class="carousel slide" data-wrap="false">' + '<ol class="carousel-indicators">' + '<li data-target="#carousel-example-generic" data-slide-to="0" class="active"/>' @@ -643,7 +643,7 @@ $(function () { var $carousel = $(carouselHTML) var getActiveId = function () { return $carousel.find('.carousel-item.active').attr('id') } - stop() + var done = assert.async() $carousel .one('slid.bs.carousel', function () { @@ -657,7 +657,7 @@ $(function () { }) .bootstrapCarousel('next') strictEqual(getActiveId(), 'three', 'carousel did not wrap around and stayed on 3rd slide') - start() + done() }) .bootstrapCarousel('next') }) diff --git a/js/tests/unit/collapse.js b/js/tests/unit/collapse.js index 30d00e413..ad59d87db 100644 --- a/js/tests/unit/collapse.js +++ b/js/tests/unit/collapse.js @@ -43,14 +43,14 @@ $(function () { ok(/height/i.test($el.attr('style')), 'has height set') }) - test('should not fire shown when show is prevented', function () { - stop() + test('should not fire shown when show is prevented', function (assert) { + var done = assert.async() $('<div class="collapse"/>') .on('show.bs.collapse', function (e) { e.preventDefault() ok(true, 'show event fired') - start() + done() }) .on('shown.bs.collapse', function () { ok(false, 'shown event fired') @@ -58,8 +58,8 @@ $(function () { .bootstrapCollapse('show') }) - test('should reset style to auto after finishing opening collapse', function () { - stop() + test('should reset style to auto after finishing opening collapse', function (assert) { + var done = assert.async() $('<div class="collapse" style="height: 0px"/>') .on('show.bs.collapse', function () { @@ -67,13 +67,13 @@ $(function () { }) .on('shown.bs.collapse', function () { strictEqual(this.style.height, '', 'height is auto') - start() + done() }) .bootstrapCollapse('show') }) - test('should remove "collapsed" class from target when collapse is shown', function () { - stop() + test('should remove "collapsed" class from target when collapse is shown', function (assert) { + var done = assert.async() var $target = $('<a data-toggle="collapse" class="collapsed" href="#test1"/>').appendTo('#qunit-fixture') @@ -81,14 +81,14 @@ $(function () { .appendTo('#qunit-fixture') .on('shown.bs.collapse', function () { ok(!$target.hasClass('collapsed')) - start() + done() }) $target.click() }) - test('should add "collapsed" class to target when collapse is hidden', function () { - stop() + test('should add "collapsed" class to target when collapse is hidden', function (assert) { + var done = assert.async() var $target = $('<a data-toggle="collapse" href="#test1"/>').appendTo('#qunit-fixture') @@ -96,14 +96,14 @@ $(function () { .appendTo('#qunit-fixture') .on('hidden.bs.collapse', function () { ok($target.hasClass('collapsed')) - start() + done() }) $target.click() }) - test('should not close a collapse when initialized with "show" if already shown', function () { - stop() + test('should not close a collapse when initialized with "show" if already shown', function (assert) { + var done = assert.async() expect(0) @@ -115,11 +115,11 @@ $(function () { $test.bootstrapCollapse('show') - setTimeout(start, 0) + setTimeout(done, 0) }) - test('should open a collapse when initialized with "show" if not already shown', function () { - stop() + test('should open a collapse when initialized with "show" if not already shown', function (assert) { + var done = assert.async() expect(1) @@ -131,11 +131,11 @@ $(function () { $test.bootstrapCollapse('show') - setTimeout(start, 0) + setTimeout(done, 0) }) - test('should remove "collapsed" class from active accordion target', function () { - stop() + test('should remove "collapsed" class from active accordion target', function (assert) { + var done = assert.async() var accordionHTML = '<div class="panel-group" id="accordion">' + '<div class="panel"/>' @@ -161,14 +161,14 @@ $(function () { ok($target2.hasClass('collapsed'), 'inactive target 2 does have class "collapsed"') ok(!$target3.hasClass('collapsed'), 'active target 3 does not have class "collapsed"') - start() + done() }) $target3.click() }) - test('should allow dots in data-parent', function () { - stop() + test('should allow dots in data-parent', function (assert) { + var done = assert.async() var accordionHTML = '<div class="panel-group accordion">' + '<div class="panel"/>' @@ -194,14 +194,14 @@ $(function () { ok($target2.hasClass('collapsed'), 'inactive target 2 does have class "collapsed"') ok(!$target3.hasClass('collapsed'), 'active target 3 does not have class "collapsed"') - start() + done() }) $target3.click() }) - test('should set aria-expanded="true" on target when collapse is shown', function () { - stop() + test('should set aria-expanded="true" on target when collapse is shown', function (assert) { + var done = assert.async() var $target = $('<a data-toggle="collapse" class="collapsed" href="#test1" aria-expanded="false"/>').appendTo('#qunit-fixture') @@ -209,14 +209,14 @@ $(function () { .appendTo('#qunit-fixture') .on('shown.bs.collapse', function () { equal($target.attr('aria-expanded'), 'true', 'aria-expanded on target is "true"') - start() + done() }) $target.click() }) - test('should set aria-expanded="false" on target when collapse is hidden', function () { - stop() + test('should set aria-expanded="false" on target when collapse is hidden', function (assert) { + var done = assert.async() var $target = $('<a data-toggle="collapse" href="#test1" aria-expanded="true"/>').appendTo('#qunit-fixture') @@ -224,14 +224,14 @@ $(function () { .appendTo('#qunit-fixture') .on('hidden.bs.collapse', function () { equal($target.attr('aria-expanded'), 'false', 'aria-expanded on target is "false"') - start() + done() }) $target.click() }) - test('should change aria-expanded from active accordion target to "false" and set the newly active one to "true"', function () { - stop() + test('should change aria-expanded from active accordion target to "false" and set the newly active one to "true"', function (assert) { + var done = assert.async() var accordionHTML = '<div class="panel-group" id="accordion">' + '<div class="panel"/>' @@ -257,14 +257,14 @@ $(function () { equal($target2.attr('aria-expanded'), 'false', 'inactive target 2 has aria-expanded="false"') equal($target3.attr('aria-expanded'), 'true', 'active target 3 has aria-expanded="false"') - start() + done() }) $target3.click() }) - test('should not fire show event if show is prevented because other element is still transitioning', function () { - stop() + test('should not fire show event if show is prevented because other element is still transitioning', function (assert) { + var done = assert.async() var accordionHTML = '<div id="accordion">' + '<div class="panel"/>' @@ -294,12 +294,12 @@ $(function () { setTimeout(function () { ok(!showFired, 'show event didn\'t fire') - start() + done() }, 1) }) - test('should add "collapsed" class to target when collapse is hidden via manual invocation', function () { - stop() + test('should add "collapsed" class to target when collapse is hidden via manual invocation', function (assert) { + var done = assert.async() var $target = $('<a data-toggle="collapse" href="#test1"/>').appendTo('#qunit-fixture') @@ -307,13 +307,13 @@ $(function () { .appendTo('#qunit-fixture') .on('hidden.bs.collapse', function () { ok($target.hasClass('collapsed')) - start() + done() }) .bootstrapCollapse('hide') }) - test('should remove "collapsed" class from target when collapse is shown via manual invocation', function () { - stop() + test('should remove "collapsed" class from target when collapse is shown via manual invocation', function (assert) { + var done = assert.async() var $target = $('<a data-toggle="collapse" class="collapsed" href="#test1"/>').appendTo('#qunit-fixture') @@ -321,7 +321,7 @@ $(function () { .appendTo('#qunit-fixture') .on('shown.bs.collapse', function () { ok(!$target.hasClass('collapsed')) - start() + done() }) .bootstrapCollapse('show') }) diff --git a/js/tests/unit/dropdown.js b/js/tests/unit/dropdown.js index 3cdf637ee..9a7fbcff2 100644 --- a/js/tests/unit/dropdown.js +++ b/js/tests/unit/dropdown.js @@ -157,7 +157,7 @@ $(function () { strictEqual($('#qunit-fixture .open').length, 0, '"open" class removed') }) - test('should fire show and hide event', function () { + test('should fire show and hide event', function (assert) { var dropdownHTML = '<ul class="tabs">' + '<li class="dropdown">' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' @@ -174,7 +174,7 @@ $(function () { .find('[data-toggle="dropdown"]') .bootstrapDropdown() - stop() + var done = assert.async() $dropdown .parent('.dropdown') @@ -183,7 +183,7 @@ $(function () { }) .on('hide.bs.dropdown', function () { ok(true, 'hide was fired') - start() + done() }) $dropdown.click() @@ -191,7 +191,7 @@ $(function () { }) - test('should fire shown and hidden event', function () { + test('should fire shown and hidden event', function (assert) { var dropdownHTML = '<ul class="tabs">' + '<li class="dropdown">' + '<a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown</a>' @@ -208,7 +208,7 @@ $(function () { .find('[data-toggle="dropdown"]') .bootstrapDropdown() - stop() + var done = assert.async() $dropdown .parent('.dropdown') @@ -217,15 +217,15 @@ $(function () { }) .on('hidden.bs.dropdown', function () { ok(true, 'hidden was fired') - start() + done() }) $dropdown.click() $(document.body).click() }) - test('should ignore keyboard events within <input>s and <textarea>s', function () { - stop() + test('should ignore keyboard events within <input>s and <textarea>s', function (assert) { + var done = assert.async() var dropdownHTML = '<ul class="tabs">' + '<li class="dropdown">' @@ -259,7 +259,7 @@ $(function () { $textarea.focus().trigger($.Event('keydown', { which: 38 })) ok($(document.activeElement).is($textarea), 'textarea still focused') - start() + done() }) $dropdown.click() diff --git a/js/tests/unit/modal.js b/js/tests/unit/modal.js index efd478b20..c1af15a2f 100644 --- a/js/tests/unit/modal.js +++ b/js/tests/unit/modal.js @@ -33,36 +33,36 @@ $(function () { ok($.fn.bootstrapModal.Constructor.DEFAULTS, 'default object exposed') }) - test('should insert into dom when show method is called', function () { - stop() + test('should insert into dom when show method is called', function (assert) { + var done = assert.async() $('<div id="modal-test"/>') .on('shown.bs.modal', function () { notEqual($('#modal-test').length, 0, 'modal inserted into dom') - start() + done() }) .bootstrapModal('show') }) - test('should fire show event', function () { - stop() + test('should fire show event', function (assert) { + var done = assert.async() $('<div id="modal-test"/>') .on('show.bs.modal', function () { ok(true, 'show event fired') - start() + done() }) .bootstrapModal('show') }) - test('should not fire shown when show was prevented', function () { - stop() + test('should not fire shown when show was prevented', function (assert) { + var done = assert.async() $('<div id="modal-test"/>') .on('show.bs.modal', function (e) { e.preventDefault() ok(true, 'show event fired') - start() + done() }) .on('shown.bs.modal', function () { ok(false, 'shown event fired') @@ -70,8 +70,8 @@ $(function () { .bootstrapModal('show') }) - test('should hide modal when hide is called', function () { - stop() + test('should hide modal when hide is called', function (assert) { + var done = assert.async() $('<div id="modal-test"/>') .on('shown.bs.modal', function () { @@ -81,13 +81,13 @@ $(function () { }) .on('hidden.bs.modal', function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') - start() + done() }) .bootstrapModal('show') }) - test('should toggle when toggle is called', function () { - stop() + test('should toggle when toggle is called', function (assert) { + var done = assert.async() $('<div id="modal-test"/>') .on('shown.bs.modal', function () { @@ -97,13 +97,13 @@ $(function () { }) .on('hidden.bs.modal', function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') - start() + done() }) .bootstrapModal('toggle') }) - test('should remove from dom when click [data-dismiss="modal"]', function () { - stop() + test('should remove from dom when click [data-dismiss="modal"]', function (assert) { + var done = assert.async() $('<div id="modal-test"><span class="close" data-dismiss="modal"/></div>') .on('shown.bs.modal', function () { @@ -113,13 +113,13 @@ $(function () { }) .on('hidden.bs.modal', function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') - start() + done() }) .bootstrapModal('toggle') }) - test('should allow modal close with "backdrop:false"', function () { - stop() + test('should allow modal close with "backdrop:false"', function (assert) { + var done = assert.async() $('<div id="modal-test" data-backdrop="false"/>') .on('shown.bs.modal', function () { @@ -128,13 +128,13 @@ $(function () { }) .on('hidden.bs.modal', function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') - start() + done() }) .bootstrapModal('show') }) - test('should close modal when clicking outside of modal-content', function () { - stop() + test('should close modal when clicking outside of modal-content', function (assert) { + var done = assert.async() $('<div id="modal-test"><div class="contents"/></div>') .on('shown.bs.modal', function () { @@ -145,13 +145,13 @@ $(function () { }) .on('hidden.bs.modal', function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') - start() + done() }) .bootstrapModal('show') }) - test('should close modal when escape key is pressed via keydown', function () { - stop() + test('should close modal when escape key is pressed via keydown', function (assert) { + var done = assert.async() var div = $('<div id="modal-test"/>') div @@ -163,14 +163,14 @@ $(function () { setTimeout(function () { ok(!$('#modal-test').is(':visible'), 'modal hidden') div.remove() - start() + done() }, 0) }) .bootstrapModal('show') }) - test('should not close modal when escape key is pressed via keyup', function () { - stop() + test('should not close modal when escape key is pressed via keyup', function (assert) { + var done = assert.async() var div = $('<div id="modal-test"/>') div @@ -182,14 +182,14 @@ $(function () { setTimeout(function () { ok($('#modal-test').is(':visible'), 'modal still visible') div.remove() - start() + done() }, 0) }) .bootstrapModal('show') }) - test('should trigger hide event once when clicking outside of modal-content', function () { - stop() + test('should trigger hide event once when clicking outside of modal-content', function (assert) { + var done = assert.async() var triggered @@ -201,31 +201,36 @@ $(function () { .on('hide.bs.modal', function () { triggered += 1 strictEqual(triggered, 1, 'modal hide triggered once') - start() + done() }) .bootstrapModal('show') }) - test('should close reopened modal with [data-dismiss="modal"] click', function () { - stop() + test('should close reopened modal with [data-dismiss="modal"] click', function (assert) { + var done = assert.async() $('<div id="modal-test"><div class="contents"><div id="close" data-dismiss="modal"/></div></div>') - .on('shown.bs.modal', function () { + .one('shown.bs.modal', function () { $('#close').click() - ok(!$('#modal-test').is(':visible'), 'modal hidden') }) .one('hidden.bs.modal', function () { + // after one open-close cycle + ok(!$('#modal-test').is(':visible'), 'modal hidden') $(this) + .one('shown.bs.modal', function () { + $('#close').click() + }) .one('hidden.bs.modal', function () { - start() + ok(!$('#modal-test').is(':visible'), 'modal hidden') + done() }) .bootstrapModal('show') }) .bootstrapModal('show') }) - test('should restore focus to toggling element when modal is hidden after having been opened via data-api', function () { - stop() + test('should restore focus to toggling element when modal is hidden after having been opened via data-api', function (assert) { + var done = assert.async() var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture') @@ -233,7 +238,7 @@ $(function () { .on('hidden.bs.modal', function () { setTimeout(function () { ok($(document.activeElement).is($toggleBtn), 'toggling element is once again focused') - start() + done() }, 0) }) .on('shown.bs.modal', function () { @@ -244,8 +249,8 @@ $(function () { $toggleBtn.click() }) - test('should not restore focus to toggling element if the associated show event gets prevented', function () { - stop() + test('should not restore focus to toggling element if the associated show event gets prevented', function (assert) { + var done = assert.async() var $toggleBtn = $('<button data-toggle="modal" data-target="#modal-test"/>').appendTo('#qunit-fixture') var $otherBtn = $('<button id="other-btn"/>').appendTo('#qunit-fixture') @@ -260,7 +265,7 @@ $(function () { .on('hidden.bs.modal', function () { setTimeout(function () { ok($(document.activeElement).is($otherBtn), 'focus returned to toggling element') - start() + done() }, 0) }) .on('shown.bs.modal', function () { diff --git a/js/tests/unit/popover.js b/js/tests/unit/popover.js index 729ba46b7..6eff24927 100644 --- a/js/tests/unit/popover.js +++ b/js/tests/unit/popover.js @@ -188,7 +188,7 @@ $(function () { equal($('.popover').length, 0, 'popover was removed') }) - test('should detach popover content rather than removing it so that event handlers are left intact', function () { + test('should detach popover content rather than removing it so that event handlers are left intact', function (assert) { var $content = $('<div class="content-with-handler"><a class="btn btn-warning">Button with event handler</a></div>').appendTo('#qunit-fixture') var handlerCalled = false @@ -207,7 +207,7 @@ $(function () { } }) - stop() + var done = assert.async() $div .one('shown.bs.popover', function () { $div @@ -217,7 +217,7 @@ $(function () { $('.content-with-handler .btn').click() $div.bootstrapPopover('destroy') ok(handlerCalled, 'content\'s event handler still present') - start() + done() }) .bootstrapPopover('show') }) diff --git a/js/tests/unit/scrollspy.js b/js/tests/unit/scrollspy.js index 0c9081491..028da4bd9 100644 --- a/js/tests/unit/scrollspy.js +++ b/js/tests/unit/scrollspy.js @@ -29,8 +29,8 @@ $(function () { strictEqual($scrollspy[0], $el[0], 'collection contains element') }) - test('should only switch "active" class on current target', function () { - stop() + test('should only switch "active" class on current target', function (assert) { + var done = assert.async() var sectionHTML = '<div id="root" class="active">' + '<div class="topbar">' @@ -67,14 +67,14 @@ $(function () { $scrollspy.on('scroll.bs.scrollspy', function () { ok($section.hasClass('active'), '"active" class still on root node') - start() + done() }) $scrollspy.scrollTop(350) }) - test('should correctly select middle navigation option when large offset is used', function () { - stop() + test('should correctly select middle navigation option when large offset is used', function (assert) { + var done = assert.async() var sectionHTML = '<div id="header" style="height: 500px;"></div>' + '<nav id="navigation" class="navbar">' @@ -100,13 +100,13 @@ $(function () { ok(!$section.find('#one-link').parent().hasClass('active'), '"active" class removed from first section') ok($section.find('#two-link').parent().hasClass('active'), '"active" class on middle section') ok(!$section.find('#three-link').parent().hasClass('active'), '"active" class not on last section') - start() + done() }) $scrollspy.scrollTop(550) }) - test('should add the active class to the correct element', function () { + test('should add the active class to the correct element', function (assert) { var navbarHtml = '<nav class="navbar">' + '<ul class="nav">' @@ -128,10 +128,10 @@ $(function () { var testElementIsActiveAfterScroll = function (element, target) { var deferred = $.Deferred() var scrollHeight = Math.ceil($content.scrollTop() + $(target).position().top) - stop() + var done = assert.async() $content.one('scroll', function () { ok($(element).hasClass('active'), 'target:' + target + ', element' + element) - start() + done() deferred.resolve() }) $content.scrollTop(scrollHeight) @@ -142,8 +142,8 @@ $(function () { .then(function () { return testElementIsActiveAfterScroll('#li-2', '#div-2') }) }) - test('should clear selection if above the first section', function () { - stop() + test('should clear selection if above the first section', function (assert) { + var done = assert.async() var sectionHTML = '<div id="header" style="height: 500px;"></div>' + '<nav id="navigation" class="navbar">' @@ -176,7 +176,7 @@ $(function () { $scrollspy .one('scroll.bs.scrollspy', function () { strictEqual($('.active').length, 0, 'selection cleared') - start() + done() }) .scrollTop(0) }) diff --git a/js/tests/unit/tab.js b/js/tests/unit/tab.js index 9b2a18d57..1228ae3fa 100644 --- a/js/tests/unit/tab.js +++ b/js/tests/unit/tab.js @@ -59,14 +59,14 @@ $(function () { equal($('#qunit-fixture').find('.active').attr('id'), 'home') }) - test('should not fire shown when show is prevented', function () { - stop() + test('should not fire shown when show is prevented', function (assert) { + var done = assert.async() $('<div class="tab"/>') .on('show.bs.tab', function (e) { e.preventDefault() ok(true, 'show event fired') - start() + done() }) .on('shown.bs.tab', function () { ok(false, 'shown event fired') @@ -74,8 +74,8 @@ $(function () { .bootstrapTab('show') }) - test('show and shown events should reference correct relatedTarget', function () { - stop() + test('show and shown events should reference correct relatedTarget', function (assert) { + var done = assert.async() var dropHTML = '<ul class="drop">' + '<li class="dropdown"><a data-toggle="dropdown" href="#">1</a>' @@ -93,16 +93,16 @@ $(function () { .find('ul > li:last a') .on('show.bs.tab', function (e) { equal(e.relatedTarget.hash, '#1-1', 'references correct element as relatedTarget') - start() }) .on('shown.bs.tab', function (e) { equal(e.relatedTarget.hash, '#1-1', 'references correct element as relatedTarget') + done() }) .bootstrapTab('show') }) - test('should fire hide and hidden events', function () { - stop() + test('should fire hide and hidden events', function (assert) { + var done = assert.async() var tabsHTML = '<ul class="tabs">' + '<li><a href="#home">Home</a></li>' @@ -123,7 +123,7 @@ $(function () { .find('li:first a') .on('hidden.bs.tab', function () { ok(true, 'hidden event fired') - start() + done() }) .bootstrapTab('show') .end() @@ -131,8 +131,8 @@ $(function () { .bootstrapTab('show') }) - test('should not fire hidden when hide is prevented', function () { - stop() + test('should not fire hidden when hide is prevented', function (assert) { + var done = assert.async() var tabsHTML = '<ul class="tabs">' + '<li><a href="#home">Home</a></li>' @@ -144,7 +144,7 @@ $(function () { .on('hide.bs.tab', function (e) { e.preventDefault() ok(true, 'hide event fired') - start() + done() }) .on('hidden.bs.tab', function () { ok(false, 'hidden event fired') @@ -155,8 +155,8 @@ $(function () { .bootstrapTab('show') }) - test('hide and hidden events contain correct relatedTarget', function () { - stop() + test('hide and hidden events contain correct relatedTarget', function (assert) { + var done = assert.async() var tabsHTML = '<ul class="tabs">' + '<li><a href="#home">Home</a></li>' @@ -170,7 +170,7 @@ $(function () { }) .on('hidden.bs.tab', function (e) { equal(e.relatedTarget.hash, '#profile', 'references correct element as relatedTarget') - start() + done() }) .bootstrapTab('show') .end() diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js index 8bde0f533..20efff8cc 100644 --- a/js/tests/unit/tooltip.js +++ b/js/tests/unit/tooltip.js @@ -115,37 +115,37 @@ $(function () { equal($('.tooltip').length, 0, 'tooltip removed') }) - test('should fire show event', function () { - stop() + test('should fire show event', function (assert) { + var done = assert.async() $('<div title="tooltip title"/>') .on('show.bs.tooltip', function () { ok(true, 'show event fired') - start() + done() }) .bootstrapTooltip('show') }) - test('should fire shown event', function () { - stop() + test('should fire shown event', function (assert) { + var done = assert.async() $('<div title="tooltip title"></div>') .appendTo('#qunit-fixture') .on('shown.bs.tooltip', function () { ok(true, 'shown was called') - start() + done() }) .bootstrapTooltip('show') }) - test('should not fire shown event when show was prevented', function () { - stop() + test('should not fire shown event when show was prevented', function (assert) { + var done = assert.async() $('<div title="tooltip title"/>') .on('show.bs.tooltip', function (e) { e.preventDefault() ok(true, 'show event fired') - start() + done() }) .on('shown.bs.tooltip', function () { ok(false, 'shown event fired') @@ -153,8 +153,8 @@ $(function () { .bootstrapTooltip('show') }) - test('should fire hide event', function () { - stop() + test('should fire hide event', function (assert) { + var done = assert.async() $('<div title="tooltip title"/>') .appendTo('#qunit-fixture') @@ -163,13 +163,13 @@ $(function () { }) .on('hide.bs.tooltip', function () { ok(true, 'hide event fired') - start() + done() }) .bootstrapTooltip('show') }) - test('should fire hidden event', function () { - stop() + test('should fire hidden event', function (assert) { + var done = assert.async() $('<div title="tooltip title"/>') .appendTo('#qunit-fixture') @@ -178,13 +178,13 @@ $(function () { }) .on('hidden.bs.tooltip', function () { ok(true, 'hidden event fired') - start() + done() }) .bootstrapTooltip('show') }) - test('should not fire hidden event when hide was prevented', function () { - stop() + test('should not fire hidden event when hide was prevented', function (assert) { + var done = assert.async() $('<div title="tooltip title"/>') .appendTo('#qunit-fixture') @@ -194,7 +194,7 @@ $(function () { .on('hide.bs.tooltip', function (e) { e.preventDefault() ok(true, 'hide event fired') - start() + done() }) .on('hidden.bs.tooltip', function () { ok(false, 'hidden event fired') @@ -699,8 +699,8 @@ $(function () { ok(passed, '.tooltip(\'show\') should not throw an error if element no longer is in dom') }) - test('should place tooltip on top of element', function () { - stop() + test('should place tooltip on top of element', function (assert) { + var done = assert.async() var containerHTML = '<div>' + '<p style="margin-top: 200px">' @@ -732,12 +732,12 @@ $(function () { setTimeout(function () { ok(Math.round($tooltip.offset().top + $tooltip.outerHeight()) <= Math.round($trigger.offset().top)) - start() + done() }, 0) }) - test('should place tooltip inside viewport', function () { - stop() + test('should place tooltip inside viewport', function (assert) { + var done = assert.async() var $container = $('<div/>') .css({ @@ -763,12 +763,12 @@ $(function () { setTimeout(function () { ok($('.tooltip').offset().left >= 0) - start() + done() }, 0) }) - test('should show tooltip if leave event hasn\'t occurred before delay expires', function () { - stop() + test('should show tooltip if leave event hasn\'t occurred before delay expires', function (assert) { + var done = assert.async() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') .appendTo('#qunit-fixture') @@ -780,14 +780,14 @@ $(function () { setTimeout(function () { ok($('.tooltip').is('.fade.in'), '200ms: tooltip is faded in') - start() + done() }, 200) $tooltip.trigger('mouseenter') }) - test('should not show tooltip if leave event occurs before delay expires', function () { - stop() + test('should not show tooltip if leave event occurs before delay expires', function (assert) { + var done = assert.async() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') .appendTo('#qunit-fixture') @@ -800,14 +800,14 @@ $(function () { setTimeout(function () { ok(!$('.tooltip').is('.fade.in'), '200ms: tooltip not faded in') - start() + done() }, 200) $tooltip.trigger('mouseenter') }) - test('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', function () { - stop() + test('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', function (assert) { + var done = assert.async() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') .appendTo('#qunit-fixture') @@ -824,15 +824,15 @@ $(function () { setTimeout(function () { ok($('.tooltip').is('.fade.in'), '200ms: tooltip still faded in') - start() + done() }, 200) }, 0) $tooltip.trigger('mouseenter') }) - test('should not show tooltip if leave event occurs before delay expires', function () { - stop() + test('should not show tooltip if leave event occurs before delay expires', function (assert) { + var done = assert.async() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') .appendTo('#qunit-fixture') @@ -845,14 +845,14 @@ $(function () { setTimeout(function () { ok(!$('.tooltip').is('.fade.in'), '200ms: tooltip not faded in') - start() + done() }, 200) $tooltip.trigger('mouseenter') }) - test('should not show tooltip if leave event occurs before delay expires, even if hide delay is 0', function () { - stop() + test('should not show tooltip if leave event occurs before delay expires, even if hide delay is 0', function (assert) { + var done = assert.async() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') .appendTo('#qunit-fixture') @@ -865,14 +865,14 @@ $(function () { setTimeout(function () { ok(!$('.tooltip').is('.fade.in'), '250ms: tooltip not faded in') - start() + done() }, 250) $tooltip.trigger('mouseenter') }) - test('should wait 200ms before hiding the tooltip', function () { - stop() + test('should wait 200ms before hiding the tooltip', function (assert) { + var done = assert.async() var $tooltip = $('<a href="#" rel="tooltip" title="Another tooltip"/>') .appendTo('#qunit-fixture') @@ -889,7 +889,7 @@ $(function () { setTimeout(function () { ok(!$tooltip.data('bs.tooltip').$tip.is('.in'), '200ms: tooltip removed') - start() + done() }, 200) }, 0) @@ -897,14 +897,14 @@ $(function () { $tooltip.trigger('mouseenter') }) - test('should correctly position tooltips on SVG elements', function () { + test('should correctly position tooltips on SVG elements', function (assert) { if (!window.SVGElement) { // Skip IE8 since it doesn't support SVG expect(0) return } - stop() + var done = assert.async() var styles = '<style>' + '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }' @@ -928,15 +928,15 @@ $(function () { ok(Math.abs(offset.left - 88) <= 1, 'tooltip has correct horizontal location') $circle.bootstrapTooltip('hide') equal($('.tooltip').length, 0, 'tooltip removed from dom') - start() + done() }) .bootstrapTooltip({ container: 'body', placement: 'top', trigger: 'manual' }) $circle.bootstrapTooltip('show') }) - test('should correctly determine auto placement based on container rather than parent', function () { - stop() + test('should correctly determine auto placement based on container rather than parent', function (assert) { + var done = assert.async() var styles = '<style>' + '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }' @@ -965,7 +965,7 @@ $(function () { $styles.remove() $(this).remove() equal($('.tooltip').length, 0, 'tooltip removed from dom') - start() + done() }) .bootstrapTooltip({ container: 'body', @@ -1037,8 +1037,8 @@ $(function () { equal(currentUid, $('#tt-content').text()) }) - test('should position arrow correctly when tooltip is moved to not appear offscreen', function () { - stop() + test('should position arrow correctly when tooltip is moved to not appear offscreen', function (assert) { + var done = assert.async() var styles = '<style>' + '.tooltip, .tooltip *, .tooltip *:before, .tooltip *:after { box-sizing: border-box; }' @@ -1059,7 +1059,7 @@ $(function () { $styles.remove() $(this).remove() equal($('.tooltip').length, 0, 'tooltip removed from dom') - start() + done() }) .bootstrapTooltip({ container: 'body', @@ -1069,14 +1069,14 @@ $(function () { .bootstrapTooltip('show') }) - test('should correctly position tooltips on transformed elements', function () { + test('should correctly position tooltips on transformed elements', function (assert) { var styleProps = document.documentElement.style if (!('transform' in styleProps) && !('webkitTransform' in styleProps) && !('msTransform' in styleProps)) { expect(0) return } - stop() + var done = assert.async() var styles = '<style>' + '#qunit-fixture { top: 0; left: 0; }' @@ -1096,7 +1096,7 @@ $(function () { ok(Math.abs(offset.left - 88) <= 1, 'tooltip has correct horizontal location') ok(Math.abs(offset.top - 126) <= 1, 'tooltip has correct vertical location') $element.bootstrapTooltip('hide') - start() + done() }) .bootstrapTooltip({ container: 'body', diff --git a/js/tests/vendor/qunit.css b/js/tests/vendor/qunit.css index 9437b4b60..bfcdca411 100644 --- a/js/tests/vendor/qunit.css +++ b/js/tests/vendor/qunit.css @@ -1,12 +1,12 @@ /*! - * QUnit 1.15.0 + * QUnit 1.17.0 * http://qunitjs.com/ * - * Copyright 2014 jQuery Foundation and other contributors + * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2014-08-08T16:00Z + * Date: 2015-01-19T11:58Z */ /** Font Family and Sizes */ @@ -77,6 +77,18 @@ #qunit-modulefilter-container { float: right; + padding: 0.2em; +} + +.qunit-url-config { + display: inline-block; + padding: 0.1em; +} + +.qunit-filter { + display: block; + float: right; + margin-left: 1em; } /** Tests: Pass/Fail */ @@ -91,7 +103,19 @@ list-style-position: inside; } -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { +#qunit-tests > li { + display: none; +} + +#qunit-tests li.running, +#qunit-tests li.pass, +#qunit-tests li.fail, +#qunit-tests li.skipped { + display: list-item; +} + +#qunit-tests.hidepass li.running, +#qunit-tests.hidepass li.pass { display: none; } @@ -99,6 +123,10 @@ cursor: pointer; } +#qunit-tests li.skipped strong { + cursor: default; +} + #qunit-tests li a { padding: 0.5em; color: #C2CCD1; @@ -211,6 +239,21 @@ #qunit-banner.qunit-fail { background-color: #EE5757; } +/*** Skipped tests */ + +#qunit-tests .skipped { + background-color: #EBECE9; +} + +#qunit-tests .qunit-skipped-label { + background-color: #F4FF77; + display: inline-block; + font-style: normal; + color: #366097; + line-height: 1.8em; + padding: 0 0.5em; + margin: -0.4em 0.4em -0.4em 0; +} /** Result */ diff --git a/js/tests/vendor/qunit.js b/js/tests/vendor/qunit.js index 474cfe55f..46d404157 100644 --- a/js/tests/vendor/qunit.js +++ b/js/tests/vendor/qunit.js @@ -1,12 +1,12 @@ /*! - * QUnit 1.15.0 + * QUnit 1.17.0 * http://qunitjs.com/ * - * Copyright 2014 jQuery Foundation and other contributors + * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2014-08-08T16:00Z + * Date: 2015-01-19T11:58Z */ (function( window ) { @@ -14,6 +14,7 @@ var QUnit, config, onErrorFnPrev, + loggingCallbacks = {}, fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ), toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, @@ -22,11 +23,13 @@ var QUnit, now = Date.now || function() { return new Date().getTime(); }, + globalStartCalled = false, + runStarted = false, setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, defined = { - document: typeof window.document !== "undefined", - setTimeout: typeof window.setTimeout !== "undefined", + document: window.document !== undefined, + setTimeout: window.setTimeout !== undefined, sessionStorage: (function() { var x = "qunit-test-string"; try { @@ -86,132 +89,7 @@ var QUnit, return vals; }; -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[ name ] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - test = new Test({ - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - start: function( count ) { - var message; - - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; - } - - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - - // Set the starting time when the first test is run - QUnit.config.started = QUnit.config.started || now(); - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - - message = "Called start() while already started (QUnit.config.semaphore was 0 already)"; - - if ( config.current ) { - QUnit.pushFailure( message, sourceFromStacktrace( 2 ) ); - } else { - throw new Error( message ); - } - - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13 ); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// We use the prototype to distinguish between properties that should -// be exposed as globals (and in exports) and those that shouldn't -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); +QUnit = {}; /** * Config object: Maintain internal state @@ -225,10 +103,6 @@ config = { // block until document ready blocking: true, - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, @@ -246,23 +120,39 @@ config = { // when enabled, the id is set to `true` as a `QUnit.config` property urlConfig: [ { + id: "hidepassed", + label: "Hide passed tests", + tooltip: "Only show tests and assertions that fail. Stored as query-strings." + }, + { id: "noglobals", label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + tooltip: "Enabling this will test if any test introduces new properties on the " + + "`window` object. Stored as query-strings." }, { id: "notrycatch", label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + + "exceptions in IE reasonable. Stored as query-strings." } ], // Set of all modules. - modules: {}, + modules: [], + + // The first unnamed module + currentModule: { + name: "", + tests: [] + }, callbacks: {} }; +// Push a loose unnamed module to the modules collection +config.modules.push( config.currentModule ); + // Initialize more QUnit.config and QUnit.urlParams (function() { var i, current, @@ -286,22 +176,22 @@ config = { } } + if ( urlParams.filter === true ) { + delete urlParams.filter; + } + QUnit.urlParams = urlParams; // String search anywhere in moduleName+testName config.filter = urlParams.filter; - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = []; - if ( urlParams.testNumber ) { + config.testId = []; + if ( urlParams.testId ) { - // Ensure that urlParams.testNumber is an array - urlParams.testNumber = [].concat( urlParams.testNumber ); - for ( i = 0; i < urlParams.testNumber.length; i++ ) { - current = urlParams.testNumber[ i ]; - config.testNumber.push( parseInt( current, 10 ) ); + // Ensure that urlParams.testId is an array + urlParams.testId = [].concat( urlParams.testId ); + for ( i = 0; i < urlParams.testId.length; i++ ) { + config.testId.push( urlParams.testId[ i ] ); } } @@ -309,8 +199,130 @@ config = { QUnit.isLocal = location.protocol === "file:"; }()); +// Root QUnit object. +// `QUnit` initialized at top of scope extend( QUnit, { + // call on start of module test to prepend name to all tests + module: function( name, testEnvironment ) { + var currentModule = { + name: name, + testEnvironment: testEnvironment, + tests: [] + }; + + // DEPRECATED: handles setup/teardown functions, + // beforeEach and afterEach should be used instead + if ( testEnvironment && testEnvironment.setup ) { + testEnvironment.beforeEach = testEnvironment.setup; + delete testEnvironment.setup; + } + if ( testEnvironment && testEnvironment.teardown ) { + testEnvironment.afterEach = testEnvironment.teardown; + delete testEnvironment.teardown; + } + + config.modules.push( currentModule ); + config.currentModule = currentModule; + }, + + // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. + asyncTest: function( testName, expected, callback ) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test( testName, expected, callback, true ); + }, + + test: function( testName, expected, callback, async ) { + var test; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + test = new Test({ + testName: testName, + expected: expected, + async: async, + callback: callback + }); + + test.queue(); + }, + + skip: function( testName ) { + var test = new Test({ + testName: testName, + skip: true + }); + + test.queue(); + }, + + // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. + // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. + start: function( count ) { + var globalStartAlreadyCalled = globalStartCalled; + + if ( !config.current ) { + globalStartCalled = true; + + if ( runStarted ) { + throw new Error( "Called start() outside of a test context while already started" ); + } else if ( globalStartAlreadyCalled || count > 1 ) { + 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 ( !config.pageLoaded ) { + + // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it + config.autostart = true; + return; + } + } else { + + // If a test is running, adjust its semaphore + config.current.semaphore -= count || 1; + + // Don't start until equal number of stop-calls + if ( config.current.semaphore > 0 ) { + return; + } + + // throw an Error if start is called more often than stop + if ( config.current.semaphore < 0 ) { + config.current.semaphore = 0; + + QUnit.pushFailure( + "Called start() while already started (test's semaphore was 0 already)", + sourceFromStacktrace( 2 ) + ); + return; + } + } + + resumeProcessing(); + }, + + // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. + stop: function( count ) { + + // If there isn't a test running, don't allow QUnit.stop() to be called + if ( !config.current ) { + throw new Error( "Called stop() outside of a test context" ); + } + + // If a test is running, adjust its semaphore + config.current.semaphore += count || 1; + + pauseProcessing(); + }, + config: config, // Safe object type checking @@ -351,78 +363,65 @@ extend( QUnit, { return undefined; }, - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { + extend: extend, - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), + load: function() { + config.pageLoaded = true; - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), + // Initialize the configuration options + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: 0, + updateRate: 1000, + autostart: true, + filter: "" + }, true ); - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), + config.blocking = false; - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), + if ( config.autostart ) { + resumeProcessing(); + } + } +}); - // testDone: { name, failed, passed, total, runtime } - testDone: registerLoggingCallback( "testDone" ), +// Register logging callbacks +(function() { + var i, l, key, + callbacks = [ "begin", "done", "log", "testStart", "testDone", + "moduleStart", "moduleDone" ]; + + function registerLoggingCallback( key ) { + var loggingCallback = function( callback ) { + if ( QUnit.objectType( callback ) !== "function" ) { + throw new Error( + "QUnit logging methods require a callback function as their first parameters." + ); + } - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), + config.callbacks[ key ].push( callback ); + }; - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); + // DEPRECATED: This will be removed on QUnit 2.0.0+ + // Stores the registered functions allowing restoring + // at verifyLoggingCallbacks() if modified + loggingCallbacks[ key ] = loggingCallback; -QUnit.load = function() { - runLoggingCallbacks( "begin", { - totalTests: Test.count - }); + return loggingCallback; + } - // Initialize the configuration options - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: 0, - updateRate: 1000, - autostart: true, - filter: "", - semaphore: 1 - }, true ); + for ( i = 0, l = callbacks.length; i < l; i++ ) { + key = callbacks[ i ]; - config.blocking = false; + // Initialize key collection of logging callback + if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { + config.callbacks[ key ] = []; + } - if ( config.autostart ) { - QUnit.start(); + QUnit[ key ] = registerLoggingCallback( key ); } -}; +})(); // `onErrorFnPrev` initialized at top of scope // Preserve other handlers @@ -448,7 +447,7 @@ window.onerror = function( error, filePath, linerNr ) { } else { QUnit.test( "global failure", extend(function() { QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); + }, { validTest: true } ) ); } return false; } @@ -457,21 +456,25 @@ window.onerror = function( error, filePath, linerNr ) { }; function done() { + var runtime, passed; + config.autorun = true; // Log the last module results if ( config.previousModule ) { runLoggingCallbacks( "moduleDone", { - name: config.previousModule, + name: config.previousModule.name, + tests: config.previousModule.tests, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started }); } delete config.previousModule; - var runtime = now() - config.started, - passed = config.stats.all - config.stats.bad; + runtime = now() - config.started; + passed = config.stats.all - config.stats.bad; runLoggingCallbacks( "done", { failed: config.stats.bad, @@ -481,47 +484,6 @@ function done() { }); } -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = ( test.module + ": " + test.testName ).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber.length > 0 ) { - if ( inArray( test.testNumber, config.testNumber ) < 0 ) { - return false; - } - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - // Doesn't support IE6 to IE9 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { @@ -565,15 +527,27 @@ function extractStacktrace( e, offset ) { return e.sourceURL + ":" + e.line; } } + function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); + var e = new Error(); + if ( !e.stack ) { + try { + throw e; + } catch ( err ) { + // This should already be true in most browsers + e = err; + } } + return extractStacktrace( e, offset ); } function synchronize( callback, last ) { + if ( QUnit.objectType( callback ) === "array" ) { + while ( callback.length ) { + synchronize( callback.shift() ); + } + return; + } config.queue.push( callback ); if ( config.autorun && !config.blocking ) { @@ -586,10 +560,16 @@ function process( last ) { process( last ); } var start = now(); - config.depth = config.depth ? config.depth + 1 : 1; + config.depth = ( config.depth || 0 ) + 1; while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) { + if ( !defined.setTimeout || config.updateRate <= 0 || + ( ( now() - start ) < config.updateRate ) ) { + if ( config.current ) { + + // Reset async tracking for each phase of the Test lifecycle + config.current.usedAsync = false; + } config.queue.shift()(); } else { setTimeout( next, 13 ); @@ -602,6 +582,79 @@ function process( last ) { } } +function begin() { + var i, l, + modulesLog = []; + + // If the test run hasn't officially begun yet + if ( !config.started ) { + + // Record the time of the test run's beginning + config.started = now(); + + verifyLoggingCallbacks(); + + // Delete the loose unnamed module if unused. + if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { + config.modules.shift(); + } + + // Avoid unnecessary information by not logging modules' test environments + for ( i = 0, l = config.modules.length; i < l; i++ ) { + modulesLog.push({ + name: config.modules[ i ].name, + tests: config.modules[ i ].tests + }); + } + + // The test run is officially beginning now + runLoggingCallbacks( "begin", { + totalTests: Test.count, + modules: modulesLog + }); + } + + config.blocking = false; + process( true ); +} + +function resumeProcessing() { + runStarted = true; + + // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) + if ( defined.setTimeout ) { + setTimeout(function() { + if ( config.current && config.current.semaphore > 0 ) { + return; + } + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + begin(); + }, 13 ); + } else { + begin(); + } +} + +function pauseProcessing() { + config.blocking = true; + + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout(function() { + if ( config.current ) { + config.current.semaphore = 0; + QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); + } else { + throw new Error( "Test timed out" ); + } + resumeProcessing(); + }, config.testTimeout ); + } +} + function saveGlobal() { config.pollution = []; @@ -671,18 +724,6 @@ function extend( a, b, undefOnly ) { return a; } -function registerLoggingCallback( key ) { - - // Initialize key collection of logging callback - if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { - config.callbacks[ key ] = []; - } - - return function( callback ) { - config.callbacks[ key ].push( callback ); - }; -} - function runLoggingCallbacks( key, args ) { var i, l, callbacks; @@ -692,6 +733,34 @@ function runLoggingCallbacks( key, args ) { } } +// DEPRECATED: This will be removed on 2.0.0+ +// This function verifies if the loggingCallbacks were modified by the user +// If so, it will restore it, assign the given callback and print a console warning +function verifyLoggingCallbacks() { + var loggingCallback, userCallback; + + for ( loggingCallback in loggingCallbacks ) { + if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { + + userCallback = QUnit[ loggingCallback ]; + + // Restore the callback function + QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; + + // Assign the deprecated given callback + QUnit[ loggingCallback ]( userCallback ); + + if ( window.console && window.console.warn ) { + window.console.warn( + "QUnit." + loggingCallback + " was replaced with a new value.\n" + + "Please, check out the documentation on how to apply logging callbacks.\n" + + "Reference: http://api.qunitjs.com/category/callbacks/" + ); + } + } + } +} + // from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { @@ -708,16 +777,46 @@ function inArray( elem, array ) { } function Test( settings ) { + var i, l; + + ++Test.count; + extend( this, settings ); - this.assert = new Assert( this ); this.assertions = []; - this.testNumber = ++Test.count; + this.semaphore = 0; + this.usedAsync = false; + this.module = config.currentModule; + this.stack = sourceFromStacktrace( 3 ); + + // Register unique strings + for ( i = 0, l = this.module.tests; i < l.length; i++ ) { + if ( this.module.tests[ i ].name === this.testName ) { + this.testName += " "; + } + } + + this.testId = generateHash( this.module.name, this.testName ); + + this.module.tests.push({ + name: this.testName, + testId: this.testId + }); + + if ( settings.skip ) { + + // Skipped tests will fully ignore any sent callback + this.callback = function() {}; + this.async = false; + this.expected = 0; + } else { + this.assert = new Assert( this ); + } } Test.count = 0; Test.prototype = { - setup: function() { + before: function() { if ( // Emit moduleStart when we're switching from one module to another @@ -731,47 +830,43 @@ Test.prototype = { ) { if ( hasOwn.call( config, "previousModule" ) ) { runLoggingCallbacks( "moduleDone", { - name: config.previousModule, + name: config.previousModule.name, + tests: config.previousModule.tests, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all + total: config.moduleStats.all, + runtime: now() - config.moduleStats.started }); } config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; + config.moduleStats = { all: 0, bad: 0, started: now() }; runLoggingCallbacks( "moduleStart", { - name: this.module + name: this.module.name, + tests: this.module.tests }); } config.current = this; - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); + this.testEnvironment = extend( {}, this.module.testEnvironment ); + delete this.testEnvironment.beforeEach; + delete this.testEnvironment.afterEach; this.started = now(); runLoggingCallbacks( "testStart", { name: this.testName, - module: this.module, - testNumber: this.testNumber + module: this.module.name, + testId: this.testId }); if ( !config.pollution ) { saveGlobal(); } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, this.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, this.assert ); - } catch ( e ) { - this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - } }, + run: function() { + var promise; + config.current = this; if ( this.async ) { @@ -781,18 +876,17 @@ Test.prototype = { this.callbackStarted = now(); if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, this.assert ); - this.callbackRuntime = now() - this.callbackStarted; + promise = this.callback.call( this.testEnvironment, this.assert ); + this.resolvePromise( promise ); return; } try { - this.callback.call( this.testEnvironment, this.assert ); - this.callbackRuntime = now() - this.callbackStarted; + promise = this.callback.call( this.testEnvironment, this.assert ); + this.resolvePromise( promise ); } catch ( e ) { - this.callbackRuntime = now() - this.callbackStarted; - - this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); + this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -803,31 +897,59 @@ Test.prototype = { } } }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = now() - this.callbackStarted; + + after: function() { + checkPollution(); + }, + + queueHook: function( hook, hookName ) { + var promise, + test = this; + return function runHook() { + config.current = test; + if ( config.notrycatch ) { + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + return; } - this.testEnvironment.teardown.call( this.testEnvironment, this.assert ); - return; - } else { try { - this.testEnvironment.teardown.call( this.testEnvironment, this.assert ); - } catch ( e ) { - this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); + promise = hook.call( test.testEnvironment, test.assert ); + test.resolvePromise( promise, hookName ); + } catch ( error ) { + test.pushFailure( hookName + " failed on " + test.testName + ": " + + ( error.message || error ), extractStacktrace( error, 0 ) ); } + }; + }, + + // Currently only used for module level hooks, can be used to add global level ones + hooks: function( handler ) { + var hooks = []; + + // Hooks are ignored on skipped tests + if ( this.skip ) { + return hooks; } - checkPollution(); + + if ( this.module.testEnvironment && + QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) { + hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) ); + } + + return hooks; }, + finish: function() { config.current = this; if ( config.requireExpects && this.expected === null ) { - this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + this.pushFailure( "Expected number of assertions to be defined, but expect() was " + + "not called.", this.stack ); } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + this.pushFailure( "Expected " + this.expected + " assertions, but " + + this.assertions.length + " were run", this.stack ); } else if ( this.expected === null && !this.assertions.length ) { - this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); + this.pushFailure( "Expected at least one assertion, but none were run - call " + + "expect(0) to accept zero assertions.", this.stack ); } var i, @@ -847,7 +969,8 @@ Test.prototype = { runLoggingCallbacks( "testDone", { name: this.testName, - module: this.module, + module: this.module.name, + skipped: !!this.skip, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length, @@ -855,12 +978,17 @@ Test.prototype = { // HTML Reporter use assertions: this.assertions, - testNumber: this.testNumber, + testId: this.testId, // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime }); + // QUnit.reset() is deprecated and will be replaced for a new + // fixture reset function on QUnit 2.0/2.1. + // It's still called here for backwards compatibility handling + QUnit.reset(); + config.current = undefined; }, @@ -868,26 +996,39 @@ Test.prototype = { var bad, test = this; + if ( !this.valid() ) { + return; + } + function run() { + // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); + synchronize([ + function() { + test.before(); + }, + + test.hooks( "beforeEach" ), + + function() { + test.run(); + }, + + test.hooks( "afterEach" ).reverse(), + + function() { + test.after(); + }, + function() { + test.finish(); + } + ]); } // `bad` initialized at top of scope // defer when previous test run passed, if storage is available bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); if ( bad ) { run(); @@ -899,13 +1040,14 @@ Test.prototype = { push: function( result, actual, expected, message ) { var source, details = { - module: this.module, + module: this.module.name, name: this.testName, result: result, message: message, actual: actual, expected: expected, - testNumber: this.testNumber + testId: this.testId, + runtime: now() - this.started }; if ( !result ) { @@ -926,16 +1068,18 @@ Test.prototype = { pushFailure: function( message, source, actual ) { if ( !this instanceof Test ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) ); + throw new Error( "pushFailure() assertion outside test context, was " + + sourceFromStacktrace( 2 ) ); } var details = { - module: this.module, + module: this.module.name, name: this.testName, result: false, message: message || "error", actual: actual || null, - testNumber: this.testNumber + testId: this.testId, + runtime: now() - this.started }; if ( source ) { @@ -948,20 +1092,132 @@ Test.prototype = { result: false, message: message }); + }, + + resolvePromise: function( promise, phase ) { + var then, message, + test = this; + if ( promise != null ) { + then = promise.then; + if ( QUnit.objectType( then ) === "function" ) { + QUnit.stop(); + then.call( + promise, + QUnit.start, + function( error ) { + message = "Promise rejected " + + ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + + " " + test.testName + ": " + ( error.message || error ); + test.pushFailure( message, extractStacktrace( error, 0 ) ); + + // else next test will carry the responsibility + saveGlobal(); + + // Unblock + QUnit.start(); + } + ); + } + } + }, + + valid: function() { + var include, + filter = config.filter, + module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), + fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); + + // Internally-generated tests are always valid + if ( this.callback && this.callback.validTest ) { + return true; + } + + if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { + return false; + } + + if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) { + return false; + } + + if ( !filter ) { + return true; + } + + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.toLowerCase().slice( 1 ); + } + + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; + } + + // Otherwise, do the opposite + return !include; + } + +}; + +// Resets the test setup. Useful for tests that modify the DOM. +/* +DEPRECATED: Use multiple tests instead of resetting inside a test. +Use testStart or testDone for custom cleanup. +This method will throw an error in 2.0, and will be removed in 2.1 +*/ +QUnit.reset = function() { + + // Return on non-browser environments + // This is necessary to not break on node tests + if ( typeof window === "undefined" ) { + return; + } + + var fixture = defined.document && document.getElementById && + document.getElementById( "qunit-fixture" ); + + if ( fixture ) { + fixture.innerHTML = config.fixture; } }; QUnit.pushFailure = function() { if ( !QUnit.config.current ) { - throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) ); + throw new Error( "pushFailure() assertion outside test context, in " + + sourceFromStacktrace( 2 ) ); } // Gets current test obj - var currentTest = QUnit.config.current.assert.test; + var currentTest = QUnit.config.current; return currentTest.pushFailure.apply( currentTest, arguments ); }; +// Based on Java's String.hashCode, a simple but not +// rigorously collision resistant hashing function +function generateHash( module, testName ) { + var hex, + i = 0, + hash = 0, + str = module + "\x1C" + testName, + len = str.length; + + for ( ; i < len; i++ ) { + hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); + hash |= 0; + } + + // Convert the possibly negative integer hash code into an 8 character hex string, which isn't + // strictly necessary but increases user understanding that the id is a SHA-like hash + hex = ( 0x100000000 + hash ).toString( 16 ); + if ( hex.length < 8 ) { + hex = "0000000" + hex; + } + + return hex.slice( -8 ); +} + function Assert( testContext ) { this.test = testContext; } @@ -969,7 +1225,8 @@ function Assert( testContext ) { // Assert helpers QUnit.assert = Assert.prototype = { - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. + // Specify the number of expected assertions to guarantee that failed test + // (no assertions are run at all) don't slip through. expect: function( asserts ) { if ( arguments.length === 1 ) { this.test.expected = asserts; @@ -978,20 +1235,51 @@ QUnit.assert = Assert.prototype = { } }, + // Increment this Test's semaphore counter, then return a single-use function that + // decrements that counter a maximum of once. + async: function() { + var test = this.test, + popped = false; + + test.semaphore += 1; + test.usedAsync = true; + pauseProcessing(); + + return function done() { + if ( !popped ) { + test.semaphore -= 1; + popped = true; + resumeProcessing(); + } else { + test.pushFailure( "Called the callback returned from `assert.async` more than once", + sourceFromStacktrace( 2 ) ); + } + }; + }, + // Exports test.push() to the user API - push: function() { - var assert = this; + push: function( /* result, actual, expected, message */ ) { + var assert = this, + currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; // Backwards compatibility fix. // Allows the direct use of global exported assertions and QUnit.assert.* // Although, it's use is not recommended as it can leak assertions // to other tests from async tests, because we only get a reference to the current test, // not exactly the test where assertion were intended to be called. - if ( !QUnit.config.current ) { + if ( !currentTest ) { throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); } + + if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { + currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", + sourceFromStacktrace( 2 ) ); + + // Allow this assertion to continue running anyway... + } + if ( !( assert instanceof Assert ) ) { - assert = QUnit.config.current.assert; + assert = currentTest.assert; } return assert.test.push.apply( assert.test, arguments ); }, @@ -1005,11 +1293,7 @@ QUnit.assert = Assert.prototype = { ok: function( result, message ) { message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + QUnit.dump.parse( result ) ); - if ( !!result ) { - this.push( true, result, true, message ); - } else { - this.test.pushFailure( message, null, result ); - } + this.push( !!result, result, true, message ); }, /** @@ -1017,7 +1301,7 @@ QUnit.assert = Assert.prototype = { * Prints out both actual and expected values. * @name equal * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); + * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" ); */ equal: function( actual, expected, message ) { /*jshint eqeqeq:false */ @@ -1143,6 +1427,13 @@ QUnit.assert = Assert.prototype = { } }; +// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word +// Known to us are: Closure Compiler, Narwhal +(function() { + /*jshint sub:true */ + Assert.prototype.raises = Assert.prototype[ "throws" ]; +}()); + // Test for equality any JavaScript type. // Author: Philippe Rathé <[email protected]> QUnit.equiv = (function() { @@ -1356,7 +1647,8 @@ QUnit.equiv = (function() { } // apply transition with (1..n) arguments - }( args[ 0 ], args[ 1 ] ) ) && innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); + }( args[ 0 ], args[ 1 ] ) ) && + innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); }; return innerEquiv; @@ -1386,6 +1678,11 @@ QUnit.dump = (function() { function array( arr, stack ) { var i = arr.length, ret = new Array( i ); + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Array]"; + } + this.up(); while ( i-- ) { ret[ i ] = this.parse( arr[ i ], undefined, stack ); @@ -1396,25 +1693,28 @@ QUnit.dump = (function() { var reName = /^function (\w+)/, dump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || []; - var inStack, res, - parser = this.parsers[ type || this.typeOf( obj ) ]; - type = typeof parser; - inStack = inArray( obj, stack ); + // objType is used mostly internally, you can fix a (custom) type in advance + parse: function( obj, objType, stack ) { + stack = stack || []; + var res, parser, parserType, + inStack = inArray( obj, stack ); if ( inStack !== -1 ) { return "recursion(" + ( inStack - stack.length ) + ")"; } - if ( type === "function" ) { + + objType = objType || this.typeOf( obj ); + parser = this.parsers[ objType ]; + parserType = typeof parser; + + if ( parserType === "function" ) { stack.push( obj ); res = parser.call( this, obj, stack ); stack.pop(); return res; } - return ( type === "string" ) ? parser : this.parsers.error; + return ( parserType === "string" ) ? parser : this.parsers.error; }, typeOf: function( obj ) { var type; @@ -1428,7 +1728,9 @@ QUnit.dump = (function() { type = "date"; } else if ( QUnit.is( "function", obj ) ) { type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + } else if ( obj.setInterval !== undefined && + obj.document !== undefined && + obj.nodeType === undefined ) { type = "window"; } else if ( obj.nodeType === 9 ) { type = "document"; @@ -1440,7 +1742,9 @@ QUnit.dump = (function() { toString.call( obj ) === "[object Array]" || // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) ) + ( typeof obj.length === "number" && obj.item !== undefined && + ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && + obj[ 0 ] === undefined ) ) ) ) { type = "array"; } else if ( obj.constructor === Error.prototype.constructor ) { @@ -1451,7 +1755,7 @@ QUnit.dump = (function() { return type; }, separator: function() { - return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; + return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; }, // extra can be a number, shortcut for increasing-calling-decreasing indent: function( extra ) { @@ -1460,7 +1764,7 @@ QUnit.dump = (function() { } var chr = this.indentChar; if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } return new Array( this.depth + ( extra || 0 ) ).join( chr ); }, @@ -1479,6 +1783,8 @@ QUnit.dump = (function() { join: join, // depth: 1, + maxDepth: 5, + // This is the list of parsers, to modify them, use dump.setParser parsers: { window: "[Window]", @@ -1491,6 +1797,7 @@ QUnit.dump = (function() { "undefined": "undefined", "function": function( fn ) { var ret = "function", + // functions never have name in IE name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; @@ -1506,8 +1813,13 @@ QUnit.dump = (function() { nodelist: array, "arguments": array, object: function( map, stack ) { - /*jshint forin:false */ - var ret = [], keys, key, val, i, nonEnumerableProperties; + var keys, key, val, i, nonEnumerableProperties, + ret = []; + + if ( dump.maxDepth && dump.depth > dump.maxDepth ) { + return "[object Object]"; + } + dump.up(); keys = []; for ( key in map ) { @@ -1526,7 +1838,8 @@ QUnit.dump = (function() { for ( i = 0; i < keys.length; i++ ) { key = keys[ i ]; val = map[ key ]; - ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) ); + ret.push( dump.parse( key, "key" ) + ": " + + dump.parse( val, undefined, stack ) ); } dump.down(); return join( "{", ret, "}" ); @@ -1543,10 +1856,12 @@ QUnit.dump = (function() { for ( i = 0, len = attrs.length; i < len; i++ ) { val = attrs[ i ].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". + // IE6 includes all attributes in .attributes, even ones not explicitly + // set. Those have values like undefined, null, 0, false, "" or + // "inherit". if ( val && val !== "inherit" ) { - ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" ); + ret += " " + attrs[ i ].nodeName + "=" + + dump.parse( val, "attribute" ); } } } @@ -1653,9 +1968,17 @@ if ( typeof window !== "undefined" ) { window.QUnit = QUnit; } -// For CommonJS environments, export everything -if ( typeof module !== "undefined" && module.exports ) { +// For nodejs +if ( typeof module !== "undefined" && module && module.exports ) { module.exports = QUnit; + + // For consistency with CommonJS environments' exports + module.exports.QUnit = QUnit; +} + +// For CommonJS with exports, but without module.exports, like Rhino +if ( typeof exports !== "undefined" && exports ) { + exports.QUnit = QUnit; } // Get a reference to the global object, like window in browsers @@ -1664,6 +1987,7 @@ if ( typeof module !== "undefined" && module.exports ) { })() )); /*istanbul ignore next */ +// jscs:disable maximumLineLength /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) @@ -1810,6 +2134,7 @@ QUnit.diff = (function() { return str; }; }()); +// jscs:enable (function() { @@ -1828,7 +2153,6 @@ QUnit.init = function() { config.autorun = false; config.filter = ""; config.queue = []; - config.semaphore = 1; // Return on non-browser environments // This is necessary to not break on node tests @@ -1867,27 +2191,7 @@ QUnit.init = function() { result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...<br/> "; - } -}; - -// Resets the test setup. Useful for tests that modify the DOM. -/* -DEPRECATED: Use multiple tests instead of resetting inside a test. -Use testStart or testDone for custom cleanup. -This method will throw an error in 2.0, and will be removed in 2.1 -*/ -QUnit.reset = function() { - - // Return on non-browser environments - // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { - return; - } - - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; + result.innerHTML = "Running...<br /> "; } }; @@ -1899,7 +2203,7 @@ if ( typeof window === "undefined" ) { var config = QUnit.config, hasOwn = Object.prototype.hasOwnProperty, defined = { - document: typeof window.document !== "undefined", + document: window.document !== undefined, sessionStorage: (function() { var x = "qunit-test-string"; try { @@ -1910,7 +2214,8 @@ var config = QUnit.config, return false; } }()) - }; + }, + modulesList = []; /** * Escape text for attribute or text content. @@ -2020,13 +2325,16 @@ function getUrlConfigHtml() { escaped = escapeText( val.id ); escapedTooltip = escapeText( val.tooltip ); - config[ val.id ] = QUnit.urlParams[ val.id ]; + if ( config[ val.id ] === undefined ) { + config[ val.id ] = QUnit.urlParams[ val.id ]; + } + if ( !val.value || typeof val.value === "string" ) { urlConfigHtml += "<input id='qunit-urlconfig-" + escaped + "' name='" + escaped + "' type='checkbox'" + ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) + ( config[ val.id ] ? " checked='checked'" : "" ) + - " title='" + escapedTooltip + "'><label for='qunit-urlconfig-" + escaped + + " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped + "' title='" + escapedTooltip + "'>" + val.label + "</label>"; } else { urlConfigHtml += "<label for='qunit-urlconfig-" + escaped + @@ -2064,69 +2372,145 @@ function getUrlConfigHtml() { return urlConfigHtml; } +// Handle "click" events on toolbar checkboxes and "change" for select menus. +// Updates the URL with the new state of `config.urlConfig` values. +function toolbarChanged() { + var updatedUrl, value, + field = this, + params = {}; + + // Detect if field is a select menu or a checkbox + if ( "selectedIndex" in field ) { + value = field.options[ field.selectedIndex ].value || undefined; + } else { + value = field.checked ? ( field.defaultValue || true ) : undefined; + } + + params[ field.name ] = value; + updatedUrl = setUrl( params ); + + if ( "hidepassed" === field.name && "replaceState" in window.history ) { + config[ field.name ] = value || false; + if ( value ) { + addClass( id( "qunit-tests" ), "hidepass" ); + } else { + removeClass( id( "qunit-tests" ), "hidepass" ); + } + + // It is not necessary to refresh the whole page + window.history.replaceState( null, "", updatedUrl ); + } else { + window.location = updatedUrl; + } +} + +function setUrl( params ) { + var key, + querystring = "?"; + + params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); + + for ( key in params ) { + if ( hasOwn.call( params, key ) ) { + if ( params[ key ] === undefined ) { + continue; + } + querystring += encodeURIComponent( key ); + if ( params[ key ] !== true ) { + querystring += "=" + encodeURIComponent( params[ key ] ); + } + querystring += "&"; + } + } + return location.protocol + "//" + location.host + + location.pathname + querystring.slice( 0, -1 ); +} + +function applyUrlParams() { + var selectBox = id( "qunit-modulefilter" ), + selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ), + filter = id( "qunit-filter-input" ).value; + + window.location = setUrl({ + module: ( selection === "" ) ? undefined : selection, + filter: ( filter === "" ) ? undefined : filter, + + // Remove testId filter + testId: undefined + }); +} + function toolbarUrlConfigContainer() { var urlConfigContainer = document.createElement( "span" ); urlConfigContainer.innerHTML = getUrlConfigHtml(); + addClass( urlConfigContainer, "qunit-url-config" ); // For oldIE support: // * Add handlers to the individual elements instead of the container // * Use "click" instead of "change" for checkboxes - // * Fallback from event.target to event.srcElement - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? - target.defaultValue || true : - undefined; - window.location = QUnit.url( params ); - }); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; - window.location = QUnit.url( params ); - }); + addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); + addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); return urlConfigContainer; } -function getModuleNames() { - var i, - moduleNames = []; +function toolbarLooseFilter() { + var filter = document.createElement( "form" ), + label = document.createElement( "label" ), + input = document.createElement( "input" ), + button = document.createElement( "button" ); + + addClass( filter, "qunit-filter" ); + + label.innerHTML = "Filter: "; + + input.type = "text"; + input.value = config.filter || ""; + input.name = "filter"; + input.id = "qunit-filter-input"; + + button.innerHTML = "Go"; - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push( i ); + label.appendChild( input ); + + filter.appendChild( label ); + filter.appendChild( button ); + addEvent( filter, "submit", function( ev ) { + applyUrlParams(); + + if ( ev && ev.preventDefault ) { + ev.preventDefault(); } - } - moduleNames.sort(function( a, b ) { - return a.localeCompare( b ); + return false; }); - return moduleNames; + return filter; } function toolbarModuleFilterHtml() { var i, - moduleFilterHtml = "", - moduleNames = getModuleNames(); + moduleFilterHtml = ""; - if ( moduleNames.length <= 1 ) { + if ( !modulesList.length ) { return false; } + modulesList.sort(function( a, b ) { + return a.localeCompare( b ); + }); + moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" + "<select id='qunit-modulefilter' name='modulefilter'><option value='' " + - ( config.module === undefined ? "selected='selected'" : "" ) + + ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) + ">< All Modules ></option>"; - for ( i = 0; i < moduleNames.length; i++ ) { + for ( i = 0; i < modulesList.length; i++ ) { moduleFilterHtml += "<option value='" + - escapeText( encodeURIComponent( moduleNames[ i ] ) ) + "' " + - ( config.module === moduleNames[ i ] ? "selected='selected'" : "" ) + - ">" + escapeText( moduleNames[ i ] ) + "</option>"; + escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " + + ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) + + ">" + escapeText( modulesList[ i ] ) + "</option>"; } moduleFilterHtml += "</select>"; @@ -2134,7 +2518,8 @@ function toolbarModuleFilterHtml() { } function toolbarModuleFilter() { - var moduleFilter = document.createElement( "span" ), + var toolbar = id( "qunit-testrunner-toolbar" ), + moduleFilter = document.createElement( "span" ), moduleFilterHtml = toolbarModuleFilterHtml(); if ( !moduleFilterHtml ) { @@ -2144,75 +2529,27 @@ function toolbarModuleFilter() { moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ], - selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - - return moduleFilter; -} - -function toolbarFilter() { - var testList = id( "qunit-tests" ), - filter = document.createElement( "input" ); + addEvent( moduleFilter.lastChild, "change", applyUrlParams ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - if ( filter.checked ) { - addClass( testList, "hidepass" ); - if ( defined.sessionStorage ) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } - } else { - removeClass( testList, "hidepass" ); - if ( defined.sessionStorage ) { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && - sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - - addClass( testList, "hidepass" ); - } - - return filter; -} - -function toolbarLabel() { - var label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - - return label; + toolbar.appendChild( moduleFilter ); } function appendToolbar() { - var moduleFilter, - toolbar = id( "qunit-testrunner-toolbar" ); + var toolbar = id( "qunit-testrunner-toolbar" ); if ( toolbar ) { - toolbar.appendChild( toolbarFilter() ); - toolbar.appendChild( toolbarLabel() ); toolbar.appendChild( toolbarUrlConfigContainer() ); + toolbar.appendChild( toolbarLooseFilter() ); + } +} - moduleFilter = toolbarModuleFilter(); - if ( moduleFilter ) { - toolbar.appendChild( moduleFilter ); - } +function appendHeader() { + var header = id( "qunit-header" ); + + if ( header ) { + header.innerHTML = "<a href='" + + setUrl({ filter: undefined, module: undefined, testId: undefined }) + + "'>" + header.innerHTML + "</a> "; } } @@ -2221,9 +2558,6 @@ function appendBanner() { if ( banner ) { banner.className = ""; - banner.innerHTML = "<a href='" + - QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + - "'>" + banner.innerHTML + "</a> "; } } @@ -2241,7 +2575,7 @@ function appendTestResults() { result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...<br> "; + result.innerHTML = "Running...<br /> "; } } @@ -2255,28 +2589,84 @@ function storeFixture() { function appendUserAgent() { var userAgent = id( "qunit-userAgent" ); if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; + userAgent.innerHTML = ""; + userAgent.appendChild( document.createTextNode( navigator.userAgent ) ); + } +} + +function appendTestsList( modules ) { + var i, l, x, z, test, moduleObj; + + for ( i = 0, l = modules.length; i < l; i++ ) { + moduleObj = modules[ i ]; + + if ( moduleObj.name ) { + modulesList.push( moduleObj.name ); + } + + for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { + test = moduleObj.tests[ x ]; + + appendTest( test.name, test.testId, moduleObj.name ); + } + } +} + +function appendTest( name, testId, moduleName ) { + var title, rerunTrigger, testBlock, assertList, + tests = id( "qunit-tests" ); + + if ( !tests ) { + return; } + + title = document.createElement( "strong" ); + title.innerHTML = getNameHtml( name, moduleName ); + + rerunTrigger = document.createElement( "a" ); + rerunTrigger.innerHTML = "Rerun"; + rerunTrigger.href = setUrl({ testId: testId }); + + testBlock = document.createElement( "li" ); + testBlock.appendChild( title ); + testBlock.appendChild( rerunTrigger ); + testBlock.id = "qunit-test-output-" + testId; + + assertList = document.createElement( "ol" ); + assertList.className = "qunit-assert-list"; + + testBlock.appendChild( assertList ); + + tests.appendChild( testBlock ); } // HTML Reporter initialization and load -QUnit.begin(function() { +QUnit.begin(function( details ) { var qunit = id( "qunit" ); + // Fixture is the only one necessary to run without the #qunit element + storeFixture(); + if ( qunit ) { qunit.innerHTML = - "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + - "<h2 id='qunit-banner'></h2>" + - "<div id='qunit-testrunner-toolbar'></div>" + - "<h2 id='qunit-userAgent'></h2>" + - "<ol id='qunit-tests'></ol>"; + "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" + + "<h2 id='qunit-banner'></h2>" + + "<div id='qunit-testrunner-toolbar'></div>" + + "<h2 id='qunit-userAgent'></h2>" + + "<ol id='qunit-tests'></ol>"; } + appendHeader(); appendBanner(); appendTestResults(); appendUserAgent(); appendToolbar(); - storeFixture(); + appendTestsList( details.modules ); + toolbarModuleFilter(); + + if ( qunit && config.hidepassed ) { + addClass( qunit.lastChild, "hidepass" ); + } }); QUnit.done(function( details ) { @@ -2286,7 +2676,7 @@ QUnit.done(function( details ) { html = [ "Tests completed in ", details.runtime, - " milliseconds.<br>", + " milliseconds.<br />", "<span class='passed'>", details.passed, "</span> assertions of <span class='total'>", @@ -2343,35 +2733,20 @@ function getNameHtml( name, module ) { } QUnit.testStart(function( details ) { - var a, b, li, running, assertList, - name = getNameHtml( details.name, details.module ), - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = name; - - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: details.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = "qunit-test-output" + details.testNumber; - - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; + var running, testBlock; - li.appendChild( assertList ); + testBlock = id( "qunit-test-output-" + details.testId ); + if ( testBlock ) { + testBlock.className = "running"; + } else { - tests.appendChild( li ); + // Report later registered tests + appendTest( details.name, details.testId, details.module ); } running = id( "qunit-testresult" ); if ( running ) { - running.innerHTML = "Running: <br>" + name; + running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module ); } }); @@ -2379,7 +2754,7 @@ QUnit.testStart(function( details ) { QUnit.log(function( details ) { var assertList, assertLi, message, expected, actual, - testItem = id( "qunit-test-output" + details.testNumber ); + testItem = id( "qunit-test-output-" + details.testId ); if ( !testItem ) { return; @@ -2387,6 +2762,7 @@ QUnit.log(function( details ) { message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); message = "<span class='test-message'>" + message + "</span>"; + message += "<span class='runtime'>@ " + details.runtime + " ms</span>"; // pushFailure doesn't provide details.expected // when it calls, it's implicit to also not show expected and diff stuff @@ -2430,19 +2806,15 @@ QUnit.log(function( details ) { QUnit.testDone(function( details ) { var testTitle, time, testItem, assertList, - good, bad, testCounts, + good, bad, testCounts, skipped, tests = id( "qunit-tests" ); - // QUnit.reset() is deprecated and will be replaced for a new - // fixture reset function on QUnit 2.0/2.1. - // It's still called here for backwards compatibility handling - QUnit.reset(); - if ( !tests ) { return; } - testItem = id( "qunit-test-output" + details.testNumber ); + testItem = id( "qunit-test-output-" + details.testId ); + assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; good = details.passed; @@ -2471,20 +2843,28 @@ QUnit.testDone(function( details ) { testTitle.innerHTML += " <b class='counts'>(" + testCounts + details.assertions.length + ")</b>"; - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - }); - - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; + if ( details.skipped ) { + testItem.className = "skipped"; + skipped = document.createElement( "em" ); + skipped.className = "qunit-skipped-label"; + skipped.innerHTML = "skipped"; + testItem.insertBefore( skipped, testTitle ); + } else { + addEvent( testTitle, "click", function() { + toggleClass( assertList, "qunit-collapsed" ); + }); - testItem.className = bad ? "fail" : "pass"; + testItem.className = bad ? "fail" : "pass"; - testItem.insertBefore( time, assertList ); + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = details.runtime + " ms"; + testItem.insertBefore( time, assertList ); + } }); if ( !defined.document || document.readyState === "complete" ) { + config.pageLoaded = true; config.autorun = true; } |
