Skip to content
Browse files

Merge branch 'master' of https://github.com/jquery/jquery-mobile

  • Loading branch information...
2 parents 012eaec + f1072b7 commit 8211e3bc5a5358b9982e8eaaa2e001004f7b017b levidehaan committed Aug 9, 2012
Showing with 1,857 additions and 1,269 deletions.
  1. +2 −1 css/structure/jquery.mobile.button.css
  2. +4 −2 css/structure/jquery.mobile.controlgroup.css
  3. +1 −1 css/structure/jquery.mobile.core.css
  4. +0 −1 docs/_assets/css/jqm-docs.css
  5. +3 −1 docs/lists/docs-lists.html
  6. +2 −1 docs/lists/lists-autodividers.html
  7. +1 −0 docs/lists/lists-search-filtertext.html
  8. +8 −6 docs/pages/popup/index.html
  9. +1 −0 docs/pages/popup/map.html
  10. +2 −2 docs/pages/popup/methods.html
  11. +4 −4 docs/pages/popup/options.html
  12. +2 −0 docs/pages/popup/popup-examples.css
  13. +1 −1 docs/pages/popup/popup-examples.js
  14. +1 −8 docs/pages/popup/popup-iframes.html
  15. +28 −23 external/qunit.css
  16. +1,148 −769 external/qunit.js
  17. +4 −1 js/events/orientationchange.js
  18. +6 −3 js/events/touch.js
  19. +1 −0 js/index.php
  20. +1 −1 js/jquery.mobile.init.js
  21. +37 −4 js/jquery.mobile.navigation.js
  22. +5 −5 js/jquery.mobile.navigation.pushstate.js
  23. +1 −1 js/jquery.mobile.support.js
  24. +1 −1 js/jquery.mobile.support.orientation.js
  25. +20 −0 js/jquery.mobile.support.touch.js
  26. +1 −1 js/transitions/flip.js
  27. +1 −1 js/transitions/flow.js
  28. +1 −1 js/transitions/pop.js
  29. +1 −1 js/transitions/slide.js
  30. +1 −1 js/transitions/slidedown.js
  31. +1 −1 js/transitions/slidefade.js
  32. +1 −1 js/transitions/slideup.js
  33. +1 −1 js/transitions/turn.js
  34. +2 −7 js/widgets/forms/slider.js
  35. +2 −6 js/widgets/forms/textinput.js
  36. +3 −2 js/widgets/listview.autodividers.js
  37. +24 −4 js/widgets/navbar.js
  38. +2 −2 js/widgets/popup.js
  39. +2 −2 tests/jquery.testHelper.js
  40. +4 −4 tests/unit/button-markup/buttonMarkup_core.js
  41. +9 −9 tests/unit/button/button_core.js
  42. +2 −2 tests/unit/checkboxradio/checkboxradio_core.js
  43. +1 −1 tests/unit/collapsible/collapsible_core.js
  44. +2 −2 tests/unit/controlgroup/controlgroup_core.js
  45. +31 −31 tests/unit/core/core.js
  46. +2 −2 tests/unit/core/core_scroll.js
  47. +3 −3 tests/unit/degrade-inputs/degradeInputs.js
  48. +23 −19 tests/unit/event/event_core.js
  49. +1 −1 tests/unit/field-contain/fieldContain_events.js
  50. +4 −4 tests/unit/fixed-toolbar/fixedToolbar.js
  51. +9 −9 tests/unit/init/init_core.js
  52. +4 −4 tests/unit/listview/index.html
  53. +50 −49 tests/unit/listview/listview_core.js
  54. +1 −1 tests/unit/listview/listview_nested.js
  55. +11 −11 tests/unit/loader/loader_core.js
  56. +4 −4 tests/unit/media/media_core.js
  57. +13 −2 tests/unit/navbar/navbar_core.js
  58. +49 −49 tests/unit/navigation/navigation_core.js
  59. +124 −111 tests/unit/navigation/navigation_helpers.js
  60. +1 −1 tests/unit/navigation/navigation_paths.js
  61. +1 −1 tests/unit/navigation/navigation_transitions.js
  62. +2 −2 tests/unit/page-sections/page_core.js
  63. +7 −7 tests/unit/page/page_core.js
  64. +4 −0 tests/unit/popup/index.html
  65. +76 −5 tests/unit/popup/popup_core.js
  66. +7 −7 tests/unit/select/select_cached.js
  67. +14 −14 tests/unit/select/select_core.js
  68. +3 −3 tests/unit/select/select_events.js
  69. +3 −3 tests/unit/select/select_native.js
  70. +7 −7 tests/unit/slider/slider_core.js
  71. +29 −23 tests/unit/slider/slider_events.js
  72. +14 −2 tests/unit/support/support_core.js
  73. +12 −6 tests/unit/textinput/textinput_core.js
  74. +3 −3 tests/unit/widget/widget_core.js
View
3 css/structure/jquery.mobile.button.css
@@ -1,10 +1,11 @@
.ui-btn { display: block; text-align: center; cursor:pointer; position: relative; margin: .5em 0; padding: 0; }
.ui-mini { margin-top: .25em; margin-bottom: .25em; }
.ui-btn-left, .ui-btn-right, .ui-input-clear, .ui-btn-inline,
-.ui-block-a > .ui-btn, .ui-block-b > .ui-btn, .ui-block-c > .ui-btn, .ui-block-d > .ui-btn, .ui-block-e > .ui-btn { margin-right: 5px; margin-left: 5px; }
+.ui-grid-a .ui-btn, .ui-grid-b .ui-btn, .ui-grid-c .ui-btn, .ui-grid-d .ui-btn, .ui-grid-e .ui-btn, .ui-grid-solo .ui-btn { margin-right: 5px; margin-left: 5px; }
.ui-btn-inner { font-size: 16px; padding: .6em 20px; min-width: .75em; display: block; position: relative; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; zoom: 1; }
.ui-btn input, .ui-btn button { z-index: 2; }
.ui-btn-left, .ui-btn-right, .ui-btn-inline { display: inline-block; vertical-align: middle; }
+.ui-mobile .ui-btn-left, .ui-mobile .ui-btn-right { margin: 0; } /* .ui-mobile to increase specificity level */
.ui-btn-block { display: block; }
.ui-header > .ui-btn,
View
6 css/structure/jquery.mobile.controlgroup.css
@@ -13,13 +13,15 @@
.ui-controlgroup .ui-btn-icon-notext { width: auto; height: auto; top: auto; }
.ui-controlgroup .ui-btn-icon-notext .ui-btn-inner { height: 20px; padding: .6em 20px .6em 20px }
.ui-controlgroup-horizontal .ui-btn-icon-notext .ui-btn-inner { width: 18px; }
-.ui-controlgroup.ui-mini .ui-btn-icon-notext .ui-btn-inner { height: 16px; padding: .55em 11px .5em 11px; }
+.ui-controlgroup.ui-mini .ui-btn-icon-notext .ui-btn-inner,
+.ui-header .ui-controlgroup .ui-btn-icon-notext .ui-btn-inner,
+.ui-footer .ui-controlgroup .ui-btn-icon-notext .ui-btn-inner { height: 16px; padding: .55em 11px .5em 11px; }
.ui-controlgroup .ui-btn-icon-notext .ui-btn-inner .ui-icon { position: absolute; top: 50%; right: 50%; margin: -9px -9px 0 0; }
.ui-controlgroup-horizontal .ui-controlgroup-controls:before,
.ui-controlgroup-horizontal .ui-controlgroup-controls:after { content: ""; display: table; }
.ui-controlgroup-horizontal .ui-controlgroup-controls:after { clear: both; }
-.ui-controlgroup-horizontal .ui-controlgroup-controls { display: inline-block; zoom: 1; }
+.ui-controlgroup-horizontal .ui-controlgroup-controls { display: inline-block; vertical-align: middle; zoom: 1; }
.ui-controlgroup-horizontal .ui-btn-inner { text-align: center; }
.ui-controlgroup-horizontal.ui-mini .ui-btn-inner { height: 16px; line-height: 16px; }
.ui-controlgroup-horizontal .ui-btn, .ui-controlgroup-horizontal .ui-select,
View
2 css/structure/jquery.mobile.core.css
@@ -4,7 +4,7 @@
.ui-mobile a img, .ui-mobile fieldset { border-width: 0; }
/* responsive page widths */
-.ui-mobile-viewport { margin: 0; overflow-x: visible; -webkit-text-size-adjust: none; -ms-text-size-adjust:none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
+.ui-mobile-viewport { margin: 0; overflow-x: visible; -webkit-text-size-adjust: 100%; -ms-text-size-adjust:none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
/* Issue #2066 */
body.ui-mobile-viewport,
div.ui-mobile-viewport { overflow-x: hidden; }
View
1 docs/_assets/css/jqm-docs.css
@@ -145,7 +145,6 @@ dd h4 { margin:15px 0 0 0; }
.localnav {
margin:0 0 20px 0;
- overflow:hidden;
}
.localnav li {
float:left;
View
4 docs/lists/docs-lists.html
@@ -81,7 +81,7 @@
<p>A listview can be configured to automatically generate dividers for its items. This is done by adding a <code>data-autodividers="true"</code> attribute to any listview.</p>
- <p>By default, the text used to create dividers is the uppercased first letter of either the item's link text (for linked lists) or the item's text (for read-only lists). Alternatively, if you are using formatted list items, you can specify divider text by setting the <code>autodividersSelector</code> option on the listview programmatically. For example, to add a custom selector to the element with <code>id="mylistview"</code>:</p>
+ <p>By default, the text used to create dividers is the uppercased first letter of the item's text. Alternatively you can specify divider text by setting the <code>autodividersSelector</code> option on the listview programmatically. For example, to add a custom selector to the element with <code>id="mylistview"</code>:</p>
<pre><code>
$("#mylistview").listview({
@@ -97,6 +97,8 @@
});
</code></pre>
+ <p>Note that if you are using formatted list items that contain a floating element (for example <code>ui-li-aside</code>), this element precedes its siblings regardless the order in your HTML markup. This results in the first character of the floating element being used as divider text. Therefore it is recommended to specify the divider text in this case.</p>
+
<p>If new list items are added to the list or removed from it, the dividers are <em>not</em> automatically updated: you should call <code>refresh()</code> on the listview to redraw the autodividers.</p>
<a href="lists-autodividers.html" data-role="button" data-icon="arrow-r" data-iconpos="right">Autodividers example</a>
View
3 docs/lists/lists-autodividers.html
@@ -59,7 +59,7 @@
<ul data-role="listview" data-theme="c" data-dividertheme="d">
<li data-role="list-divider">Listviews</li>
- <li><a href="docs-lists.html">List markup conventions</a></li>
+ <li><a href="docs-lists.html">List basics &amp; API</a></li>
<li><a href="lists-ul.html">Basic linked list</a></li>
<li><a href="lists-nested.html">Nested list</a></li>
<li><a href="lists-ol.html">Numbered list</a></li>
@@ -76,6 +76,7 @@
<li><a href="lists-search.html">Search filter bar</a></li>
<li><a href="lists-search-inset.html">Inset search filter bar</a></li>
<li><a href="lists-search-with-dividers.html">Search filter bar with dividers</a></li>
+ <li><a href="lists-search-filtertext.html">Search filter hidden data</a></li>
<li><a href="lists-readonly.html">Read-only lists</a></li>
<li><a href="lists-readonly-inset.html">Read-only inset lists</a></li>
View
1 docs/lists/lists-search-filtertext.html
@@ -60,6 +60,7 @@
<li><a href="lists-split.html">Split button list</a></li>
<li><a href="lists-divider.html">List dividers</a></li>
+ <li><a href="lists-autodividers.html">Autodividers</a></li>
<li><a href="lists-count.html">Count bubble</a></li>
<li><a href="lists-thumbnails.html">Thumbnails</a></li>
<li><a href="lists-icons.html">Icons</a></li>
View
14 docs/pages/popup/index.html
@@ -162,22 +162,22 @@ <h3 class="ui-title">Are you sure you want to delete this page?</h3>
<p>This plugin will autoinitialize on any page that contains a div with the attribute <code>data-role="popup"</code>. However, if needed you can directly call the <code>popup</code> plugin on any selector, just like any jQuery plugin and programmatically work with the popup <a href="options.html">options</a>, <a href="methods.html">methods</a>, and <a href="events.html">events</a> API:</p>
<pre><code>
-$('#myPopupDiv').popup();
+$( "#myPopupDiv" ).popup();
</code></pre>
<h2>Opening popups</h2>
<p>Using the markup-based configuration, when a link with the <code>data-rel="popup"</code> is tapped, the corresponding popup container with the id referenced in the <code>href</code> of the link will be shown. To open a popup programmatically, call popup with the <code>open</code> method on the popup container:</p>
<pre><code>
-$('#myPopupDiv').popup("open")
+$( "#myPopupDiv" ).popup( "open" )
</code></pre>
<h2>Closing popups</h2>
<p>Popups can be closed either by clicking outside the popup widget or by pressing the <code>Esc</code> key. Popups can also be closed via the <code>close</code> method:
<pre><code>
-$('#myPopupDiv').popup("close")
+$( "#myPopupDiv" ).popup( "close" )
</code></pre>
<p>To add an explicit close button to a popup, add a link with the role of button into the popup container with a <code>data-rel="back"</code> attribute which will close the popup when tapped. We have created helper classes to position buttons in the upper left (<code>ui-btn-left</code>) or upper right (<code>ui-btn-right</code>) of the popup but you may need to tweak these or add custom positioning styles depending on your design. We recommend adding standard content padding to the popup to make room for the buttons (see next section).</p>
@@ -300,7 +300,7 @@ <h3 class="ui-title">Are you sure you want to delete this page?</h3>
<p>I'm a simple popup.</p>
</div>
- <p>When you launch the popup from any of the buttons, the <code>data-transition</code> set on the button will be used. However, if you launch the popup programmatically, such as via <code>$("#transitionExample").popup("open")</code>, the <code>data-transition</code> attribute specified in the definition of the popup will be used if present.</p>
+ <p>When you launch the popup from any of the buttons, the <code>data-transition</code> set on the button will be used. However, if you launch the popup programmatically, such as via <code>$( "#transitionExample" ).popup( "open" )</code>, the <code>data-transition</code> attribute specified in the definition of the popup will be used if present.</p>
<h2>Theming the popup and overlay</h2>
@@ -341,8 +341,10 @@ <h3 class="ui-title">Are you sure you want to delete this page?</h3>
<p>The framework does not currently support chaining of popups so it's not possible to embed a link from one popup to another popup. All links with a <code>data-rel="popup"</code> inside a popup will not do anything at all.</p>
<p>This also means that custom select menus will not work inside popups, because they are themselves implemented using popups. If you place a select menu inside a popup, it will be rendered as a native select menu, even if you specify <code>data-native-menu="false"</code>.</p>
- <div align="right"><a href="popup-images.html" data-ajax="false" data-role="button" data-icon="arrow-r" data-iconpos="right" data-inline="false" align="right">Advanced popup examples</a>
- </div>
+ <br>
+ <a href="popup-images.html" data-ajax="false" data-role="button" data-inline="true" data-icon="arrow-r" data-iconpos="right">Scaling images</a>
+ <a href="popup-iframes.html" data-ajax="false" data-role="button" data-inline="true" data-icon="arrow-r" data-iconpos="right">Map + video iframes</a>
+ <a href="popup-panels.html" data-ajax="false" data-role="button" data-inline="true" data-icon="arrow-r" data-iconpos="right">Overlay panels</a>
</div><!--/content-primary -->
View
1 docs/pages/popup/map.html
@@ -18,6 +18,7 @@
<style>
html {
height: 100%;
+ overflow: hidden;
}
body {
margin: 0;
View
4 docs/pages/popup/methods.html
@@ -42,7 +42,7 @@
<dt><code>open( options )</code> display the popup using the specified options</dt>
<dd>
<pre><code>
-$('.selector').popup('open', options);
+$( ".selector" ).popup( "open", options);
</code></pre>
<p><code>options</code> is an object literal which may have any of the following properties:</p>
<table>
@@ -67,7 +67,7 @@
<dt><code>close</code> close an open popup</dt>
<dd>
<pre><code>
-$('.selector').popup('close');
+$( ".selector" ).popup( "close" );
</code></pre>
</dd>
</dl>
View
8 docs/pages/popup/options.html
@@ -44,7 +44,7 @@
<dd>
<p class="default">default: true</p>
<p>Sets whether to draw the popup with rounded corners. This option is also exposed as a data attribute: <code>data-corners=&quot;true&quot;</code></p>
- <pre><code>$('.selector').popup(<strong>{ corners: true }</strong>);</code></pre>
+ <pre><code>$( ".selector" ).popup(<strong>{ corners: true }</strong>);</code></pre>
</dd>
<dt><code>initSelector</code> <em>CSS selector string</em></dt>
@@ -61,7 +61,7 @@
<dd>
<p class="default">default: null</p>
<p>Sets the color scheme (swatch) for the popup background, which covers the entire window. If not explicitly set, the background will be transparent.</p>
- <pre><code>$('.selector').popup(<strong>{ overlayTheme: "a" }</strong>);</code></pre>
+ <pre><code>$( ".selector" ).popup(<strong>{ overlayTheme: "a" }</strong>);</code></pre>
<p>This option is also exposed as a data attribute: <code>data-overlay-theme=&quot;a&quot;</code></p>
</dd>
@@ -87,15 +87,15 @@
<dd>
<p class="default">default: true</p>
<p>Sets whether to draw a shadow around the popup. This option is also exposed as a data attribute: <code>data-shadow=&quot;true&quot;</code></p>
- <pre><code>$('.selector').popup(<strong>{ shadow: true }</strong>);</code></pre>
+ <pre><code>$( ".selector" ).popup(<strong>{ shadow: true }</strong>);</code></pre>
</dd>
<dt><code>theme</code> <em>string</em></dt>
<dd>
<p class="default">default: null</p>
<p>Sets the color scheme (swatch) for the popup contents. Unless explicitly set to <code>'none'</code>, the
theme for the popup will be assigned the first time the popup is shown by inheriting the page theme or, failing that, by the hard-coded value <code>'c'</code>. If you set it to 'none', the popup will not have any theme at all, and will be transparent.</p>
- <pre><code>$('.selector').popup(<strong>{ theme: "a" }</strong>);</code></pre>
+ <pre><code>$( ".selector" ).popup(<strong>{ theme: "a" }</strong>);</code></pre>
<p>This option is also exposed as a data attribute: <code>data-theme=&quot;a&quot;</code></p>
</dd>
View
2 docs/pages/popup/popup-examples.css
@@ -1,3 +1,5 @@
+iframe { border: none; }
+
#popupPanel-popup {
right: 0 !important;
left: auto !important;
View
2 docs/pages/popup/popup-examples.js
@@ -92,7 +92,7 @@ $( document ).on( "pageinit", function() {
});
$( "#popupPanel button" ).on( "click", function() {
- $( this ).closest( ".ui-popup" ).popup('close');
+ $( "#popupPanel" ).popup('close');
});
});
View
9 docs/pages/popup/popup-iframes.html
@@ -159,20 +159,13 @@
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map( document.getElementById( "map_canvas" ), myOptions );
- var marker = new google.maps.Marker({
- position: myLatlng,
- map: map,
- title: "University of Westminster"
- });
- google.maps.event.addListener( map, 'bounds_changed', function() {
- map.panTo( myLatlng );
- });
}
&lt;/script&gt;
&lt;script src="http://maps.google.com/maps/api/js?sensor=false"&gt;&lt;/script&gt;
&lt;style&gt;
html {
height: 100%;
+ overflow: hidden;
}
body {
margin: 0;
View
51 external/qunit.css
@@ -1,9 +1,9 @@
/**
- * QUnit - A JavaScript Unit Testing Framework
+ * QUnit v1.9.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
- * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
@@ -38,10 +38,10 @@
line-height: 1em;
font-weight: normal;
- border-radius: 15px 15px 0 0;
- -moz-border-radius: 15px 15px 0 0;
- -webkit-border-top-right-radius: 15px;
- -webkit-border-top-left-radius: 15px;
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-top-right-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
}
#qunit-header a {
@@ -54,6 +54,11 @@
color: #fff;
}
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+ padding: 0 .5em 0 .1em;
+}
+
#qunit-banner {
height: 5px;
}
@@ -108,13 +113,9 @@
background-color: #fff;
- border-radius: 15px;
- -moz-border-radius: 15px;
- -webkit-border-radius: 15px;
-
- box-shadow: inset 0px 2px 13px #999;
- -moz-box-shadow: inset 0px 2px 13px #999;
- -webkit-box-shadow: inset 0px 2px 13px #999;
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
}
#qunit-tests table {
@@ -157,8 +158,7 @@
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
- margin: 0.5em;
- padding: 0.4em 0.5em 0.4em 0.5em;
+ padding: 5px;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
@@ -167,9 +167,9 @@
/*** Passing Styles */
#qunit-tests li li.pass {
- color: #5E740B;
+ color: #3c510c;
background-color: #fff;
- border-left: 26px solid #C6E746;
+ border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
@@ -185,15 +185,15 @@
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
- border-left: 26px solid #EE5757;
+ border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
- border-radius: 0 0 15px 15px;
- -moz-border-radius: 0 0 15px 15px;
- -webkit-border-bottom-right-radius: 15px;
- -webkit-border-bottom-left-radius: 15px;
+ border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
@@ -216,16 +216,21 @@
border-bottom: 1px solid white;
}
+#qunit-testresult .module-name {
+ font-weight: bold;
+}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
+ width: 1000px;
+ height: 1000px;
}
[data-nstest-role='page'], [data-nstest-role='dialog'] {
position: absolute !important;
top: -10000px !important;
-}
+}
View
1,917 external/qunit.js
@@ -1,77 +1,96 @@
/**
- * QUnit - A JavaScript Unit Testing Framework
+ * QUnit v1.9.0 - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
- * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
-(function(window) {
+(function( window ) {
-var defined = {
+var QUnit,
+ config,
+ onErrorFnPrev,
+ testId = 0,
+ fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
+ var x = "qunit-test-string";
try {
- return !!sessionStorage.getItem;
- } catch(e) {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch( e ) {
return false;
}
- })()
+ }())
};
-var testId = 0;
-
-var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
- this.name = name;
- this.testName = testName;
- this.expected = expected;
- this.testEnvironmentArg = testEnvironmentArg;
- this.async = async;
- this.callback = callback;
+function Test( settings ) {
+ extend( this, settings );
this.assertions = [];
-};
+ this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
Test.prototype = {
init: function() {
- var tests = id("qunit-tests");
- if (tests) {
- var b = document.createElement("strong");
- b.innerHTML = "Running " + this.name;
- var li = document.createElement("li");
- li.appendChild( b );
- li.className = "running";
- li.id = this.id = "test-output" + testId++;
+ var a, b, li,
+ tests = id( "qunit-tests" );
+
+ if ( tests ) {
+ b = document.createElement( "strong" );
+ b.innerHTML = this.name;
+
+ // `a` initialized at top of scope
+ a = document.createElement( "a" );
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ testNumber: this.testNumber });
+
+ li = document.createElement( "li" );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.className = "running";
+ li.id = this.id = "qunit-test-output" + testId++;
+
tests.appendChild( li );
}
},
setup: function() {
- if (this.module != config.previousModule) {
+ if ( this.module !== config.previousModule ) {
if ( config.previousModule ) {
- runLoggingCallbacks('moduleDone', QUnit, {
+ runLoggingCallbacks( "moduleDone", QUnit, {
name: config.previousModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
- } );
+ });
}
config.previousModule = this.module;
config.moduleStats = { all: 0, bad: 0 };
- runLoggingCallbacks( 'moduleStart', QUnit, {
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ } else if ( config.autorun ) {
+ runLoggingCallbacks( "moduleStart", QUnit, {
name: this.module
- } );
+ });
}
config.current = this;
+
this.testEnvironment = extend({
setup: function() {},
teardown: function() {}
- }, this.moduleTestEnvironment);
- if (this.testEnvironmentArg) {
- extend(this.testEnvironment, this.testEnvironmentArg);
- }
+ }, this.moduleTestEnvironment );
- runLoggingCallbacks( 'testStart', QUnit, {
+ runLoggingCallbacks( "testStart", QUnit, {
name: this.testName,
module: this.module
});
@@ -80,67 +99,92 @@ Test.prototype = {
// TODO why??
QUnit.current_testEnvironment = this.testEnvironment;
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ if ( config.notrycatch ) {
+ this.testEnvironment.setup.call( this.testEnvironment );
+ return;
+ }
try {
- if ( !config.pollution ) {
- saveGlobal();
- }
-
- this.testEnvironment.setup.call(this.testEnvironment);
- } catch(e) {
- QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
+ this.testEnvironment.setup.call( this.testEnvironment );
+ } catch( e ) {
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
}
},
run: function() {
+ config.current = this;
+
+ var running = id( "qunit-testresult" );
+
+ if ( running ) {
+ running.innerHTML = "Running: <br/>" + this.name;
+ }
+
if ( this.async ) {
QUnit.stop();
}
if ( config.notrycatch ) {
- this.callback.call(this.testEnvironment);
+ this.callback.call( this.testEnvironment, QUnit.assert );
return;
}
+
try {
- this.callback.call(this.testEnvironment);
- } catch(e) {
- fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
- QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ } catch( e ) {
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
- start();
+ QUnit.start();
}
}
},
teardown: function() {
- try {
- this.testEnvironment.teardown.call(this.testEnvironment);
- checkPollution();
- } catch(e) {
- QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
+ config.current = this;
+ if ( config.notrycatch ) {
+ this.testEnvironment.teardown.call( this.testEnvironment );
+ return;
+ } else {
+ try {
+ this.testEnvironment.teardown.call( this.testEnvironment );
+ } catch( e ) {
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+ }
}
+ checkPollution();
},
finish: function() {
- if ( this.expected && this.expected != this.assertions.length ) {
- QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
+ config.current = this;
+ if ( config.requireExpects && this.expected == null ) {
+ QUnit.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 ) {
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+ } else if ( this.expected == null && !this.assertions.length ) {
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
}
- var good = 0, bad = 0,
- tests = id("qunit-tests");
+ var assertion, a, b, i, li, ol,
+ test = this,
+ good = 0,
+ bad = 0,
+ tests = id( "qunit-tests" );
config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length;
if ( tests ) {
- var ol = document.createElement("ol");
+ ol = document.createElement( "ol" );
- for ( var i = 0; i < this.assertions.length; i++ ) {
- var assertion = this.assertions[i];
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ assertion = this.assertions[i];
- var li = document.createElement("li");
+ li = document.createElement( "li" );
li.className = assertion.result ? "pass" : "fail";
- li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
ol.appendChild( li );
if ( assertion.result ) {
@@ -154,49 +198,48 @@ Test.prototype = {
// store result when possible
if ( QUnit.config.reorder && defined.sessionStorage ) {
- if (bad) {
- sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
} else {
- sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
}
}
- if (bad == 0) {
+ if ( bad === 0 ) {
ol.style.display = "none";
}
- var b = document.createElement("strong");
+ // `b` initialized at top of scope
+ b = document.createElement( "strong" );
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
- var a = document.createElement("a");
- a.innerHTML = "Rerun";
- a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
-
addEvent(b, "click", function() {
var next = b.nextSibling.nextSibling,
display = next.style.display;
next.style.display = display === "none" ? "block" : "none";
});
- addEvent(b, "dblclick", function(e) {
+ addEvent(b, "dblclick", function( e ) {
var target = e && e.target ? e.target : window.event.srcElement;
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
target = target.parentNode;
}
if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
- window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+ window.location = QUnit.url({ testNumber: test.testNumber });
}
});
- var li = id(this.id);
+ // `li` initialized at top of scope
+ li = id( this.id );
li.className = bad ? "fail" : "pass";
li.removeChild( li.firstChild );
+ a = li.firstChild;
li.appendChild( b );
- li.appendChild( a );
+ li.appendChild ( a );
li.appendChild( ol );
} else {
- for ( var i = 0; i < this.assertions.length; i++ ) {
+ for ( i = 0; i < this.assertions.length; i++ ) {
if ( !this.assertions[i].result ) {
bad++;
config.stats.bad++;
@@ -205,23 +248,23 @@ Test.prototype = {
}
}
- try {
- QUnit.reset();
- } catch(e) {
- fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
- }
-
- runLoggingCallbacks( 'testDone', QUnit, {
+ runLoggingCallbacks( "testDone", QUnit, {
name: this.testName,
module: this.module,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length
- } );
+ });
+
+ QUnit.reset();
+
+ config.current = undefined;
},
queue: function() {
- var test = this;
+ var bad,
+ test = this;
+
synchronize(function() {
test.init();
});
@@ -240,214 +283,288 @@ Test.prototype = {
test.finish();
});
}
+
+ // `bad` initialized at top of scope
// defer when previous test run passed, if storage is available
- var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
- if (bad) {
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+ if ( bad ) {
run();
} else {
- synchronize(run);
- };
+ synchronize( run, true );
+ }
}
-
};
-var QUnit = {
+// 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) {
+ module: function( name, testEnvironment ) {
config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment;
},
- asyncTest: function(testName, expected, callback) {
+ asyncTest: function( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
- expected = 0;
+ expected = null;
}
- QUnit.test(testName, expected, callback, true);
+ QUnit.test( testName, expected, callback, true );
},
- test: function(testName, expected, callback, async) {
- var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
+ test: function( testName, expected, callback, async ) {
+ var test,
+ name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
- // is 2nd argument a testEnvironment?
- if ( expected && typeof expected === 'object') {
- testEnvironmentArg = expected;
- expected = null;
- }
if ( config.currentModule ) {
- name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
+ name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
}
- if ( !validTest(config.currentModule + ": " + testName) ) {
+ test = new Test({
+ name: name,
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback,
+ module: config.currentModule,
+ moduleTestEnvironment: config.currentModuleTestEnviroment,
+ stack: sourceFromStacktrace( 2 )
+ });
+
+ if ( !validTest( test ) ) {
return;
}
- var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
- test.module = config.currentModule;
- test.moduleTestEnvironment = config.currentModuleTestEnviroment;
test.queue();
},
- /**
- * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
- */
- expect: function(asserts) {
+ // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
config.current.expected = asserts;
},
+ start: function( count ) {
+ config.semaphore -= count || 1;
+ // don't start until equal number of stop-calls
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ // ignore if start is called more often then stop
+ if ( config.semaphore < 0 ) {
+ config.semaphore = 0;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ window.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 = window.setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout );
+ }
+ }
+};
+
+// Asssert helpers
+// All of these must call either QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+QUnit.assert = {
/**
- * Asserts true.
+ * Asserts rough true-ish result.
+ * @name ok
+ * @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
- ok: function(a, msg) {
- a = !!a;
- var details = {
- result: a,
- message: msg
- };
- msg = escapeInnerText(msg);
- runLoggingCallbacks( 'log', QUnit, details );
+ ok: function( result, msg ) {
+ if ( !config.current ) {
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+ result = !!result;
+
+ var source,
+ details = {
+ result: result,
+ message: msg
+ };
+
+ msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
+ msg = "<span class='test-message'>" + msg + "</span>";
+
+ if ( !result ) {
+ source = sourceFromStacktrace( 2 );
+ if ( source ) {
+ details.source = source;
+ msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+ }
+ }
+ runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
- result: a,
+ result: result,
message: msg
});
},
/**
- * Checks that the first two arguments are equal, with an optional message.
+ * Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values.
- *
- * Prefered to ok( actual == expected, message )
- *
- * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
- *
- * @param Object actual
- * @param Object expected
- * @param String message (optional)
+ * @name equal
+ * @function
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/
- equal: function(actual, expected, message) {
- QUnit.push(expected == actual, actual, expected, message);
+ equal: function( actual, expected, message ) {
+ QUnit.push( expected == actual, actual, expected, message );
},
- notEqual: function(actual, expected, message) {
- QUnit.push(expected != actual, actual, expected, message);
+ /**
+ * @name notEqual
+ * @function
+ */
+ notEqual: function( actual, expected, message ) {
+ QUnit.push( expected != actual, actual, expected, message );
},
- deepEqual: function(actual, expected, message) {
- QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
+ /**
+ * @name deepEqual
+ * @function
+ */
+ deepEqual: function( actual, expected, message ) {
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
- notDeepEqual: function(actual, expected, message) {
- QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
+ /**
+ * @name notDeepEqual
+ * @function
+ */
+ notDeepEqual: function( actual, expected, message ) {
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
- strictEqual: function(actual, expected, message) {
- QUnit.push(expected === actual, actual, expected, message);
+ /**
+ * @name strictEqual
+ * @function
+ */
+ strictEqual: function( actual, expected, message ) {
+ QUnit.push( expected === actual, actual, expected, message );
},
- notStrictEqual: function(actual, expected, message) {
- QUnit.push(expected !== actual, actual, expected, message);
+ /**
+ * @name notStrictEqual
+ * @function
+ */
+ notStrictEqual: function( actual, expected, message ) {
+ QUnit.push( expected !== actual, actual, expected, message );
},
- raises: function(block, expected, message) {
- var actual, ok = false;
+ throws: function( block, expected, message ) {
+ var actual,
+ ok = false;
- if (typeof expected === 'string') {
+ // 'expected' is optional
+ if ( typeof expected === "string" ) {
message = expected;
expected = null;
}
+ config.current.ignoreGlobalErrors = true;
try {
- block();
+ block.call( config.current.testEnvironment );
} catch (e) {
actual = e;
}
+ config.current.ignoreGlobalErrors = false;
- if (actual) {
+ if ( actual ) {
// we don't want to validate thrown error
- if (!expected) {
+ if ( !expected ) {
ok = true;
// expected is a regexp
- } else if (QUnit.objectType(expected) === "regexp") {
- ok = expected.test(actual);
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
+ ok = expected.test( actual );
// expected is a constructor
- } else if (actual instanceof expected) {
+ } else if ( actual instanceof expected ) {
ok = true;
// expected is a validation function which returns true is validation passed
- } else if (expected.call({}, actual) === true) {
+ } else if ( expected.call( {}, actual ) === true ) {
ok = true;
}
- }
- QUnit.ok(ok, message);
- },
-
- start: function(count) {
- config.semaphore -= count || 1;
- if (config.semaphore > 0) {
- // don't start until equal number of stop-calls
- return;
- }
- if (config.semaphore < 0) {
- // ignore if start is called more often then stop
- config.semaphore = 0;
- }
- // A slight delay, to avoid any current callbacks
- if ( defined.setTimeout ) {
- window.setTimeout(function() {
- if (config.semaphore > 0) {
- return;
- }
- if ( config.timeout ) {
- clearTimeout(config.timeout);
- }
-
- config.blocking = false;
- process();
- }, 13);
+ QUnit.push( ok, actual, null, message );
} else {
- config.blocking = false;
- process();
+ QUnit.pushFailure( message, null, 'No exception was thrown.' );
}
- },
+ }
+};
- stop: function(count) {
- config.semaphore += count || 1;
- config.blocking = true;
+/**
+ * @deprecate since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility
+ */
+extend( QUnit, QUnit.assert );
- if ( config.testTimeout && defined.setTimeout ) {
- clearTimeout(config.timeout);
- config.timeout = window.setTimeout(function() {
- QUnit.ok( false, "Test timed out" );
- config.semaphore = 1;
- QUnit.start();
- }, config.testTimeout);
- }
- }
+/**
+ * @deprecated since 1.9.0
+ * Kept global "raises()" for backwards compatibility
+ */
+QUnit.raises = QUnit.assert.throws;
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.equals = function() {
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
};
-//We want access to the constructor's prototype
+// We want access to the constructor's prototype
(function() {
- function F(){};
+ function F() {}
F.prototype = QUnit;
QUnit = new F();
- //Make F QUnit's constructor so that we can add to the prototype later
+ // Make F QUnit's constructor so that we can add to the prototype later
QUnit.constructor = F;
-})();
+}());
-// Backwards compatibility, deprecated
-QUnit.equals = QUnit.equal;
-QUnit.same = QUnit.deepEqual;
-
-// Maintain internal state
-var config = {
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
// The queue of tests to run
queue: [],
@@ -465,9 +582,25 @@ var config = {
// by default, modify document.title when suite is done
altertitle: true,
- urlConfig: ['noglobals', 'notrycatch'],
+ // when enabled, all tests must call expect()
+ requireExpects: false,
+
+ // add checkboxes that are persisted in the query-string
+ // when enabled, the id is set to `true` as a `QUnit.config` property
+ urlConfig: [
+ {
+ 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."
+ },
+ {
+ 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."
+ }
+ ],
- //logging callback queues
+ // logging callback queues
begin: [],
done: [],
log: [],
@@ -477,16 +610,17 @@ var config = {
moduleDone: []
};
-// Load paramaters
+// Initialize more QUnit.config and QUnit.urlParams
(function() {
- var location = window.location || { search: "", protocol: "file:" },
+ var i,
+ location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ),
length = params.length,
urlParams = {},
current;
if ( params[ 0 ] ) {
- for ( var i = 0; i < length; i++ ) {
+ for ( i = 0; i < length; i++ ) {
current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] );
// allow just a key to turn on a flag, e.g., test.html?noglobals
@@ -496,32 +630,39 @@ var config = {
}
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 = parseInt( urlParams.testNumber, 10 ) || null;
+
// Figure out if we're running the tests from a server or not
- QUnit.isLocal = !!(location.protocol === 'file:');
-})();
+ QUnit.isLocal = location.protocol === "file:";
+}());
-// Expose the API as global variables, unless an 'exports'
-// object exists, in that case we assume we're in CommonJS
-if ( typeof exports === "undefined" || typeof require === "undefined" ) {
- extend(window, QUnit);
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+ extend( window, QUnit );
+
+ // Expose QUnit object
window.QUnit = QUnit;
-} else {
- extend(exports, QUnit);
- exports.QUnit = QUnit;
}
-// define these after exposing globals to keep them in these QUnit namespace only
-extend(QUnit, {
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
config: config,
// Initialize the configuration options
init: function() {
- extend(config, {
+ extend( config, {
stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 },
- started: +new Date,
+ started: +new Date(),
updateRate: 1000,
blocking: false,
autostart: true,
@@ -531,9 +672,21 @@ extend(QUnit, {
semaphore: 0
});
- var tests = id( "qunit-tests" ),
- banner = id( "qunit-banner" ),
- result = id( "qunit-testresult" );
+ var tests, banner, result,
+ qunit = id( "qunit" );
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
+ "<h2 id='qunit-banner'></h2>" +
+ "<div id='qunit-testrunner-toolbar'></div>" +
+ "<h2 id='qunit-userAgent'></h2>" +
+ "<ol id='qunit-tests'></ol>";
+ }
+
+ tests = id( "qunit-tests" );
+ banner = id( "qunit-banner" );
+ result = id( "qunit-testresult" );
if ( tests ) {
tests.innerHTML = "";
@@ -552,43 +705,36 @@ extend(QUnit, {
result.id = "qunit-testresult";
result.className = "result";
tests.parentNode.insertBefore( result, tests );
- result.innerHTML = 'Running...<br/>&nbsp;';
+ result.innerHTML = "Running...<br/>&nbsp;";
}
},
- /**
- * Resets the test setup. Useful for tests that modify the DOM.
- *
- * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
- */
+ // Resets the test setup. Useful for tests that modify the DOM.
+ // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
reset: function() {
+ var fixture;
+
if ( window.jQuery ) {
jQuery( "#qunit-fixture" ).html( config.fixture );
} else {
- var main = id( 'qunit-fixture' );
- if ( main ) {
- main.innerHTML = config.fixture;
+ fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
}
}
},
- /**
- * Trigger an event on an element.
- *
- * @example triggerEvent( document.body, "click" );
- *
- * @param DOMElement elem
- * @param String type
- */
+ // Trigger an event on an element.
+ // @example triggerEvent( document.body, "click" );
triggerEvent: function( elem, type, event ) {
if ( document.createEvent ) {
- event = document.createEvent("MouseEvents");
+ event = document.createEvent( "MouseEvents" );
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
- elem.dispatchEvent( event );
+ elem.dispatchEvent( event );
} else if ( elem.fireEvent ) {
- elem.fireEvent("on"+type);
+ elem.fireEvent( "on" + type );
}
},
@@ -598,78 +744,126 @@ extend(QUnit, {
},
objectType: function( obj ) {
- if (typeof obj === "undefined") {
+ if ( typeof obj === "undefined" ) {
return "undefined";
-
// consider: typeof null === object
}
- if (obj === null) {
+ if ( obj === null ) {
return "null";
}
- var type = Object.prototype.toString.call( obj )
- .match(/^\[object\s(.*)\]$/)[1] || '';
+ var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
- switch (type) {
- case 'Number':
- if (isNaN(obj)) {
- return "nan";
- } else {
- return "number";
- }
- case 'String':
- case 'Boolean':
- case 'Array':
- case 'Date':
- case 'RegExp':
- case 'Function':
- return type.toLowerCase();
+ switch ( type ) {
+ case "Number":
+ if ( isNaN(obj) ) {
+ return "nan";
+ }
+ return "number";
+ case "String":
+ case "Boolean":
+ case "Array":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ return type.toLowerCase();
}
- if (typeof obj === "object") {
- return "object";
+ if ( typeof obj === "object" ) {
+ return "object";
}
return undefined;
},
- push: function(result, actual, expected, message) {
- var details = {
- result: result,
- message: message,
- actual: actual,
- expected: expected
- };
+ push: function( result, actual, expected, message ) {
+ if ( !config.current ) {
+ throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+ }
+
+ var output, source,
+ details = {
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
+ message = "<span class='test-message'>" + message + "</span>";
+ output = message;
+
+ if ( !result ) {
+ expected = escapeInnerText( QUnit.jsDump.parse(expected) );
+ actual = escapeInnerText( QUnit.jsDump.parse(actual) );
+ output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
+
+ if ( actual != expected ) {
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
+ output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
+ }
- message = escapeInnerText(message) || (result ? "okay" : "failed");
- message = '<span class="test-message">' + message + "</span>";
- expected = escapeInnerText(QUnit.jsDump.parse(expected));
- actual = escapeInnerText(QUnit.jsDump.parse(actual));
- var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
- if (actual != expected) {
- output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
- output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
- }
- if (!result) {
- var source = sourceFromStacktrace();
- if (source) {
+ source = sourceFromStacktrace();
+
+ if ( source ) {
details.source = source;
- output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>';
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
}
+
+ output += "</table>";
}
- output += "</table>";
- runLoggingCallbacks( 'log', QUnit, details );
+ runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
result: !!result,
message: output
});
},
+ pushFailure: function( message, source, actual ) {
+ if ( !config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+
+ var output,
+ details = {
+ result: false,
+ message: message
+ };
+
+ message = escapeInnerText( message ) || "error";
+ message = "<span class='test-message'>" + message + "</span>";
+ output = message;
+
+ output += "<table>";
+
+ if ( actual ) {
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
+ }
+
+ if ( source ) {
+ details.source = source;
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+ }
+
+ output += "</table>";
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: false,
+ message: output
+ });
+ },
+
url: function( params ) {
params = extend( extend( {}, QUnit.urlParams ), params );
- var querystring = "?",
- key;
+ var key,
+ querystring = "?";
+
for ( key in params ) {
+ if ( !hasOwn.call( params, key ) ) {
+ continue;
+ }
querystring += encodeURIComponent( key ) + "=" +
encodeURIComponent( params[ key ] ) + "&";
}
@@ -679,141 +873,215 @@ extend(QUnit, {
extend: extend,
id: id,
addEvent: addEvent
+ // load, equiv, jsDump, diff: Attached later
});
-//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
-//Doing this allows us to tell if the following methods have been overwritten on the actual
-//QUnit object, which is a deprecated way of using the callbacks.
-extend(QUnit.constructor.prototype, {
+/**
+ * @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, {
+
// Logging callbacks; all receive a single argument with the listed properties
// run test/logs.html for any related changes
- begin: registerLoggingCallback('begin'),
+ begin: registerLoggingCallback( "begin" ),
+
// done: { failed, passed, total, runtime }
- done: registerLoggingCallback('done'),
+ done: registerLoggingCallback( "done" ),
+
// log: { result, actual, expected, message }
- log: registerLoggingCallback('log'),
+ log: registerLoggingCallback( "log" ),
+
// testStart: { name }
- testStart: registerLoggingCallback('testStart'),
+ testStart: registerLoggingCallback( "testStart" ),
+
// testDone: { name, failed, passed, total }
- testDone: registerLoggingCallback('testDone'),
+ testDone: registerLoggingCallback( "testDone" ),
+
// moduleStart: { name }
- moduleStart: registerLoggingCallback('moduleStart'),
+ moduleStart: registerLoggingCallback( "moduleStart" ),
+
// moduleDone: { name, failed, passed, total }
- moduleDone: registerLoggingCallback('moduleDone')
+ moduleDone: registerLoggingCallback( "moduleDone" )
});
if ( typeof document === "undefined" || document.readyState === "complete" ) {
config.autorun = true;
}
QUnit.load = function() {
- runLoggingCallbacks( 'begin', QUnit, {} );
+ runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue
- var oldconfig = extend({}, config);
+ var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes,
+ urlConfigHtml = "",
+ oldconfig = extend( {}, config );
+
QUnit.init();
extend(config, oldconfig);
config.blocking = false;
- var urlConfigHtml = '', len = config.urlConfig.length;
- for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) {
- config[val] = QUnit.urlParams[val];
- urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>';
+ len = config.urlConfig.length;
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[i];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val,
+ tooltip: "[no tooltip available]"
+ };
+ }
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
}
- var userAgent = id("qunit-userAgent");
+ // `userAgent` initialized at top of scope
+ userAgent = id( "qunit-userAgent" );
if ( userAgent ) {
userAgent.innerHTML = navigator.userAgent;
}
- var banner = id("qunit-header");
+
+ // `banner` initialized at top of scope
+ banner = id( "qunit-header" );
if ( banner ) {
- banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml;
- addEvent( banner, "change", function( event ) {
- var params = {};
- params[ event.target.name ] = event.target.checked ? true : undefined;
- window.location = QUnit.url( params );
- });
+ banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
}
- var toolbar = id("qunit-testrunner-toolbar");
+ // `toolbar` initialized at top of scope
+ toolbar = id( "qunit-testrunner-toolbar" );
if ( toolbar ) {
- var filter = document.createElement("input");
+ // `filter` initialized at top of scope
+ filter = document.createElement( "input" );
filter.type = "checkbox";
filter.id = "qunit-filter-pass";
+
addEvent( filter, "click", function() {
- var ol = document.getElementById("qunit-tests");
+ var tmp,
+ ol = document.getElementById( "qunit-tests" );
+
if ( filter.checked ) {
ol.className = ol.className + " hidepass";
} else {
- var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
- ol.className = tmp.replace(/ hidepass /, " ");
+ tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace( / hidepass /, " " );
}
if ( defined.sessionStorage ) {
if (filter.checked) {
- sessionStorage.setItem("qunit-filter-passed-tests", "true");
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
} else {
- sessionStorage.removeItem("qunit-filter-passed-tests");
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
}
}
});
- if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
+
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
filter.checked = true;
- var ol = document.getElementById("qunit-tests");
+ // `ol` initialized at top of scope
+ ol = document.getElementById( "qunit-tests" );
ol.className = ol.className + " hidepass";
}
toolbar.appendChild( filter );
- var label = document.createElement("label");
- label.setAttribute("for", "qunit-filter-pass");
+ // `label` initialized at top of scope
+ label = document.createElement( "label" );
+ label.setAttribute( "for", "qunit-filter-pass" );
+ label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests";
toolbar.appendChild( label );
+
+ urlConfigCheckboxes = document.createElement( 'span' );
+ urlConfigCheckboxes.innerHTML = urlConfigHtml;
+ addEvent( urlConfigCheckboxes, "change", function( event ) {
+ var params = {};
+ params[ event.target.name ] = event.target.checked ? true : undefined;
+ window.location = QUnit.url( params );
+ });
+ toolbar.appendChild( urlConfigCheckboxes );
}
- var main = id('qunit-fixture');
+ // `main` initialized at top of scope
+ main = id( "qunit-fixture" );
if ( main ) {
config.fixture = main.innerHTML;
}
- if (config.autostart) {
+ if ( config.autostart ) {
QUnit.start();
}
};
-addEvent(window, "load", QUnit.load);
+addEvent( window, "load", QUnit.load );
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will surpress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+ var ret = false;
+ if ( onErrorFnPrev ) {
+ ret = onErrorFnPrev( error, filePath, linerNr );
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not surpressed.
+ if ( ret !== true ) {
+ if ( QUnit.config.current ) {
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
+ return true;
+ }
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ } else {
+ QUnit.test( "global failure", function() {
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ });
+ }
+ return false;
+ }
+
+ return ret;
+};
function done() {
config.autorun = true;
// Log the last module results
if ( config.currentModule ) {
- runLoggingCallbacks( 'moduleDone', QUnit, {
+ runLoggingCallbacks( "moduleDone", QUnit, {
name: config.currentModule,
failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all
- } );
+ });
}
- var banner = id("qunit-banner"),
- tests = id("qunit-tests"),
- runtime = +new Date - config.started,
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ runtime = +new Date() - config.started,
passed = config.stats.all - config.stats.bad,
html = [
- 'Tests completed in ',
+ "Tests completed in ",
runtime,
- ' milliseconds.<br/>',
- '<span class="passed">',
+ " milliseconds.<br/>",
+ "<span class='passed'>",
passed,
- '</span> tests of <span class="total">',
+ "</span> tests of <span class='total'>",
config.stats.all,
- '</span> passed, <span class="failed">',
+ "</span> passed, <span class='failed'>",
config.stats.bad,
- '</span> failed.'
- ].join('');
+ "</span> failed."
+ ].join( "" );
if ( banner ) {
- banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
+ banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
}
if ( tests ) {
@@ -824,70 +1092,119 @@ function done() {
// show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset
document.title = [
- (config.stats.bad ? "\u2716" : "\u2714"),
- document.title.replace(/^[\u2714\u2716] /i, "")
- ].join(" ");
+ ( config.stats.bad ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
}
- runLoggingCallbacks( 'done', QUnit, {
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+ // `key` & `i` initialized at top of scope
+ for ( i = 0; i < sessionStorage.length; i++ ) {
+ key = sessionStorage.key( i++ );
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
+ sessionStorage.removeItem( key );
+ }
+ }
+ }
+
+ runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad,
passed: passed,
total: config.stats.all,
runtime: runtime
- } );
+ });
}
-function validTest( name ) {
- var filter = config.filter,
- run = false;
+/** @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();
+
+ if ( config.testNumber ) {
+ return test.testNumber === config.testNumber;
+ }
+
+ if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
+ return false;
+ }
if ( !filter ) {
return true;
}
- var not = filter.charAt( 0 ) === "!";
- if ( not ) {
+ include = filter.charAt( 0 ) !== "!";
+ if ( !include ) {
filter = filter.slice( 1 );
}
- if ( name.indexOf( filter ) !== -1 ) {
- return !not;
+ // If the filter matches, we need to honour include
+ if ( fullName.indexOf( filter ) !== -1 ) {
+ return include;
}
- if ( not ) {
- run = true;
- }
-
- return run;
+ // Otherwise, do the opposite