Skip to content
This repository
Browse code

Fixed bugs and added unit tests

  • Loading branch information...
commit 24b2aa9ed3ba89084044de815fd83f94757f9010 1 parent 206e43e
Ulrich Sossou authored
8 bootstrap-tour.coffee
@@ -82,12 +82,14 @@
82 82
83 83 # Verify if tour is enabled
84 84 ended: ->
85   - @getState("end")
  85 + !!@getState("end")
86 86
87 87 # Restart tour
88 88 restart: ->
89 89 @setState("current_step", null)
90 90 @setState("end", null)
  91 + @setCurrentStep(0)
  92 + @start()
91 93
92 94 # Hide the specified step
93 95 hideStep: (i) ->
@@ -126,7 +128,7 @@
126 128 step.onShow(@) if step.onShow?
127 129
128 130 # Show popover
129   - @showPopover(step, i)
  131 + @_showPopover(step, i)
130 132
131 133 # Setup current step variable
132 134 setCurrentStep: (value) ->
@@ -152,7 +154,7 @@
152 154 @showStep(step.next)
153 155
154 156 # Show step popover
155   - showPopover: (step, i) ->
  157 + _showPopover: (step, i) ->
156 158 content = "#{step.content}<br /><p>"
157 159 if step.end
158 160 content += "<a href='#' class='end'>End</a>"
10 bootstrap-tour.js
@@ -96,12 +96,14 @@
96 96 };
97 97
98 98 Tour.prototype.ended = function() {
99   - return this.getState("end");
  99 + return !!this.getState("end");
100 100 };
101 101
102 102 Tour.prototype.restart = function() {
103 103 this.setState("current_step", null);
104   - return this.setState("end", null);
  104 + this.setState("end", null);
  105 + this.setCurrentStep(0);
  106 + return this.start();
105 107 };
106 108
107 109 Tour.prototype.hideStep = function(i) {
@@ -137,7 +139,7 @@
137 139 if (step.onShow != null) {
138 140 step.onShow(this);
139 141 }
140   - return this.showPopover(step, i);
  142 + return this._showPopover(step, i);
141 143 };
142 144
143 145 Tour.prototype.setCurrentStep = function(value) {
@@ -167,7 +169,7 @@
167 169 return this.showStep(step.next);
168 170 };
169 171
170   - Tour.prototype.showPopover = function(step, i) {
  172 + Tour.prototype._showPopover = function(step, i) {
171 173 var content, tip;
172 174 content = "" + step.content + "<br /><p>";
173 175 if (step.end) {
90 deps/bootstrap-alert.js
... ... @@ -0,0 +1,90 @@
  1 +/* ==========================================================
  2 + * bootstrap-alert.js v2.0.4
  3 + * http://twitter.github.com/bootstrap/javascript.html#alerts
  4 + * ==========================================================
  5 + * Copyright 2012 Twitter, Inc.
  6 + *
  7 + * Licensed under the Apache License, Version 2.0 (the "License");
  8 + * you may not use this file except in compliance with the License.
  9 + * You may obtain a copy of the License at
  10 + *
  11 + * http://www.apache.org/licenses/LICENSE-2.0
  12 + *
  13 + * Unless required by applicable law or agreed to in writing, software
  14 + * distributed under the License is distributed on an "AS IS" BASIS,
  15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16 + * See the License for the specific language governing permissions and
  17 + * limitations under the License.
  18 + * ========================================================== */
  19 +
  20 +
  21 +!function ($) {
  22 +
  23 + "use strict"; // jshint ;_;
  24 +
  25 +
  26 + /* ALERT CLASS DEFINITION
  27 + * ====================== */
  28 +
  29 + var dismiss = '[data-dismiss="alert"]'
  30 + , Alert = function (el) {
  31 + $(el).on('click', dismiss, this.close)
  32 + }
  33 +
  34 + Alert.prototype.close = function (e) {
  35 + var $this = $(this)
  36 + , selector = $this.attr('data-target')
  37 + , $parent
  38 +
  39 + if (!selector) {
  40 + selector = $this.attr('href')
  41 + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
  42 + }
  43 +
  44 + $parent = $(selector)
  45 +
  46 + e && e.preventDefault()
  47 +
  48 + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
  49 +
  50 + $parent.trigger(e = $.Event('close'))
  51 +
  52 + if (e.isDefaultPrevented()) return
  53 +
  54 + $parent.removeClass('in')
  55 +
  56 + function removeElement() {
  57 + $parent
  58 + .trigger('closed')
  59 + .remove()
  60 + }
  61 +
  62 + $.support.transition && $parent.hasClass('fade') ?
  63 + $parent.on($.support.transition.end, removeElement) :
  64 + removeElement()
  65 + }
  66 +
  67 +
  68 + /* ALERT PLUGIN DEFINITION
  69 + * ======================= */
  70 +
  71 + $.fn.alert = function (option) {
  72 + return this.each(function () {
  73 + var $this = $(this)
  74 + , data = $this.data('alert')
  75 + if (!data) $this.data('alert', (data = new Alert(this)))
  76 + if (typeof option == 'string') data[option].call($this)
  77 + })
  78 + }
  79 +
  80 + $.fn.alert.Constructor = Alert
  81 +
  82 +
  83 + /* ALERT DATA-API
  84 + * ============== */
  85 +
  86 + $(function () {
  87 + $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
  88 + })
  89 +
  90 +}(window.jQuery);
16 deps/example-tour.js
@@ -24,7 +24,7 @@ jQuery(function($) {
24 24 + "complicated things. <br \/>Power to the people! :P"
25 25 });
26 26 tour.addStep({
27   - path: "test.html",
  27 + path: "page.html",
28 28 element: "h1",
29 29 placement: "bottom",
30 30 title: "See, you are not restricted to only one page",
@@ -42,13 +42,15 @@ jQuery(function($) {
42 42 tour.start();
43 43
44 44 if ( tour.ended() ) {
45   - $(".content").prepend('<div class="alert">\
46   - <button class="close" data-dismiss="alert">×</button>\
47   - You ended the demo tour. <a href="" class="restart">Restart the demo tour.</a>\
48   - </div>');
  45 + $('<div class="alert">\
  46 + <button class="close" data-dismiss="alert">×</button>\
  47 + You ended the demo tour. <a href="" class="restart">Restart the demo tour.</a>\
  48 + </div>').prependTo(".content").alert();
49 49 }
50 50
51   - $(".restart").click(function () {
52   - tour.restart()
  51 + $(".restart").click(function (e) {
  52 + e.preventDefault();
  53 + tour.restart();
  54 + $(this).parents(".alert").alert("close");
53 55 });
54 56 });
1  index.html
@@ -188,6 +188,7 @@ <h2 id="license">License</h2>
188 188 <script src="deps/google-code-prettify/prettify.js"></script>
189 189 <script src="deps/bootstrap-tooltip.js"></script>
190 190 <script src="deps/bootstrap-popover.js"></script>
  191 + <script src="deps/bootstrap-alert.js"></script>
191 192 <script src="deps/jquery.cookie.js"></script>
192 193 <script src="bootstrap-tour.js"></script>
193 194 <script src="deps/example-tour.js"></script>
45 page.html
... ... @@ -0,0 +1,45 @@
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 + <head>
  4 + <meta charset="utf-8">
  5 + <title>Bootstrap Tour, An Extension of Bootstrap from Twitter</title>
  6 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7 + <meta name="description" content="">
  8 + <meta name="author" content="">
  9 +
  10 + <!-- Le styles -->
  11 + <link href="deps/bootstrap.css" rel="stylesheet">
  12 + <style>
  13 + body {
  14 + padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
  15 + }
  16 + </style>
  17 + <link href="deps/bootstrap-responsive.css" rel="stylesheet">
  18 +
  19 + <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
  20 + <!--[if lt IE 9]>
  21 + <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
  22 + <![endif]-->
  23 + </head>
  24 +
  25 + <body>
  26 +
  27 + <div class="container">
  28 +
  29 + <h1>This is just a test.</h1>
  30 + <p>Nothing to see here. Move on!</p>
  31 +
  32 + </div> <!-- /container -->
  33 +
  34 + <!-- Le javascript
  35 + ================================================== -->
  36 + <!-- Placed at the end of the document so the pages load faster -->
  37 + <script src="deps/jquery.js"></script>
  38 + <script src="deps/bootstrap-tooltip.js"></script>
  39 + <script src="deps/bootstrap-popover.js"></script>
  40 + <script src="deps/jquery.cookie.js"></script>
  41 + <script src="bootstrap-tour.js"></script>
  42 + <script src="deps/example-tour.js"></script>
  43 +
  44 + </body>
  45 +</html>
46 test.html
... ... @@ -1,45 +1,25 @@
1 1 <!DOCTYPE html>
2   -<html lang="en">
  2 +<html>
3 3 <head>
4 4 <meta charset="utf-8">
5   - <title>Bootstrap Tour, An Extension of Bootstrap from Twitter</title>
6   - <meta name="viewport" content="width=device-width, initial-scale=1.0">
7   - <meta name="description" content="">
8   - <meta name="author" content="">
  5 + <title>Bootstrap Tour Unit Tests</title>
  6 + <link rel="stylesheet" href="tests/qunit/qunit.css">
  7 + <script src="tests/qunit/qunit.js"></script>
  8 + <script src="tests/tests.js"></script>
9 9
10   - <!-- Le styles -->
11 10 <link href="deps/bootstrap.css" rel="stylesheet">
12   - <style>
13   - body {
14   - padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
15   - }
16   - </style>
17   - <link href="deps/bootstrap-responsive.css" rel="stylesheet">
18   -
19   - <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
20   - <!--[if lt IE 9]>
21   - <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
22   - <![endif]-->
23   - </head>
24   -
25   - <body>
26   -
27   - <div class="container">
28   -
29   - <h1>This is just a test.</h1>
30   - <p>Nothing to see here. Move on!</p>
31   -
32   - </div> <!-- /container -->
33   -
34   - <!-- Le javascript
35   - ================================================== -->
36   - <!-- Placed at the end of the document so the pages load faster -->
37 11 <script src="deps/jquery.js"></script>
38 12 <script src="deps/bootstrap-tooltip.js"></script>
39 13 <script src="deps/bootstrap-popover.js"></script>
40 14 <script src="deps/jquery.cookie.js"></script>
41 15 <script src="bootstrap-tour.js"></script>
42   - <script src="deps/example-tour.js"></script>
43   -
  16 + </head>
  17 + <body>
  18 + <h1 id="qunit-header">Bootstrap Tour Unit Tests</h1>
  19 + <h2 id="qunit-banner"></h2>
  20 + <div id="qunit-testrunner-toolbar"></div>
  21 + <h2 id="qunit-userAgent"></h2>
  22 + <ol id="qunit-tests"></ol>
  23 + <div id="qunit-fixture"></div>
44 24 </body>
45 25 </html>
236 tests/qunit/qunit.css
... ... @@ -0,0 +1,236 @@
  1 +/**
  2 + * QUnit v1.8.0 - A JavaScript Unit Testing Framework
  3 + *
  4 + * http://docs.jquery.com/QUnit
  5 + *
  6 + * Copyright (c) 2012 John Resig, Jörn Zaefferer
  7 + * Dual licensed under the MIT (MIT-LICENSE.txt)
  8 + * or GPL (GPL-LICENSE.txt) licenses.
  9 + */
  10 +
  11 +/** Font Family and Sizes */
  12 +
  13 +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
  14 + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
  15 +}
  16 +
  17 +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
  18 +#qunit-tests { font-size: smaller; }
  19 +
  20 +
  21 +/** Resets */
  22 +
  23 +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
  24 + margin: 0;
  25 + padding: 0;
  26 +}
  27 +
  28 +
  29 +/** Header */
  30 +
  31 +#qunit-header {
  32 + padding: 0.5em 0 0.5em 1em;
  33 +
  34 + color: #8699a4;
  35 + background-color: #0d3349;
  36 +
  37 + font-size: 1.5em;
  38 + line-height: 1em;
  39 + font-weight: normal;
  40 +
  41 + border-radius: 15px 15px 0 0;
  42 + -moz-border-radius: 15px 15px 0 0;
  43 + -webkit-border-top-right-radius: 15px;
  44 + -webkit-border-top-left-radius: 15px;
  45 +}
  46 +
  47 +#qunit-header a {
  48 + text-decoration: none;
  49 + color: #c2ccd1;
  50 +}
  51 +
  52 +#qunit-header a:hover,
  53 +#qunit-header a:focus {
  54 + color: #fff;
  55 +}
  56 +
  57 +#qunit-header label {
  58 + display: inline-block;
  59 + padding-left: 0.5em;
  60 +}
  61 +
  62 +#qunit-banner {
  63 + height: 5px;
  64 +}
  65 +
  66 +#qunit-testrunner-toolbar {
  67 + padding: 0.5em 0 0.5em 2em;
  68 + color: #5E740B;
  69 + background-color: #eee;
  70 +}
  71 +
  72 +#qunit-userAgent {
  73 + padding: 0.5em 0 0.5em 2.5em;
  74 + background-color: #2b81af;
  75 + color: #fff;
  76 + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
  77 +}
  78 +
  79 +
  80 +/** Tests: Pass/Fail */
  81 +
  82 +#qunit-tests {
  83 + list-style-position: inside;
  84 +}
  85 +
  86 +#qunit-tests li {
  87 + padding: 0.4em 0.5em 0.4em 2.5em;
  88 + border-bottom: 1px solid #fff;
  89 + list-style-position: inside;
  90 +}
  91 +
  92 +#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
  93 + display: none;
  94 +}
  95 +
  96 +#qunit-tests li strong {
  97 + cursor: pointer;
  98 +}
  99 +
  100 +#qunit-tests li a {
  101 + padding: 0.5em;
  102 + color: #c2ccd1;
  103 + text-decoration: none;
  104 +}
  105 +#qunit-tests li a:hover,
  106 +#qunit-tests li a:focus {
  107 + color: #000;
  108 +}
  109 +
  110 +#qunit-tests ol {
  111 + margin-top: 0.5em;
  112 + padding: 0.5em;
  113 +
  114 + background-color: #fff;
  115 +
  116 + border-radius: 15px;
  117 + -moz-border-radius: 15px;
  118 + -webkit-border-radius: 15px;
  119 +
  120 + box-shadow: inset 0px 2px 13px #999;
  121 + -moz-box-shadow: inset 0px 2px 13px #999;
  122 + -webkit-box-shadow: inset 0px 2px 13px #999;
  123 +}
  124 +
  125 +#qunit-tests table {
  126 + border-collapse: collapse;
  127 + margin-top: .2em;
  128 +}
  129 +
  130 +#qunit-tests th {
  131 + text-align: right;
  132 + vertical-align: top;
  133 + padding: 0 .5em 0 0;
  134 +}
  135 +
  136 +#qunit-tests td {
  137 + vertical-align: top;
  138 +}
  139 +
  140 +#qunit-tests pre {
  141 + margin: 0;
  142 + white-space: pre-wrap;
  143 + word-wrap: break-word;
  144 +}
  145 +
  146 +#qunit-tests del {
  147 + background-color: #e0f2be;
  148 + color: #374e0c;
  149 + text-decoration: none;
  150 +}
  151 +
  152 +#qunit-tests ins {
  153 + background-color: #ffcaca;
  154 + color: #500;
  155 + text-decoration: none;
  156 +}
  157 +
  158 +/*** Test Counts */
  159 +
  160 +#qunit-tests b.counts { color: black; }
  161 +#qunit-tests b.passed { color: #5E740B; }
  162 +#qunit-tests b.failed { color: #710909; }
  163 +
  164 +#qunit-tests li li {
  165 + margin: 0.5em;
  166 + padding: 0.4em 0.5em 0.4em 0.5em;
  167 + background-color: #fff;
  168 + border-bottom: none;
  169 + list-style-position: inside;
  170 +}
  171 +
  172 +/*** Passing Styles */
  173 +
  174 +#qunit-tests li li.pass {
  175 + color: #5E740B;
  176 + background-color: #fff;
  177 + border-left: 26px solid #C6E746;
  178 +}
  179 +
  180 +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
  181 +#qunit-tests .pass .test-name { color: #366097; }
  182 +
  183 +#qunit-tests .pass .test-actual,
  184 +#qunit-tests .pass .test-expected { color: #999999; }
  185 +
  186 +#qunit-banner.qunit-pass { background-color: #C6E746; }
  187 +
  188 +/*** Failing Styles */
  189 +
  190 +#qunit-tests li li.fail {
  191 + color: #710909;
  192 + background-color: #fff;
  193 + border-left: 26px solid #EE5757;
  194 + white-space: pre;
  195 +}
  196 +
  197 +#qunit-tests > li:last-child {
  198 + border-radius: 0 0 15px 15px;
  199 + -moz-border-radius: 0 0 15px 15px;
  200 + -webkit-border-bottom-right-radius: 15px;
  201 + -webkit-border-bottom-left-radius: 15px;
  202 +}
  203 +
  204 +#qunit-tests .fail { color: #000000; background-color: #EE5757; }
  205 +#qunit-tests .fail .test-name,
  206 +#qunit-tests .fail .module-name { color: #000000; }
  207 +
  208 +#qunit-tests .fail .test-actual { color: #EE5757; }
  209 +#qunit-tests .fail .test-expected { color: green; }
  210 +
  211 +#qunit-banner.qunit-fail { background-color: #EE5757; }
  212 +
  213 +
  214 +/** Result */
  215 +
  216 +#qunit-testresult {
  217 + padding: 0.5em 0.5em 0.5em 2.5em;
  218 +
  219 + color: #2b81af;
  220 + background-color: #D2E0E6;
  221 +
  222 + border-bottom: 1px solid white;
  223 +}
  224 +#qunit-testresult .module-name {
  225 + font-weight: bold;
  226 +}
  227 +
  228 +/** Fixture */
  229 +
  230 +#qunit-fixture {
  231 + position: absolute;
  232 + top: -10000px;
  233 + left: -10000px;
  234 + width: 1000px;
  235 + height: 1000px;
  236 +}
1,863 tests/qunit/qunit.js
... ... @@ -0,0 +1,1863 @@
  1 +/**
  2 + * QUnit v1.8.0 - A JavaScript Unit Testing Framework
  3 + *
  4 + * http://docs.jquery.com/QUnit
  5 + *
  6 + * Copyright (c) 2012 John Resig, Jörn Zaefferer
  7 + * Dual licensed under the MIT (MIT-LICENSE.txt)
  8 + * or GPL (GPL-LICENSE.txt) licenses.
  9 + */
  10 +
  11 +(function( window ) {
  12 +
  13 +var QUnit,
  14 + config,
  15 + onErrorFnPrev,
  16 + testId = 0,
  17 + fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
  18 + toString = Object.prototype.toString,
  19 + hasOwn = Object.prototype.hasOwnProperty,
  20 + defined = {
  21 + setTimeout: typeof window.setTimeout !== "undefined",
  22 + sessionStorage: (function() {
  23 + var x = "qunit-test-string";
  24 + try {
  25 + sessionStorage.setItem( x, x );
  26 + sessionStorage.removeItem( x );
  27 + return true;
  28 + } catch( e ) {
  29 + return false;
  30 + }
  31 + }())
  32 +};
  33 +
  34 +function Test( settings ) {
  35 + extend( this, settings );
  36 + this.assertions = [];
  37 + this.testNumber = ++Test.count;
  38 +}
  39 +
  40 +Test.count = 0;
  41 +
  42 +Test.prototype = {
  43 + init: function() {
  44 + var a, b, li,
  45 + tests = id( "qunit-tests" );
  46 +
  47 + if ( tests ) {
  48 + b = document.createElement( "strong" );
  49 + b.innerHTML = this.name;
  50 +
  51 + // `a` initialized at top of scope
  52 + a = document.createElement( "a" );
  53 + a.innerHTML = "Rerun";
  54 + a.href = QUnit.url({ testNumber: this.testNumber });
  55 +
  56 + li = document.createElement( "li" );
  57 + li.appendChild( b );
  58 + li.appendChild( a );
  59 + li.className = "running";
  60 + li.id = this.id = "qunit-test-output" + testId++;
  61 +
  62 + tests.appendChild( li );
  63 + }
  64 + },
  65 + setup: function() {
  66 + if ( this.module !== config.previousModule ) {
  67 + if ( config.previousModule ) {
  68 + runLoggingCallbacks( "moduleDone", QUnit, {
  69 + name: config.previousModule,
  70 + failed: config.moduleStats.bad,
  71 + passed: config.moduleStats.all - config.moduleStats.bad,
  72 + total: config.moduleStats.all
  73 + });
  74 + }
  75 + config.previousModule = this.module;
  76 + config.moduleStats = { all: 0, bad: 0 };
  77 + runLoggingCallbacks( "moduleStart", QUnit, {
  78 + name: this.module
  79 + });
  80 + } else if ( config.autorun ) {
  81 + runLoggingCallbacks( "moduleStart", QUnit, {
  82 + name: this.module
  83 + });
  84 + }
  85 +
  86 + config.current = this;
  87 +
  88 + this.testEnvironment = extend({
  89 + setup: function() {},
  90 + teardown: function() {}
  91 + }, this.moduleTestEnvironment );
  92 +
  93 + runLoggingCallbacks( "testStart", QUnit, {
  94 + name: this.testName,
  95 + module: this.module
  96 + });
  97 +
  98 + // allow utility functions to access the current test environment
  99 + // TODO why??
  100 + QUnit.current_testEnvironment = this.testEnvironment;
  101 +
  102 + if ( !config.pollution ) {
  103 + saveGlobal();
  104 + }
  105 + if ( config.notrycatch ) {
  106 + this.testEnvironment.setup.call( this.testEnvironment );
  107 + return;
  108 + }
  109 + try {
  110 + this.testEnvironment.setup.call( this.testEnvironment );
  111 + } catch( e ) {
  112 + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
  113 + }
  114 + },
  115 + run: function() {
  116 + config.current = this;
  117 +
  118 + var running = id( "qunit-testresult" );
  119 +
  120 + if ( running ) {
  121 + running.innerHTML = "Running: <br/>" + this.name;
  122 + }
  123 +
  124 + if ( this.async ) {
  125 + QUnit.stop();
  126 + }
  127 +
  128 + if ( config.notrycatch ) {
  129 + this.callback.call( this.testEnvironment, QUnit.assert );
  130 + return;
  131 + }
  132 +
  133 + try {
  134 + this.callback.call( this.testEnvironment, QUnit.assert );
  135 + } catch( e ) {
  136 + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
  137 + // else next test will carry the responsibility
  138 + saveGlobal();
  139 +
  140 + // Restart the tests if they're blocking
  141 + if ( config.blocking ) {
  142 + QUnit.start();
  143 + }
  144 + }
  145 + },
  146 + teardown: function() {
  147 + config.current = this;
  148 + if ( config.notrycatch ) {
  149 + this.testEnvironment.teardown.call( this.testEnvironment );
  150 + return;
  151 + } else {
  152 + try {
  153 + this.testEnvironment.teardown.call( this.testEnvironment );
  154 + } catch( e ) {
  155 + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
  156 + }
  157 + }
  158 + checkPollution();
  159 + },
  160 + finish: function() {
  161 + config.current = this;
  162 + if ( config.requireExpects && this.expected == null ) {
  163 + QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
  164 + } else if ( this.expected != null && this.expected != this.assertions.length ) {
  165 + QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
  166 + } else if ( this.expected == null && !this.assertions.length ) {
  167 + QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
  168 + }
  169 +
  170 + var assertion, a, b, i, li, ol,
  171 + test = this,
  172 + good = 0,
  173 + bad = 0,
  174 + tests = id( "qunit-tests" );
  175 +
  176 + config.stats.all += this.assertions.length;
  177 + config.moduleStats.all += this.assertions.length;
  178 +
  179 + if ( tests ) {
  180 + ol = document.createElement( "ol" );
  181 +
  182 + for ( i = 0; i < this.assertions.length; i++ ) {
  183 + assertion = this.assertions[i];
  184 +
  185 + li = document.createElement( "li" );
  186 + li.className = assertion.result ? "pass" : "fail";
  187 + li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
  188 + ol.appendChild( li );
  189 +
  190 + if ( assertion.result ) {
  191 + good++;
  192 + } else {
  193 + bad++;
  194 + config.stats.bad++;
  195 + config.moduleStats.bad++;
  196 + }
  197 + }
  198 +
  199 + // store result when possible
  200 + if ( QUnit.config.reorder && defined.sessionStorage ) {
  201 + if ( bad ) {
  202 + sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
  203 + } else {
  204 + sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
  205 + }
  206 + }
  207 +
  208 + if ( bad === 0 ) {
  209 + ol.style.display = "none";
  210 + }
  211 +
  212 + // `b` initialized at top of scope
  213 + b = document.createElement( "strong" );
  214 + b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
  215 +
  216 + addEvent(b, "click", function() {
  217 + var next = b.nextSibling.nextSibling,
  218 + display = next.style.display;
  219 + next.style.display = display === "none" ? "block" : "none";
  220 + });
  221 +
  222 + addEvent(b, "dblclick", function( e ) {
  223 + var target = e && e.target ? e.target : window.event.srcElement;
  224 + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
  225 + target = target.parentNode;
  226 + }
  227 + if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
  228 + window.location = QUnit.url({ testNumber: test.testNumber });
  229 + }
  230 + });
  231 +
  232 + // `li` initialized at top of scope
  233 + li = id( this.id );
  234 + li.className = bad ? "fail" : "pass";
  235 + li.removeChild( li.firstChild );
  236 + a = li.firstChild;
  237 + li.appendChild( b );
  238 + li.appendChild ( a );
  239 + li.appendChild( ol );
  240 +
  241 + } else {
  242 + for ( i = 0; i < this.assertions.length; i++ ) {
  243 + if ( !this.assertions[i].result ) {
  244 + bad++;
  245 + config.stats.bad++;
  246 + config.moduleStats.bad++;
  247 + }
  248 + }
  249 + }
  250 +
  251 + runLoggingCallbacks( "testDone", QUnit, {
  252 + name: this.testName,
  253 + module: this.module,
  254 + failed: bad,
  255 + passed: this.assertions.length - bad,
  256 + total: this.assertions.length
  257 + });
  258 +
  259 + QUnit.reset();
  260 +
  261 + config.current = undefined;
  262 + },
  263 +
  264 + queue: function() {
  265 + var bad,
  266 + test = this;
  267 +
  268 + synchronize(function() {
  269 + test.init();
  270 + });
  271 + function run() {
  272 + // each of these can by async
  273 + synchronize(function() {
  274 + test.setup();
  275 + });
  276 + synchronize(function() {
  277 + test.run();
  278 + });
  279 + synchronize(function() {
  280 + test.teardown();
  281 + });
  282 + synchronize(function() {
  283 + test.finish();
  284 + });
  285 + }
  286 +
  287 + // `bad` initialized at top of scope
  288 + // defer when previous test run passed, if storage is available
  289 + bad = QUnit.config.reorder && defined.sessionStorage &&
  290 + +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
  291 +
  292 + if ( bad ) {
  293 + run();
  294 + } else {
  295 + synchronize( run, true );
  296 + }
  297 + }
  298 +};
  299 +
  300 +// Root QUnit object.
  301 +// `QUnit` initialized at top of scope
  302 +QUnit = {
  303 +
  304 + // call on start of module test to prepend name to all tests
  305 + module: function( name, testEnvironment ) {
  306 + config.currentModule = name;
  307 + config.currentModuleTestEnviroment = testEnvironment;
  308 + },
  309 +
  310 + asyncTest: function( testName, expected, callback ) {
  311 + if ( arguments.length === 2 ) {
  312 + callback = expected;
  313 + expected = null;
  314 + }
  315 +
  316 + QUnit.test( testName, expected, callback, true );
  317 + },
  318 +
  319 + test: function( testName, expected, callback, async ) {
  320 + var test,
  321 + name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
  322 +
  323 + if ( arguments.length === 2 ) {
  324 + callback = expected;
  325 + expected = null;
  326 + }
  327 +
  328 + if ( config.currentModule ) {
  329 + name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
  330 + }
  331 +
  332 + test = new Test({
  333 + name: name,
  334 + testName: testName,
  335 + expected: expected,
  336 + async: async,
  337 + callback: callback,
  338 + module: config.currentModule,
  339 + moduleTestEnvironment: config.currentModuleTestEnviroment,
  340 + stack: sourceFromStacktrace( 2 )
  341 + });
  342 +
  343 + if ( !validTest( test ) ) {
  344 + return;
  345 + }
  346 +
  347 + test.queue();
  348 + },
  349 +
  350 + // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
  351 + expect: function( asserts ) {
  352 + config.current.expected = asserts;
  353 + },
  354 +
  355 + start: function( count ) {
  356 + config.semaphore -= count || 1;
  357 + // don't start until equal number of stop-calls
  358 + if ( config.semaphore > 0 ) {
  359 + return;
  360 + }
  361 + // ignore if start is called more often then stop
  362 + if ( config.semaphore < 0 ) {
  363 + config.semaphore = 0;
  364 + }
  365 + // A slight delay, to avoid any current callbacks
  366 + if ( defined.setTimeout ) {
  367 + window.setTimeout(function() {
  368 + if ( config.semaphore > 0 ) {
  369 + return;
  370 + }
  371 + if ( config.timeout ) {
  372 + clearTimeout( config.timeout );
  373 + }
  374 +
  375 + config.blocking = false;
  376 + process( true );
  377 + }, 13);
  378 + } else {
  379 + config.blocking = false;
  380 + process( true );
  381 + }
  382 + },
  383 +
  384 + stop: function( count ) {
  385 + config.semaphore += count || 1;
  386 + config.blocking = true;
  387 +
  388 + if ( config.testTimeout && defined.setTimeout ) {
  389 + clearTimeout( config.timeout );
  390 + config.timeout = window.setTimeout(function() {
  391 + QUnit.ok( false, "Test timed out" );
  392 + config.semaphore = 1;
  393 + QUnit.start();
  394 + }, config.testTimeout );
  395 + }
  396 + }
  397 +};
  398 +
  399 +// Asssert helpers
  400 +// All of these must call either QUnit.push() or manually do:
  401 +// - runLoggingCallbacks( "log", .. );
  402 +// - config.current.assertions.push({ .. });
  403 +QUnit.assert = {
  404 + /**
  405 + * Asserts rough true-ish result.
  406 + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
  407 + */
  408 + ok: function( result, msg ) {
  409 + if ( !config.current ) {
  410 + throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
  411 + }
  412 + result = !!result;
  413 +
  414 + var source,
  415 + details = {
  416 + result: result,
  417 + message: msg
  418 + };
  419 +
  420 + msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
  421 + msg = "<span class='test-message'>" + msg + "</span>";
  422 +
  423 + if ( !result ) {
  424 + source = sourceFromStacktrace( 2 );
  425 + if ( source ) {
  426 + details.source = source;
  427 + msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
  428 + }
  429 + }
  430 + runLoggingCallbacks( "log", QUnit, details );
  431 + config.current.assertions.push({
  432 + result: result,
  433 + message: msg
  434 + });
  435 + },
  436 +
  437 + /**
  438 + * Assert that the first two arguments are equal, with an optional message.
  439 + * Prints out both actual and expected values.
  440 + * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
  441 + */
  442 + equal: function( actual, expected, message ) {
  443 + QUnit.push( expected == actual, actual, expected, message );
  444 + },
  445 +
  446 + notEqual: function( actual, expected, message ) {
  447 + QUnit.push( expected != actual, actual, expected, message );
  448 + },
  449 +
  450 + deepEqual: function( actual, expected, message ) {
  451 + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
  452 + },
  453 +
  454 + notDeepEqual: function( actual, expected, message ) {
  455 + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
  456 + },
  457 +
  458 + strictEqual: function( actual, expected, message ) {
  459 + QUnit.push( expected === actual, actual, expected, message );
  460 + },
  461 +
  462 + notStrictEqual: function( actual, expected, message ) {
  463 + QUnit.push( expected !== actual, actual, expected, message );
  464 + },
  465 +
  466 + raises: function( block, expected, message ) {
  467 + var actual,
  468 + ok = false;
  469 +
  470 + if ( typeof expected === "string" ) {
  471 + message = expected;
  472 + expected = null;
  473 + }
  474 +
  475 + config.current.ignoreGlobalErrors = true;
  476 + try {
  477 + block.call( config.current.testEnvironment );
  478 + } catch (e) {
  479 + actual = e;
  480 + }
  481 + config.current.ignoreGlobalErrors = false;
  482 +
  483 + if ( actual ) {
  484 + // we don't want to validate thrown error
  485 + if ( !expected ) {
  486 + ok = true;
  487 + // expected is a regexp
  488 + } else if ( QUnit.objectType( expected ) === "regexp" ) {
  489 + ok = expected.test( actual );
  490 + // expected is a constructor
  491 + } else if ( actual instanceof expected ) {
  492 + ok = true;
  493 + // expected is a validation function which returns true is validation passed
  494 + } else if ( expected.call( {}, actual ) === true ) {
  495 + ok = true;
  496 + }
  497 + }
  498 +
  499 + QUnit.push( ok, actual, null, message );
  500 + }
  501 +};
  502 +
  503 +// @deprecated: Kept assertion helpers in root for backwards compatibility
  504 +extend( QUnit, QUnit.assert );
  505 +
  506 +/**
  507 + * @deprecated: Kept for backwards compatibility
  508 + * next step: remove entirely
  509 + */
  510 +QUnit.equals = function() {
  511 + QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
  512 +};
  513 +QUnit.same = function() {
  514 + QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
  515 +};
  516 +
  517 +// We want access to the constructor's prototype
  518 +(function() {
  519 + function F() {}
  520 + F.prototype = QUnit;
  521 + QUnit = new F();
  522 + // Make F QUnit's constructor so that we can add to the prototype later
  523 + QUnit.constructor = F;
  524 +}());
  525 +
  526 +/**
  527 + * Config object: Maintain internal state
  528 + * Later exposed as QUnit.config
  529 + * `config` initialized at top of scope
  530 + */
  531 +config = {
  532 + // The queue of tests to run
  533 + queue: [],
  534 +
  535 + // block until document ready
  536 + blocking: true,
  537 +
  538 + // when enabled, show only failing tests
  539 + // gets persisted through sessionStorage and can be changed in UI via checkbox
  540 + hidepassed: false,
  541 +
  542 + // by default, run previously failed tests first
  543 + // very useful in combination with "Hide passed tests" checked
  544 + reorder: true,
  545 +
  546 + // by default, modify document.title when suite is done
  547 + altertitle: true,
  548 +
  549 + // when enabled, all tests must call expect()
  550 + requireExpects: false,
  551 +
  552 + urlConfig: [ "noglobals", "notrycatch" ],
  553 +
  554 + // logging callback queues
  555 + begin: [],
  556 + done: [],
  557 + log: [],
  558 + testStart: [],
  559 + testDone: [],
  560 + moduleStart: [],
  561 + moduleDone: []
  562 +};
  563 +
  564 +// Initialize more QUnit.config and QUnit.urlParams
  565 +(function() {
  566 + var i,
  567 + location = window.location || { search: "", protocol: "file:" },
  568 + params = location.search.slice( 1 ).split( "&" ),
  569 + length = params.length,
  570 + urlParams = {},
  571 + current;
  572 +
  573 + if ( params[ 0 ] ) {
  574 + for ( i = 0; i < length; i++ ) {
  575 + current = params[ i ].split( "=" );
  576 + current[ 0 ] = decodeURIComponent( current[ 0 ] );
  577 + // allow just a key to turn on a flag, e.g., test.html?noglobals
  578 + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
  579 + urlParams[ current[ 0 ] ] = current[ 1 ];
  580 + }
  581 + }
  582 +
  583 + QUnit.urlParams = urlParams;
  584 +
  585 + // String search anywhere in moduleName+testName
  586 + config.filter = urlParams.filter;
  587 +
  588 + // Exact match of the module name
  589 + config.module = urlParams.module;
  590 +
  591 + config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
  592 +
  593 + // Figure out if we're running the tests from a server or not
  594 + QUnit.isLocal = location.protocol === "file:";
  595 +}());
  596 +
  597 +// Export global variables, unless an 'exports' object exists,
  598 +// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
  599 +if ( typeof exports === "undefined" ) {
  600 + extend( window, QUnit );
  601 +
  602 + // Expose QUnit object
  603 + window.QUnit = QUnit;
  604 +}
  605 +
  606 +// Extend QUnit object,
  607 +// these after set here because they should not be exposed as global functions
  608 +extend( QUnit, {
  609 + config: config,
  610 +
  611 + // Initialize the configuration options
  612 + init: function() {
  613 + extend( config, {
  614 + stats: { all: 0, bad: 0 },
  615 + moduleStats: { all: 0, bad: 0 },
  616 + started: +new Date(),
  617 + updateRate: 1000,
  618 + blocking: false,
  619 + autostart: true,
  620 + autorun: false,
  621 + filter: "",
  622 + queue: [],
  623 + semaphore: 0
  624 + });
  625 +
  626 + var tests, banner, result,
  627 + qunit = id( "qunit" );
  628 +
  629 + if ( qunit ) {
  630 + qunit.innerHTML =
  631 + "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
  632 + "<h2 id='qunit-banner'></h2>" +
  633 + "<div id='qunit-testrunner-toolbar'></div>" +
  634 + "<h2 id='qunit-userAgent'></h2>" +
  635 + "<ol id='qunit-tests'></ol>";
  636 + }
  637 +
  638 + tests = id( "qunit-tests" );
  639 + banner = id( "qunit-banner" );
  640 + result = id( "qunit-testresult" );
  641 +
  642 + if ( tests ) {
  643 + tests.innerHTML = "";
  644 + }
  645 +
  646 + if ( banner ) {
  647 + banner.className = "";
  648 + }
  649 +
  650 + if ( result ) {
  651 + result.parentNode.removeChild( result );
  652 + }
  653 +
  654 + if ( tests ) {
  655 + result = document.createElement( "p" );
  656 + result.id = "qunit-testresult";
  657 + result.className = "result";
  658 + tests.parentNode.insertBefore( result, tests );
  659 + result.innerHTML = "Running...<br/>&nbsp;";
  660 + }
  661 + },
  662 +
  663 + // Resets the test setup. Useful for tests that modify the DOM.
  664 + // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
  665 + reset: function() {