0
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-01-17 09:52:29 +01:00

Merge branch 'master' into text-emphasis-variant

This commit is contained in:
Mark Otto 2014-01-18 14:11:36 -08:00
commit 689e75e4b1
55 changed files with 2407 additions and 1673 deletions

View File

@ -7,14 +7,14 @@ module.exports = function (grunt) {
grunt.util.linefeed = '\n'; grunt.util.linefeed = '\n';
RegExp.quote = function (string) { RegExp.quote = function (string) {
return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&') return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
} };
var BsLessdocParser = require('./docs/grunt/bs-lessdoc-parser.js') var BsLessdocParser = require('./docs/grunt/bs-lessdoc-parser.js');
var fs = require('fs') var fs = require('fs');
var generateGlyphiconsData = require('./docs/grunt/bs-glyphicons-data-generator.js') var generateGlyphiconsData = require('./docs/grunt/bs-glyphicons-data-generator.js');
var generateRawFilesJs = require('./docs/grunt/bs-raw-files-generator.js') var generateRawFilesJs = require('./docs/grunt/bs-raw-files-generator.js');
var path = require('path') var path = require('path');
// Project configuration. // Project configuration.
grunt.initConfig({ grunt.initConfig({
@ -131,11 +131,11 @@ module.exports = function (grunt) {
report: 'min' report: 'min'
}, },
src: [ src: [
'docs/assets/js/less.min.js', 'docs/assets/js/vendor/less.min.js',
'docs/assets/js/jszip.js', 'docs/assets/js/vendor/jszip.js',
'docs/assets/js/uglify.min.js', 'docs/assets/js/vendor/uglify.min.js',
'docs/assets/js/blob.js', 'docs/assets/js/vendor/blob.js',
'docs/assets/js/filesaver.js', 'docs/assets/js/vendor/filesaver.js',
'docs/assets/js/raw-files.js', 'docs/assets/js/raw-files.js',
'docs/assets/js/customizer.js' 'docs/assets/js/customizer.js'
], ],
@ -147,7 +147,7 @@ module.exports = function (grunt) {
report: 'min' report: 'min'
}, },
src: [ src: [
'docs/assets/js/holder.js', 'docs/assets/js/vendor/holder.js',
'docs/assets/js/application.js' 'docs/assets/js/application.js'
], ],
dest: 'docs/assets/js/docs.min.js' dest: 'docs/assets/js/docs.min.js'
@ -325,8 +325,8 @@ module.exports = function (grunt) {
sed: { sed: {
versionNumber: { versionNumber: {
pattern: (function () { pattern: (function () {
var old = grunt.option('oldver') var old = grunt.option('oldver');
return old ? RegExp.quote(old) : old return old ? RegExp.quote(old) : old;
})(), })(),
replacement: grunt.option('newver'), replacement: grunt.option('newver'),
recursive: true recursive: true
@ -364,16 +364,16 @@ module.exports = function (grunt) {
testSubtasks.push('validate-html'); testSubtasks.push('validate-html');
} }
// Only run Sauce Labs tests if there's a Sauce access key // Only run Sauce Labs tests if there's a Sauce access key
if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
// Skip Sauce if running a different subset of the test suite // Skip Sauce if running a different subset of the test suite
&& (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'sauce-js-unit')) { (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'sauce-js-unit')) {
testSubtasks.push('connect'); testSubtasks.push('connect');
testSubtasks.push('saucelabs-qunit'); testSubtasks.push('saucelabs-qunit');
} }
// Only run BrowserStack tests if there's a BrowserStack access key // Only run BrowserStack tests if there's a BrowserStack access key
if (typeof process.env.BROWSERSTACK_KEY !== 'undefined' if (typeof process.env.BROWSERSTACK_KEY !== 'undefined' &&
// Skip BrowserStack if running a different subset of the test suite // Skip BrowserStack if running a different subset of the test suite
&& (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'browserstack-js-unit')) { (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'browserstack-js-unit')) {
testSubtasks.push('browserstack_runner'); testSubtasks.push('browserstack_runner');
} }
grunt.registerTask('test', testSubtasks); grunt.registerTask('test', testSubtasks);

View File

@ -14,6 +14,8 @@ baseurl: /
url: http://localhost:9001 url: http://localhost:9001
encoding: UTF-8 encoding: UTF-8
exclude: ["vendor"]
# Custom vars # Custom vars
current_version: 3.0.3 current_version: 3.0.3
repo: https://github.com/twbs/bootstrap repo: https://github.com/twbs/bootstrap

View File

@ -2,6 +2,7 @@
"username": "--secure--", "username": "--secure--",
"key": "--secure--", "key": "--secure--",
"test_path": "js/tests/index.html", "test_path": "js/tests/index.html",
"debug": true,
"browsers": [ "browsers": [
{ {
"browser": "firefox", "browser": "firefox",

File diff suppressed because one or more lines are too long

View File

@ -251,7 +251,11 @@ table {
border: 1px solid #ddd !important; border: 1px solid #ddd !important;
} }
} }
*, * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*:before, *:before,
*:after { *:after {
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
@ -5060,9 +5064,9 @@ button.close {
-moz-transition: -moz-transform .3s ease-out; -moz-transition: -moz-transform .3s ease-out;
-o-transition: -o-transform .3s ease-out; -o-transition: -o-transform .3s ease-out;
transition: transform .3s ease-out; transition: transform .3s ease-out;
-webkit-transform: translate(0, -25%); -webkit-transform: translate(0, -25%);
-ms-transform: translate(0, -25%); -ms-transform: translate(0, -25%);
transform: translate(0, -25%); transform: translate(0, -25%);
} }
.modal.in .modal-dialog { .modal.in .modal-dialog {
-webkit-transform: translate(0, 0); -webkit-transform: translate(0, 0);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11
dist/js/bootstrap.js vendored
View File

@ -322,7 +322,7 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap requires jQuery'
Carousel.prototype.pause = function (e) { Carousel.prototype.pause = function (e) {
e || (this.paused = true) e || (this.paused = true)
if (this.$element.find('.next, .prev').length && $.support.transition.end) { if (this.$element.find('.next, .prev').length && $.support.transition) {
this.$element.trigger($.support.transition.end) this.$element.trigger($.support.transition.end)
this.cycle(true) this.cycle(true)
} }
@ -587,6 +587,7 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap requires jQuery'
var data = $this.data('bs.collapse') var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && option == 'show') option = !option
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]() if (typeof option == 'string') data[option]()
}) })
@ -828,7 +829,7 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap requires jQuery'
this.escape() this.escape()
this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.backdrop(function () { this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade') var transition = $.support.transition && that.$element.hasClass('fade')
@ -881,7 +882,7 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap requires jQuery'
this.$element this.$element
.removeClass('in') .removeClass('in')
.attr('aria-hidden', true) .attr('aria-hidden', true)
.off('click.dismiss.modal') .off('click.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ? $.support.transition && this.$element.hasClass('fade') ?
this.$element this.$element
@ -933,7 +934,7 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap requires jQuery'
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body) .appendTo(document.body)
this.$element.on('click.dismiss.modal', $.proxy(function (e) { this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (e.target !== e.currentTarget) return if (e.target !== e.currentTarget) return
this.options.backdrop == 'static' this.options.backdrop == 'static'
? this.$element[0].focus.call(this.$element[0]) ? this.$element[0].focus.call(this.$element[0])
@ -1015,7 +1016,7 @@ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap requires jQuery'
}) })
$(document) $(document)
.on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') }) .on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') })
.on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') }) .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
}(jQuery); }(jQuery);

File diff suppressed because one or more lines are too long

View File

@ -131,7 +131,7 @@
<div class="bs-customizer-input"> <div class="bs-customizer-input">
<label for="input-@line-height-computed">@line-height-computed</label> <label for="input-@line-height-computed">@line-height-computed</label>
<input id="input-@line-height-computed" type="text" value="floor((@font-size-base * @line-height-base))" data-var="@line-height-computed" class="form-control"/> <input id="input-@line-height-computed" type="text" value="floor((@font-size-base * @line-height-base))" data-var="@line-height-computed" class="form-control"/>
<p class="help-block">Computed &quot;line-height&quot; (<code>font-size</code> &amp;times; <code>line-height</code>) for use with <code>margin</code>, <code>padding</code>, etc.</p> <p class="help-block">Computed &quot;line-height&quot; (<code>font-size</code> * <code>line-height</code>) for use with <code>margin</code>, <code>padding</code>, etc.</p>
</div> </div>
<div class="bs-customizer-input"> <div class="bs-customizer-input">
<label for="input-@headings-font-family">@headings-font-family</label> <label for="input-@headings-font-family">@headings-font-family</label>
@ -1117,7 +1117,7 @@
<div class="bs-customizer-input"> <div class="bs-customizer-input">
<label for="input-@modal-content-fallback-border-color">@modal-content-fallback-border-color</label> <label for="input-@modal-content-fallback-border-color">@modal-content-fallback-border-color</label>
<input id="input-@modal-content-fallback-border-color" type="text" value="#999" data-var="@modal-content-fallback-border-color" class="form-control"/> <input id="input-@modal-content-fallback-border-color" type="text" value="#999" data-var="@modal-content-fallback-border-color" class="form-control"/>
<p class="help-block">Modal content border color &lt;strong&gt;for IE8&lt;/strong&gt;</p> <p class="help-block">Modal content border color <strong>for IE8</strong></p>
</div> </div>
<div class="bs-customizer-input"> <div class="bs-customizer-input">
<label for="input-@modal-backdrop-bg">@modal-backdrop-bg</label> <label for="input-@modal-backdrop-bg">@modal-backdrop-bg</label>

View File

@ -21,6 +21,10 @@
<li><a href="{{ site.expo }}">Expo</a></li> <li><a href="{{ site.expo }}">Expo</a></li>
<li>&middot;</li> <li>&middot;</li>
<li><a href="{{ site.blog }}">Blog</a></li> <li><a href="{{ site.blog }}">Blog</a></li>
<li>&middot;</li>
<li><a href="{{ site.repo }}/issues?state=open">Issues</a></li>
<li>&middot;</li>
<li><a href="{{ site.repo }}/releases">Releases</a></li>
</ul> </ul>
</div> </div>
</footer> </footer>

View File

@ -23,7 +23,7 @@
<li><a href="#grid-offsetting">Offsetting columns</a></li> <li><a href="#grid-offsetting">Offsetting columns</a></li>
<li><a href="#grid-nesting">Nesting columns</a></li> <li><a href="#grid-nesting">Nesting columns</a></li>
<li><a href="#grid-column-ordering">Column ordering</a></li> <li><a href="#grid-column-ordering">Column ordering</a></li>
<li><a href="#grid-less">LESS mixins and variables</a></li> <li><a href="#grid-less">Less mixins and variables</a></li>
</ul> </ul>
</li> </li>
<li> <li>

View File

@ -1,11 +1,11 @@
<li> <li>
<a href="#less">LESS components</a> <a href="#less">Less components</a>
</li> </li>
<li> <li>
<a href="#plugins">jQuery plugins</a> <a href="#plugins">jQuery plugins</a>
</li> </li>
<li> <li>
<a href="#less-variables">LESS variables</a> <a href="#less-variables">Less variables</a>
<ul class="nav"> <ul class="nav">
<li><a href="#variables-basics">Basics</a></li> <li><a href="#variables-basics">Basics</a></li>
<li><a href="#variables-buttons">Buttons</a></li> <li><a href="#variables-buttons">Buttons</a></li>

View File

@ -28,12 +28,6 @@
</li> </li>
<li> <li>
<a href="#migration">Migrating from 2.x to 3.0</a> <a href="#migration">Migrating from 2.x to 3.0</a>
<ul class="nav">
<li><a href="#migration-classes">Major class changes</a></li>
<li><a href="#migration-new">What's new</a></li>
<li><a href="#migration-dropped">What's removed</a></li>
<li><a href="#migration-notes">Additional notes</a></li>
</ul>
</li> </li>
<li> <li>
<a href="#support">Browser and device support</a> <a href="#support">Browser and device support</a>

View File

@ -0,0 +1,12 @@
<li>
<a href="#classes">Major class changes</a>
</li>
<li>
<a href="#new">What's new</a>
</li>
<li>
<a href="#dropped">What's removed</a>
</li>
<li>
<a href="#notes">Additional notes</a>
</li>

View File

@ -40,6 +40,8 @@
{% include nav-customize.html %} {% include nav-customize.html %}
{% elsif page.slug == "about" %} {% elsif page.slug == "about" %}
{% include nav-about.html %} {% include nav-about.html %}
{% elsif page.slug == "migration" %}
{% include nav-migration.html %}
{% endif %} {% endif %}
</ul> </ul>
</div> </div>

View File

@ -618,6 +618,9 @@ body {
.bs-docs-section { .bs-docs-section {
margin-bottom: 60px; margin-bottom: 60px;
} }
.bs-docs-section:last-child {
margin-bottom: 0;
}
h1[id] { h1[id] {
margin-top: 0; margin-top: 0;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -48,8 +48,8 @@ lead: "Global CSS settings, fundamental HTML elements styled and enhanced with e
</ul> </ul>
<p>These styles can be found within <code>scaffolding.less</code>.</p> <p>These styles can be found within <code>scaffolding.less</code>.</p>
<h3 id="overview-normalize">Normalize</h3> <h3 id="overview-normalize">Normalize.css</h3>
<p>For improved cross-browser rendering, we use <a href="http://necolas.github.io/normalize.css/" target="_blank">Normalize</a>, a project by <a href="http://twitter.com/necolas" target="_blank">Nicolas Gallagher</a> and <a href="http://twitter.com/jon_neal" target="_blank">Jonathan Neal</a>.</p> <p>For improved cross-browser rendering, we use <a href="http://necolas.github.io/normalize.css/" target="_blank">Normalize.css</a>, a project by <a href="http://twitter.com/necolas" target="_blank">Nicolas Gallagher</a> and <a href="http://twitter.com/jon_neal" target="_blank">Jonathan Neal</a>.</p>
<h3 id="overview-container">Containers</h3> <h3 id="overview-container">Containers</h3>
<p>Easily center a page's contents by wrapping its contents in a <code>.container</code>. Containers set <code>width</code> at various media query breakpoints to match our grid system.</p> <p>Easily center a page's contents by wrapping its contents in a <code>.container</code>. Containers set <code>width</code> at various media query breakpoints to match our grid system.</p>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -166,6 +166,6 @@
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../../dist/js/bootstrap.min.js"></script> <script src="../../dist/js/bootstrap.min.js"></script>
<script src="../../assets/js/holder.js"></script> <script src="../../assets/js/docs.min.js"></script>
</body> </body>
</html> </html>

View File

@ -201,6 +201,6 @@
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../../dist/js/bootstrap.min.js"></script> <script src="../../dist/js/bootstrap.min.js"></script>
<script src="../../assets/js/holder.js"></script> <script src="../../assets/js/docs.min.js"></script>
</body> </body>
</html> </html>

View File

@ -70,6 +70,6 @@
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../../dist/js/bootstrap.min.js"></script> <script src="../../dist/js/bootstrap.min.js"></script>
<script src="../../assets/js/holder.js"></script> <script src="../../assets/js/docs.min.js"></script>
</body> </body>
</html> </html>

View File

@ -238,6 +238,6 @@
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../../dist/js/bootstrap.min.js"></script> <script src="../../dist/js/bootstrap.min.js"></script>
<script src="../../assets/js/holder.js"></script> <script src="../../assets/js/docs.min.js"></script>
</body> </body>
</html> </html>

View File

@ -379,6 +379,6 @@
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="../../dist/js/bootstrap.min.js"></script> <script src="../../dist/js/bootstrap.min.js"></script>
<script src="../../assets/js/holder.js"></script> <script src="../../assets/js/docs.min.js"></script>
</body> </body>
</html> </html>

View File

@ -87,7 +87,7 @@ bootstrap/
<p>This is the most basic form of Bootstrap: precompiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (<code>bootstrap.*</code>), as well as compiled and minified CSS and JS (<code>bootstrap.min.*</code>). Fonts from Glyphicons are included, as is the optional Bootstrap theme.</p> <p>This is the most basic form of Bootstrap: precompiled files for quick drop-in usage in nearly any web project. We provide compiled CSS and JS (<code>bootstrap.*</code>), as well as compiled and minified CSS and JS (<code>bootstrap.min.*</code>). Fonts from Glyphicons are included, as is the optional Bootstrap theme.</p>
<h2 id="whats-included-source">Bootstrap source code</h2> <h2 id="whats-included-source">Bootstrap source code</h2>
<p>The Bootstrap source code download includes the precompiled CSS, JavaScript, and font assets, along with source LESS, JavaScript, and documentation. More specifically, it includes the following and more:</p> <p>The Bootstrap source code download includes the precompiled CSS, JavaScript, and font assets, along with source Less, JavaScript, and documentation. More specifically, it includes the following and more:</p>
{% highlight bash %} {% highlight bash %}
bootstrap/ bootstrap/
├── less/ ├── less/
@ -350,418 +350,10 @@ bootstrap/
<!-- Migration <!-- Cross link to new migration page -->
================================================== --> <div class="bs-callout bs-callout-info" id="migration">
<div class="bs-docs-section"> <h4>Migrating from v2.x to v3.x</h4>
<h1 id="migration" class="page-header">Migrating from 2.x to 3.0</h1> <p>Looking to migrate from an older version of Bootstrap to v3.x? Check out <a href="../migration">our migration guide</a>.</p>
<p class="lead">Bootstrap 3 is not backwards compatible with v2.x. Use this section as a general guide to upgrading from v2.x to v3.0. For a broader overview, see <a href="http://blog.getbootstrap.com/2013/08/19/bootstrap-3-released/">what's new</a> in the v3.0 release announcement.</p>
<h2 id="migration-classes">Major class changes</h2>
<p>This table shows the style changes between v2.x and v3.0.</p>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Bootstrap 2.x</th>
<th>Bootstrap 3.0</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>.row-fluid</code></td>
<td><code>.row</code></td>
</tr>
<tr>
<td><code>.span*</code></td>
<td><code>.col-md-*</code></td>
</tr>
<tr>
<td><code>.offset*</code></td>
<td><code>.col-md-offset-*</code></td>
</tr>
<tr>
<td><code>.brand</code></td>
<td><code>.navbar-brand</code></td>
</tr>
<tr>
<td><code>.nav-collapse</code></td>
<td><code>.navbar-collapse</code></td>
</tr>
<tr>
<td><code>.nav-toggle</code></td>
<td><code>.navbar-toggle</code></td>
</tr>
<tr>
<td><code>.btn-navbar</code></td>
<td><code>.navbar-btn</code></td>
</tr>
<tr>
<td><code>.hero-unit</code></td>
<td><code>.jumbotron</code></td>
</tr>
<tr>
<td><code>.icon-*</code></td>
<td><code>.glyphicon .glyphicon-*</code></td>
</tr>
<tr>
<td><code>.btn</code></td>
<td><code>.btn .btn-default</code></td>
</tr>
<tr>
<td><code>.btn-mini</code></td>
<td><code>.btn-xs</code></td>
</tr>
<tr>
<td><code>.btn-small</code></td>
<td><code>.btn-sm</code></td>
</tr>
<tr>
<td><code>.btn-large</code></td>
<td><code>.btn-lg</code></td>
</tr>
<tr>
<td><code>.alert-error</code></td>
<td><code>.alert-danger</code></td>
</tr>
<tr>
<td><code>.visible-phone</code></td>
<td><code>.visible-xs</code></td>
</tr>
<tr>
<td><code>.visible-tablet</code></td>
<td><code>.visible-sm</code></td>
</tr>
<tr>
<td><code>.visible-desktop</code></td>
<td>Split into <code>.visible-md .visible-lg</code></td>
</tr>
<tr>
<td><code>.hidden-phone</code></td>
<td><code>.hidden-xs</code></td>
</tr>
<tr>
<td><code>.hidden-tablet</code></td>
<td><code>.hidden-sm</code></td>
</tr>
<tr>
<td><code>.hidden-desktop</code></td>
<td>Split into <code>.hidden-md .hidden-lg</code></td>
</tr>
<tr>
<td><code>.input-small</code></td>
<td><code>.input-sm</code></td>
</tr>
<tr>
<td><code>.input-large</code></td>
<td><code>.input-lg</code></td>
</tr>
<tr>
<td><code>.input-block-level</code></td>
<td><code>.form-control</code></td>
</tr>
<tr>
<td><code>.control-group</code></td>
<td><code>.form-group</code></td>
</tr>
<tr>
<td><code>.control-group.warning .control-group.error .control-group.success</code></td>
<td><code>.form-group.has-*</code></td>
</tr>
<tr>
<td><code>.checkbox.inline</code> <code>.radio.inline</code></td>
<td><code>.checkbox-inline</code> <code>.radio-inline</code></td>
</tr>
<tr>
<td><code>.input-prepend</code> <code>.input-append</code></td>
<td><code>.input-group</code></td>
</tr>
<tr>
<td><code>.add-on</code></td>
<td><code>.input-group-addon</code></td>
</tr>
<tr>
<td><code>.img-polaroid</code></td>
<td><code>.img-thumbnail</code></td>
</tr>
<tr>
<td><code>ul.unstyled</code></td>
<td><code>.list-unstyled</code></td>
</tr>
<tr>
<td><code>ul.inline</code></td>
<td><code>.list-inline</code></td>
</tr>
<tr>
<td><code>.muted</code></td>
<td><code>.text-muted</code></td>
</tr>
<tr>
<td><code>.label</code></td>
<td><code>.label .label-default</code></td>
</tr>
<tr>
<td><code>.label-important</code></td>
<td><code>.label-danger</code></td>
</tr>
<tr>
<td><code>.text-error</code></td>
<td><code>.text-danger</code></td>
</tr>
<tr>
<td><code>.table .error</code></td>
<td><code>.table .danger</code></td>
</tr>
<tr>
<td><code>.bar</code></td>
<td><code>.progress-bar</code></td>
</tr>
<tr>
<td><code>.bar-*</code></td>
<td><code>.progress-bar-*</code></td>
</tr>
<tr>
<td><code>.accordion</code></td>
<td><code>.panel-group</code></td>
</tr>
<tr>
<td><code>.accordion-group</code></td>
<td><code>.panel .panel-default</code></td>
</tr>
<tr>
<td><code>.accordion-heading</code></td>
<td><code>.panel-heading</code></td>
</tr>
<tr>
<td><code>.accordion-body</code></td>
<td><code>.panel-collapse</code></td>
</tr>
<tr>
<td><code>.accordion-inner</code></td>
<td><code>.panel-body</code></td>
</tr>
</tbody>
</table>
</div><!-- /.table-responsive -->
<h2 id="migration-new">What's new</h2>
<p>We've added new elements and changed some existing ones. Here are the new or updated styles.</p>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Element</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Panels</td>
<td><code>.panel .panel-default</code> <code>.panel-body</code> <code>.panel-title</code> <code>.panel-heading</code> <code>.panel-footer</code> <code>.panel-collapse</code></td>
</tr>
<tr>
<td>List groups</td>
<td><code>.list-group</code> <code>.list-group-item</code> <code>.list-group-item-text</code> <code>.list-group-item-heading</code></td>
</tr>
<tr>
<td>Glyphicons</td>
<td><code>.glyphicon</code></td>
</tr>
<tr>
<td>Jumbotron</td>
<td><code>.jumbotron</code></td>
</tr>
<tr>
<td>Extra small grid (&lt;768px)</td>
<td><code>.col-xs-*</code></td>
</tr>
<tr>
<td>Small grid (&ge;768px)</td>
<td><code>.col-sm-*</code></td>
</tr>
<tr>
<td>Medium grid (&ge;992px)</td>
<td><code>.col-md-*</code></td>
</tr>
<tr>
<td>Large grid (&ge;1200px)</td>
<td><code>.col-lg-*</code></td>
</tr>
<tr>
<td>Responsive utility classes (&ge;1200px)</td>
<td><code>.visible-lg</code> <code>.hidden-lg</code></td>
</tr>
<tr>
<td>Offsets</td>
<td><code>.col-sm-offset-*</code> <code>.col-md-offset-*</code> <code>.col-lg-offset-*</code></td>
</tr>
<tr>
<td>Push</td>
<td><code>.col-sm-push-*</code> <code>.col-md-push-*</code> <code>.col-lg-push-*</code></td>
</tr>
<tr>
<td>Pull</td>
<td><code>.col-sm-pull-*</code> <code>.col-md-pull-*</code> <code>.col-lg-pull-*</code></td>
</tr>
<tr>
<td>Input groups</td>
<td><code>.input-group</code> <code>.input-group-addon</code> <code>.input-group-btn</code></td>
</tr>
<tr>
<td>Form controls</td>
<td><code>.form-control</code> <code>.form-group</code></td>
</tr>
<tr>
<td>Button group sizes</td>
<td><code>.btn-group-xs</code> <code>.btn-group-sm</code> <code>.btn-group-lg</code></td>
</tr>
<tr>
<td>Navbar text</td>
<td><code>.navbar-text</code></td>
</tr>
<tr>
<td>Navbar header</td>
<td><code>.navbar-header</code></td>
</tr>
<tr>
<td>Justified tabs / pills</td>
<td><code>.nav-justified</code></td>
</tr>
<tr>
<td>Responsive images</td>
<td><code>.img-responsive</code></td>
</tr>
<tr>
<td>Contextual table rows</td>
<td><code>.success</code> <code>.danger</code> <code>.warning</code> <code>.active</code></td>
</tr>
<tr>
<td>Contextual panels</td>
<td><code>.panel-success</code> <code>.panel-danger</code> <code>.panel-warning</code> <code>.panel-info</code></td>
</tr>
<tr>
<td>Modal</td>
<td><code>.modal-dialog</code> <code>.modal-content</code></td>
</tr>
<tr>
<td>Thumbnail image</td>
<td><code>.img-thumbnail</code></td>
</tr>
<tr>
<td>Well sizes</td>
<td><code>.well-sm</code> <code>.well-lg</code></td>
</tr>
<tr>
<td>Alert links</td>
<td><code>.alert-link</code></td>
</tr>
</tbody>
</table>
</div><!-- /.table-responsive -->
<h2 id="migration-dropped">What's removed</h2>
<p>The following elements have been dropped or changed in v3.0.</p>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Element</th>
<th>Removed from 2.x</th>
<th>3.0 Equivalent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Form actions</td>
<td><code>.form-actions</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Search form</td>
<td><code>.form-search</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Form group with info</td>
<td><code>.control-group.info</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Block level from input</td>
<td><code>.input-block-level</code></td>
<td>No direct equivalent, but <a href="../css/#forms-controls">forms controls</a> are similar.</td>
</tr>
<tr>
<td>Fluid row</td>
<td><code>.row-fluid</code></td>
<td><code>.row</code> (no more fixed grid)</td>
</tr>
<tr>
<td>Controls wrapper</td>
<td><code>.controls</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Controls row</td>
<td><code>.controls-row</code></td>
<td><code>.row</code> or <code>.form-group</code></td>
</tr>
<tr>
<td>Navbar inner</td>
<td><code>.navbar-inner</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Navbar vertical dividers</td>
<td><code>.navbar .divider-vertical</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Dropdown submenu</td>
<td><code>.dropdown-submenu</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Tab alignments</td>
<td><code>.tabs-left</code> <code>.tabs-right</code> <code>.tabs-below</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Pill-based tabbable area</td>
<td><code>.pill-content</code></td>
<td><code>.tab-content</code></td>
</tr>
<tr>
<td>Pill-based tabbable area pane</td>
<td><code>.pill-pane</code></td>
<td><code>.tab-pane</code></td>
</tr>
<tr>
<td>Nav lists</td>
<td><code>.nav-list</code> <code>.nav-header</code></td>
<td>No direct equivalent, but <a href="../components/#list-group">list groups</a> and <a href="../javascript/#collapse"><code>.panel-group</code>s</a> are similar.</td>
</tr>
</tbody>
</table>
</div><!-- /.table-responsive -->
<h2 id="migration-notes">Additional notes</h2>
<p>Other changes in v3.0 are not immediately apparent. Base classes, key styles, and behaviors have been adjusted for flexibility and our <em>mobile first</em> approach. Here's a partial list:</p>
<ul>
<li>By default, text-based form controls now receive only minimal styling. For focus colors and rounded corners, apply the <code>.form-control</code> class on the element to style.</li>
<li>Text-based form controls with the <code>.form-control</code> class applied are now 100% wide by default. Wrap inputs inside <code>&lt;div class="col-*"&gt;&lt;/div&gt;</code> to control input widths.</li>
<li><code>.badge</code> no longer has contextual (-success,-primary,etc..) classes.</li>
<li><code>.btn</code> must also use <code>.btn-default</code> to get the "default" button.</li>
<li><code>.row</code> is now fluid.</li>
<li>Images are no longer responsive by default. Use <code>.img-responsive</code> for fluid <code>&lt;img&gt;</code> size.</li>
<li>The icons, now <code>.glyphicon</code>, are now font based. Icons also require a base and icon class (e.g. <code>.glyphicon .glyphicon-asterisk</code>).</li>
<li>Typeahead has been dropped, in favor of using <a href="http://twitter.github.io/typeahead.js/">Twitter Typeahead</a>.</li>
<li>Modal markup has changed significantly. The <code>.modal-header</code>, <code>.modal-body</code>, and <code>.modal-footer</code> sections are now wrapped in <code>.modal-content</code> and <code>.modal-dialog</code> for better mobile styling and behavior.</li>
<li>The HTML loaded by the <code>remote</code> modal option is now injected into the <code>.modal</code> instead of into the <code>.modal-body</code>. This allows you to also easily vary the header and footer of the modal, not just the modal body.</li>
<li>JavaScript events are namespaced. For example, to handle the modal "show" event, use <code>'show.bs.modal'</code>. For tabs "shown" use <code>'shown.bs.tab'</code>, etc.</li>
</ul>
<p>For more information on upgrading to v3.0, and code snippets from the community, see <a href="http://bootply.com/">Bootply</a>.</p>
</div> </div>
@ -773,14 +365,53 @@ bootstrap/
<p class="lead">Bootstrap is built to work best in the latest desktop and mobile browsers, meaning older browsers might display differently styled, though fully functional, renderings of certain components.</p> <p class="lead">Bootstrap is built to work best in the latest desktop and mobile browsers, meaning older browsers might display differently styled, though fully functional, renderings of certain components.</p>
<h3 id="support-browsers">Supported browsers</h3> <h3 id="support-browsers">Supported browsers</h3>
<p>Specifically, we support the latest versions of the following:</p> <p>Specifically, we support the <strong>latest versions</strong> of the following browsers and platforms:</p>
<ul> <div class="table-responsive">
<li>Chrome (Mac, Windows, iOS, and Android)</li> <table class="table table-bordered table-striped">
<li>Safari (Mac and iOS only, as the Windows version is being abandoned)</li> <thead>
<li>Firefox (Mac, Windows)</li> <tr>
<li>Internet Explorer</li> <td></td>
<li>Opera (Mac, Windows)</li> <th>Chrome</th>
</ul> <th>Firefox</th>
<th>Internet Explorer</th>
<th>Opera</th>
<th>Safari</th>
</tr>
</thead>
<tbody>
<tr>
<th>Android</th>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-danger"><span class="glyphicon glyphicon-remove"></span> <span class="sr-only">Not Supported</span></td>
<td class="text-muted" rowspan="3" style="vertical-align: middle;">N/A</td>
<td class="text-danger"><span class="glyphicon glyphicon-remove"></span> <span class="sr-only">Not Supported</span></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<th>iOS</th>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-muted">N/A</td>
<td class="text-danger"><span class="glyphicon glyphicon-remove"></span> <span class="sr-only">Not Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
</tr>
<tr>
<th>Mac OS X</th>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
</tr>
<tr>
<th>Windows</th>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-success"><span class="glyphicon glyphicon-ok"></span> <span class="sr-only">Supported</span></td>
<td class="text-danger"><span class="glyphicon glyphicon-remove"></span> <span class="sr-only">Not Supported</span></td>
</tr>
</tbody>
</table>
</div>
<p>Unofficially, Bootstrap should look and behave well enough in Chromium and Chrome for Linux, Firefox for Linux, and Internet Explorer 7, though they are not officially supported.</p> <p>Unofficially, Bootstrap should look and behave well enough in Chromium and Chrome for Linux, Firefox for Linux, and Internet Explorer 7, though they are not officially supported.</p>
<h3 id="support-ie8-ie9">Internet Explorer 8 and 9</h3> <h3 id="support-ie8-ie9">Internet Explorer 8 and 9</h3>
@ -926,7 +557,7 @@ if (isAndroid) {
* *
* Reset individual elements or override regions to avoid conflicts due to * Reset individual elements or override regions to avoid conflicts due to
* global box model settings of Bootstrap. Two options, individual overrides and * global box model settings of Bootstrap. Two options, individual overrides and
* region resets, are available as plain CSS and uncompiled LESS formats. * region resets, are available as plain CSS and uncompiled Less formats.
*/ */
/* Option 1A: Override a single element's box model via CSS */ /* Option 1A: Override a single element's box model via CSS */
@ -936,7 +567,7 @@ if (isAndroid) {
box-sizing: content-box; box-sizing: content-box;
} }
/* Option 1B: Override a single element's box model by using a Bootstrap LESS mixin */ /* Option 1B: Override a single element's box model by using a Bootstrap Less mixin */
.element { .element {
.box-sizing(content-box); .box-sizing(content-box);
} }
@ -951,7 +582,7 @@ if (isAndroid) {
box-sizing: content-box; box-sizing: content-box;
} }
/* Option 2B: Reset an entire region with a custom LESS mixin */ /* Option 2B: Reset an entire region with a custom Less mixin */
.reset-box-sizing { .reset-box-sizing {
&, &,
*, *,
@ -1041,7 +672,7 @@ if (isAndroid) {
<h1 id="customizing" class="page-header">Customizing Bootstrap</h1> <h1 id="customizing" class="page-header">Customizing Bootstrap</h1>
<p class="lead">Bootstrap is best maintained when you treat it as a separate and independently-versioned dependency in your development environment. Doing this makes upgrading Bootstrap easier in the future.</p> <p class="lead">Bootstrap is best maintained when you treat it as a separate and independently-versioned dependency in your development environment. Doing this makes upgrading Bootstrap easier in the future.</p>
<p>Once you've downloaded and included Bootstrap's styles and scripts, you can customize its components. Just create a new stylesheet (LESS, if you like, or just plain CSS) to house your customizations.</p> <p>Once you've downloaded and included Bootstrap's styles and scripts, you can customize its components. Just create a new stylesheet (Less, if you like, or just plain CSS) to house your customizations.</p>
<div class="bs-callout bs-callout-info"> <div class="bs-callout bs-callout-info">
<h4>Compiled or minified?</h4> <h4>Compiled or minified?</h4>
@ -1103,7 +734,7 @@ if (isAndroid) {
<div class="bs-callout bs-callout-info"> <div class="bs-callout bs-callout-info">
<h4>Alternate customization methods</h4> <h4>Alternate customization methods</h4>
<p>While not recommended for folks new to Bootstrap, you may use one of two alternate methods for customization. The first is modifying the source <code>.less</code> files (making upgrades super difficult), and the second is mapping source LESS code to <a href="http://ruby.bvision.com/blog/please-stop-embedding-bootstrap-classes-in-your-html">your own classes via mixins</a>. For the time being, neither of those options are documented here.</p> <p>While not recommended for folks new to Bootstrap, you may use one of two alternate methods for customization. The first is modifying the source <code>.less</code> files (making upgrades super difficult), and the second is mapping source Less code to <a href="http://ruby.bvision.com/blog/please-stop-embedding-bootstrap-classes-in-your-html">your own classes via mixins</a>. For the time being, neither of those options are documented here.</p>
</div> </div>
<h3>Removing potential bloat</h3> <h3>Removing potential bloat</h3>

View File

@ -25,7 +25,7 @@ title: Bootstrap
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col-sm-4">
<img src="assets/img/sass-less.png" alt="Sass and Less support" class="img-responsive"> <img src="assets/img/sass-less.png" alt="Sass and Less support" class="img-responsive">
<h3>Preprocesors</h3> <h3>Preprocessors</h3>
<p>In addition to vanilla CSS, Bootstrap includes support for the two most popular CSS preprocessors, <a href="{{ page.base_url }}css#less">Less</a> and <a href="{{ page.base_url }}css#sass">Sass</a>.</p> <p>In addition to vanilla CSS, Bootstrap includes support for the two most popular CSS preprocessors, <a href="{{ page.base_url }}css#less">Less</a> and <a href="{{ page.base_url }}css#sass">Sass</a>.</p>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">

View File

@ -793,7 +793,7 @@ $('#myTab li:eq(2) a').tab('show') // Select third tab (0-indexed)
{% highlight html %} {% highlight html %}
<!-- Nav tabs --> <!-- Nav tabs -->
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li><a href="#home" data-toggle="tab">Home</a></li> <li class="active"><a href="#home" data-toggle="tab">Home</a></li>
<li><a href="#profile" data-toggle="tab">Profile</a></li> <li><a href="#profile" data-toggle="tab">Profile</a></li>
<li><a href="#messages" data-toggle="tab">Messages</a></li> <li><a href="#messages" data-toggle="tab">Messages</a></li>
<li><a href="#settings" data-toggle="tab">Settings</a></li> <li><a href="#settings" data-toggle="tab">Settings</a></li>

423
docs/migration.html Normal file
View File

@ -0,0 +1,423 @@
---
layout: default
title: Migrating to v3.x
slug: migration
lead: "Guidance on how to upgrade from Bootstrap v2.x to v3.x with emphasis on major changes, what's new, and what's been removed."
---
<!-- Migration
================================================== -->
<div class="bs-docs-section">
<h1 class="page-header">Migrating from 2.x to 3.0</h1>
<p class="lead">Bootstrap 3 is not backwards compatible with v2.x. Use this section as a general guide to upgrading from v2.x to v3.0. For a broader overview, see <a href="http://blog.getbootstrap.com/2013/08/19/bootstrap-3-released/">what's new</a> in the v3.0 release announcement.</p>
<h2 id="classes">Major class changes</h2>
<p>This table shows the style changes between v2.x and v3.0.</p>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Bootstrap 2.x</th>
<th>Bootstrap 3.0</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>.row-fluid</code></td>
<td><code>.row</code></td>
</tr>
<tr>
<td><code>.span*</code></td>
<td><code>.col-md-*</code></td>
</tr>
<tr>
<td><code>.offset*</code></td>
<td><code>.col-md-offset-*</code></td>
</tr>
<tr>
<td><code>.brand</code></td>
<td><code>.navbar-brand</code></td>
</tr>
<tr>
<td><code>.nav-collapse</code></td>
<td><code>.navbar-collapse</code></td>
</tr>
<tr>
<td><code>.nav-toggle</code></td>
<td><code>.navbar-toggle</code></td>
</tr>
<tr>
<td><code>.btn-navbar</code></td>
<td><code>.navbar-btn</code></td>
</tr>
<tr>
<td><code>.hero-unit</code></td>
<td><code>.jumbotron</code></td>
</tr>
<tr>
<td><code>.icon-*</code></td>
<td><code>.glyphicon .glyphicon-*</code></td>
</tr>
<tr>
<td><code>.btn</code></td>
<td><code>.btn .btn-default</code></td>
</tr>
<tr>
<td><code>.btn-mini</code></td>
<td><code>.btn-xs</code></td>
</tr>
<tr>
<td><code>.btn-small</code></td>
<td><code>.btn-sm</code></td>
</tr>
<tr>
<td><code>.btn-large</code></td>
<td><code>.btn-lg</code></td>
</tr>
<tr>
<td><code>.alert-error</code></td>
<td><code>.alert-danger</code></td>
</tr>
<tr>
<td><code>.visible-phone</code></td>
<td><code>.visible-xs</code></td>
</tr>
<tr>
<td><code>.visible-tablet</code></td>
<td><code>.visible-sm</code></td>
</tr>
<tr>
<td><code>.visible-desktop</code></td>
<td>Split into <code>.visible-md .visible-lg</code></td>
</tr>
<tr>
<td><code>.hidden-phone</code></td>
<td><code>.hidden-xs</code></td>
</tr>
<tr>
<td><code>.hidden-tablet</code></td>
<td><code>.hidden-sm</code></td>
</tr>
<tr>
<td><code>.hidden-desktop</code></td>
<td>Split into <code>.hidden-md .hidden-lg</code></td>
</tr>
<tr>
<td><code>.input-small</code></td>
<td><code>.input-sm</code></td>
</tr>
<tr>
<td><code>.input-large</code></td>
<td><code>.input-lg</code></td>
</tr>
<tr>
<td><code>.input-block-level</code></td>
<td><code>.form-control</code></td>
</tr>
<tr>
<td><code>.control-group</code></td>
<td><code>.form-group</code></td>
</tr>
<tr>
<td><code>.control-group.warning .control-group.error .control-group.success</code></td>
<td><code>.form-group.has-*</code></td>
</tr>
<tr>
<td><code>.checkbox.inline</code> <code>.radio.inline</code></td>
<td><code>.checkbox-inline</code> <code>.radio-inline</code></td>
</tr>
<tr>
<td><code>.input-prepend</code> <code>.input-append</code></td>
<td><code>.input-group</code></td>
</tr>
<tr>
<td><code>.add-on</code></td>
<td><code>.input-group-addon</code></td>
</tr>
<tr>
<td><code>.img-polaroid</code></td>
<td><code>.img-thumbnail</code></td>
</tr>
<tr>
<td><code>ul.unstyled</code></td>
<td><code>.list-unstyled</code></td>
</tr>
<tr>
<td><code>ul.inline</code></td>
<td><code>.list-inline</code></td>
</tr>
<tr>
<td><code>.muted</code></td>
<td><code>.text-muted</code></td>
</tr>
<tr>
<td><code>.label</code></td>
<td><code>.label .label-default</code></td>
</tr>
<tr>
<td><code>.label-important</code></td>
<td><code>.label-danger</code></td>
</tr>
<tr>
<td><code>.text-error</code></td>
<td><code>.text-danger</code></td>
</tr>
<tr>
<td><code>.table .error</code></td>
<td><code>.table .danger</code></td>
</tr>
<tr>
<td><code>.bar</code></td>
<td><code>.progress-bar</code></td>
</tr>
<tr>
<td><code>.bar-*</code></td>
<td><code>.progress-bar-*</code></td>
</tr>
<tr>
<td><code>.accordion</code></td>
<td><code>.panel-group</code></td>
</tr>
<tr>
<td><code>.accordion-group</code></td>
<td><code>.panel .panel-default</code></td>
</tr>
<tr>
<td><code>.accordion-heading</code></td>
<td><code>.panel-heading</code></td>
</tr>
<tr>
<td><code>.accordion-body</code></td>
<td><code>.panel-collapse</code></td>
</tr>
<tr>
<td><code>.accordion-inner</code></td>
<td><code>.panel-body</code></td>
</tr>
</tbody>
</table>
</div><!-- /.table-responsive -->
<h2 id="new">What's new</h2>
<p>We've added new elements and changed some existing ones. Here are the new or updated styles.</p>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Element</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Panels</td>
<td><code>.panel .panel-default</code> <code>.panel-body</code> <code>.panel-title</code> <code>.panel-heading</code> <code>.panel-footer</code> <code>.panel-collapse</code></td>
</tr>
<tr>
<td>List groups</td>
<td><code>.list-group</code> <code>.list-group-item</code> <code>.list-group-item-text</code> <code>.list-group-item-heading</code></td>
</tr>
<tr>
<td>Glyphicons</td>
<td><code>.glyphicon</code></td>
</tr>
<tr>
<td>Jumbotron</td>
<td><code>.jumbotron</code></td>
</tr>
<tr>
<td>Extra small grid (&lt;768px)</td>
<td><code>.col-xs-*</code></td>
</tr>
<tr>
<td>Small grid (&ge;768px)</td>
<td><code>.col-sm-*</code></td>
</tr>
<tr>
<td>Medium grid (&ge;992px)</td>
<td><code>.col-md-*</code></td>
</tr>
<tr>
<td>Large grid (&ge;1200px)</td>
<td><code>.col-lg-*</code></td>
</tr>
<tr>
<td>Responsive utility classes (&ge;1200px)</td>
<td><code>.visible-lg</code> <code>.hidden-lg</code></td>
</tr>
<tr>
<td>Offsets</td>
<td><code>.col-sm-offset-*</code> <code>.col-md-offset-*</code> <code>.col-lg-offset-*</code></td>
</tr>
<tr>
<td>Push</td>
<td><code>.col-sm-push-*</code> <code>.col-md-push-*</code> <code>.col-lg-push-*</code></td>
</tr>
<tr>
<td>Pull</td>
<td><code>.col-sm-pull-*</code> <code>.col-md-pull-*</code> <code>.col-lg-pull-*</code></td>
</tr>
<tr>
<td>Input groups</td>
<td><code>.input-group</code> <code>.input-group-addon</code> <code>.input-group-btn</code></td>
</tr>
<tr>
<td>Form controls</td>
<td><code>.form-control</code> <code>.form-group</code></td>
</tr>
<tr>
<td>Button group sizes</td>
<td><code>.btn-group-xs</code> <code>.btn-group-sm</code> <code>.btn-group-lg</code></td>
</tr>
<tr>
<td>Navbar text</td>
<td><code>.navbar-text</code></td>
</tr>
<tr>
<td>Navbar header</td>
<td><code>.navbar-header</code></td>
</tr>
<tr>
<td>Justified tabs / pills</td>
<td><code>.nav-justified</code></td>
</tr>
<tr>
<td>Responsive images</td>
<td><code>.img-responsive</code></td>
</tr>
<tr>
<td>Contextual table rows</td>
<td><code>.success</code> <code>.danger</code> <code>.warning</code> <code>.active</code></td>
</tr>
<tr>
<td>Contextual panels</td>
<td><code>.panel-success</code> <code>.panel-danger</code> <code>.panel-warning</code> <code>.panel-info</code></td>
</tr>
<tr>
<td>Modal</td>
<td><code>.modal-dialog</code> <code>.modal-content</code></td>
</tr>
<tr>
<td>Thumbnail image</td>
<td><code>.img-thumbnail</code></td>
</tr>
<tr>
<td>Well sizes</td>
<td><code>.well-sm</code> <code>.well-lg</code></td>
</tr>
<tr>
<td>Alert links</td>
<td><code>.alert-link</code></td>
</tr>
</tbody>
</table>
</div><!-- /.table-responsive -->
<h2 id="dropped">What's removed</h2>
<p>The following elements have been dropped or changed in v3.0.</p>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Element</th>
<th>Removed from 2.x</th>
<th>3.0 Equivalent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Form actions</td>
<td><code>.form-actions</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Search form</td>
<td><code>.form-search</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Form group with info</td>
<td><code>.control-group.info</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Block level from input</td>
<td><code>.input-block-level</code></td>
<td>No direct equivalent, but <a href="../css/#forms-controls">forms controls</a> are similar.</td>
</tr>
<tr>
<td>Fluid row</td>
<td><code>.row-fluid</code></td>
<td><code>.row</code> (no more fixed grid)</td>
</tr>
<tr>
<td>Controls wrapper</td>
<td><code>.controls</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Controls row</td>
<td><code>.controls-row</code></td>
<td><code>.row</code> or <code>.form-group</code></td>
</tr>
<tr>
<td>Navbar inner</td>
<td><code>.navbar-inner</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Navbar vertical dividers</td>
<td><code>.navbar .divider-vertical</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Dropdown submenu</td>
<td><code>.dropdown-submenu</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Tab alignments</td>
<td><code>.tabs-left</code> <code>.tabs-right</code> <code>.tabs-below</code></td>
<td class="text-muted">N/A</td>
</tr>
<tr>
<td>Pill-based tabbable area</td>
<td><code>.pill-content</code></td>
<td><code>.tab-content</code></td>
</tr>
<tr>
<td>Pill-based tabbable area pane</td>
<td><code>.pill-pane</code></td>
<td><code>.tab-pane</code></td>
</tr>
<tr>
<td>Nav lists</td>
<td><code>.nav-list</code> <code>.nav-header</code></td>
<td>No direct equivalent, but <a href="../components/#list-group">list groups</a> and <a href="../javascript/#collapse"><code>.panel-group</code>s</a> are similar.</td>
</tr>
</tbody>
</table>
</div><!-- /.table-responsive -->
<h2 id="notes">Additional notes</h2>
<p>Other changes in v3.0 are not immediately apparent. Base classes, key styles, and behaviors have been adjusted for flexibility and our <em>mobile first</em> approach. Here's a partial list:</p>
<ul>
<li>By default, text-based form controls now receive only minimal styling. For focus colors and rounded corners, apply the <code>.form-control</code> class on the element to style.</li>
<li>Text-based form controls with the <code>.form-control</code> class applied are now 100% wide by default. Wrap inputs inside <code>&lt;div class="col-*"&gt;&lt;/div&gt;</code> to control input widths.</li>
<li><code>.badge</code> no longer has contextual (-success,-primary,etc..) classes.</li>
<li><code>.btn</code> must also use <code>.btn-default</code> to get the "default" button.</li>
<li><code>.row</code> is now fluid.</li>
<li>Images are no longer responsive by default. Use <code>.img-responsive</code> for fluid <code>&lt;img&gt;</code> size.</li>
<li>The icons, now <code>.glyphicon</code>, are now font based. Icons also require a base and icon class (e.g. <code>.glyphicon .glyphicon-asterisk</code>).</li>
<li>Typeahead has been dropped, in favor of using <a href="http://twitter.github.io/typeahead.js/">Twitter Typeahead</a>.</li>
<li>Modal markup has changed significantly. The <code>.modal-header</code>, <code>.modal-body</code>, and <code>.modal-footer</code> sections are now wrapped in <code>.modal-content</code> and <code>.modal-dialog</code> for better mobile styling and behavior.</li>
<li>The HTML loaded by the <code>remote</code> modal option is now injected into the <code>.modal</code> instead of into the <code>.modal-body</code>. This allows you to also easily vary the header and footer of the modal, not just the modal body.</li>
<li>JavaScript events are namespaced. For example, to handle the modal "show" event, use <code>'show.bs.modal'</code>. For tabs "shown" use <code>'shown.bs.tab'</code>, etc.</li>
</ul>
<p>For more information on upgrading to v3.0, and code snippets from the community, see <a href="http://bootply.com/">Bootply</a>.</p>
</div>

View File

@ -68,7 +68,7 @@
Carousel.prototype.pause = function (e) { Carousel.prototype.pause = function (e) {
e || (this.paused = true) e || (this.paused = true)
if (this.$element.find('.next, .prev').length && $.support.transition.end) { if (this.$element.find('.next, .prev').length && $.support.transition) {
this.$element.trigger($.support.transition.end) this.$element.trigger($.support.transition.end)
this.cycle(true) this.cycle(true)
} }

View File

@ -127,6 +127,7 @@
var data = $this.data('bs.collapse') var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && option == 'show') option = !option
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]() if (typeof option == 'string') data[option]()
}) })

View File

@ -50,7 +50,7 @@
this.escape() this.escape()
this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.backdrop(function () { this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade') var transition = $.support.transition && that.$element.hasClass('fade')
@ -103,7 +103,7 @@
this.$element this.$element
.removeClass('in') .removeClass('in')
.attr('aria-hidden', true) .attr('aria-hidden', true)
.off('click.dismiss.modal') .off('click.dismiss.bs.modal')
$.support.transition && this.$element.hasClass('fade') ? $.support.transition && this.$element.hasClass('fade') ?
this.$element this.$element
@ -155,7 +155,7 @@
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body) .appendTo(document.body)
this.$element.on('click.dismiss.modal', $.proxy(function (e) { this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (e.target !== e.currentTarget) return if (e.target !== e.currentTarget) return
this.options.backdrop == 'static' this.options.backdrop == 'static'
? this.$element[0].focus.call(this.$element[0]) ? this.$element[0].focus.call(this.$element[0])
@ -237,7 +237,7 @@
}) })
$(document) $(document)
.on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') }) .on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') })
.on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') }) .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
}(jQuery); }(jQuery);

View File

@ -17,7 +17,7 @@
</script> </script>
<!-- plugin sources --> <!-- plugin sources -->
<script src="../../js/transition.js"></script> <script>$.support.transition = false</script>
<script src="../../js/alert.js"></script> <script src="../../js/alert.js"></script>
<script src="../../js/button.js"></script> <script src="../../js/button.js"></script>
<script src="../../js/carousel.js"></script> <script src="../../js/carousel.js"></script>
@ -47,10 +47,7 @@
</head> </head>
<body> <body>
<div> <div>
<h1 id="qunit-header">Bootstrap Plugin Test Suite</h1> <div id="qunit"></div>
<h2 id="qunit-banner"></h2>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"></div> <div id="qunit-fixture"></div>
</div> </div>
</body> </body>

View File

@ -157,16 +157,15 @@ $(function () {
test('should trigger hide event once when clicking outside of modal-content', function () { test('should trigger hide event once when clicking outside of modal-content', function () {
stop() stop()
$.support.transition = false $.support.transition = false
var div = $('<div id="modal-test"><div class="contents"></div></div>')
var triggered var triggered
var div = $('<div id="modal-test"><div class="contents"></div></div>')
div div
.bind('shown.bs.modal', function () { .bind('shown.bs.modal', function () {
triggered = 0 triggered = 0
$('#modal-test').click() $('#modal-test').click()
}) })
.one('hidden.bs.modal', function () {
div.modal('show')
})
.bind('hide.bs.modal', function () { .bind('hide.bs.modal', function () {
triggered += 1 triggered += 1
ok(triggered === 1, 'modal hide triggered once') ok(triggered === 1, 'modal hide triggered once')

View File

@ -73,11 +73,14 @@ $(function () {
'</ul>' '</ul>'
$(dropHTML).find('ul>li:first a').tab('show').end() $(dropHTML).find('ul>li:first a').tab('show').end()
.find('ul>li:last a').on('show', function (event) { .find('ul>li:last a')
.on('show.bs.tab', function (event) {
equal(event.relatedTarget.hash, '#1-1') equal(event.relatedTarget.hash, '#1-1')
}).on('shown', function (event) { })
.on('show.bs.tab', function (event) {
equal(event.relatedTarget.hash, '#1-1') equal(event.relatedTarget.hash, '#1-1')
}).tab('show') })
.tab('show')
}) })
}) })

View File

@ -332,7 +332,7 @@ $(function () {
var tooltip = container.find('.tooltip') var tooltip = container.find('.tooltip')
start() start()
ok(tooltip.offset().top + tooltip.outerHeight() <= tooltiped.offset().top) ok(Math.round(tooltip.offset().top + tooltip.outerHeight()) <= Math.round(tooltiped.offset().top))
container.remove() container.remove()
}, 100) }, 100)
}) })
@ -347,7 +347,11 @@ $(function () {
.tooltip('show'), .tooltip('show'),
tooltip = container.find('.tooltip') tooltip = container.find('.tooltip')
ok( Math.round(target.offset().top + (target[0].offsetHeight / 2) - (tooltip[0].offsetHeight / 2)) === Math.round(tooltip.offset().top) ) // this is some dumb hack shit because sub pixels in firefox
var top = Math.round(target.offset().top + (target[0].offsetHeight / 2) - (tooltip[0].offsetHeight / 2))
var top2 = Math.round(tooltip.offset().top)
var topDiff = top - top2
ok(topDiff <= 1 && topDiff >= -1)
target.tooltip('hide') target.tooltip('hide')
}) })
@ -402,7 +406,6 @@ $(function () {
.tooltip({placement: 'auto'}) .tooltip({placement: 'auto'})
.tooltip('show') .tooltip('show')
ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned bottom') ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned bottom')
topTooltip.tooltip('hide') topTooltip.tooltip('hide')
@ -415,14 +418,6 @@ $(function () {
ok($('.tooltip').is('.left'), 'right positioned tooltip is dynamically positioned left') ok($('.tooltip').is('.left'), 'right positioned tooltip is dynamically positioned left')
rightTooltip.tooltip('hide') rightTooltip.tooltip('hide')
var bottomTooltip = $('<div style="display: inline-block; position: absolute; bottom: 0;" rel="tooltip" title="Bottom tooltip">Bottom Dynamic Tooltip</div>')
.appendTo('#dynamic-tt-test')
.tooltip({placement: 'auto bottom'})
.tooltip('show')
ok($('.tooltip').is('.top'), 'bottom positioned tooltip is dynamically positioned top')
bottomTooltip.tooltip('hide')
var leftTooltip = $('<div style="display: inline-block; position: absolute; left: 0;" rel="tooltip" title="Left tooltip">Left Dynamic Tooltip</div>') var leftTooltip = $('<div style="display: inline-block; position: absolute; left: 0;" rel="tooltip" title="Left tooltip">Left Dynamic Tooltip</div>')
.appendTo('#dynamic-tt-test') .appendTo('#dynamic-tt-test')
.tooltip({placement: 'auto left'}) .tooltip({placement: 'auto left'})

View File

@ -1,13 +0,0 @@
$(function () {
module('transition')
test('should be defined on jquery support object', function () {
ok($.support.transition !== undefined, 'transition object is defined')
})
test('should provide an end object', function () {
ok($.support.transition ? $.support.transition.end : true, 'end string is defined')
})
})

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,12 @@
/** /*!
* QUnit 1.0.0 - A JavaScript Unit Testing Framework * QUnit 1.13.0
* http://qunitjs.com/
* *
* http://docs.jquery.com/QUnit * Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
* *
* Copyright (c) 2011 John Resig, Jörn Zaefferer * Date: 2014-01-04T17:09Z
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/ */
/** Font Family and Sizes */ /** Font Family and Sizes */
@ -20,7 +21,7 @@
/** Resets */ /** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -38,10 +39,10 @@
line-height: 1em; line-height: 1em;
font-weight: normal; font-weight: normal;
border-radius: 15px 15px 0 0; border-radius: 5px 5px 0 0;
-moz-border-radius: 15px 15px 0 0; -moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 15px; -webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 15px; -webkit-border-top-left-radius: 5px;
} }
#qunit-header a { #qunit-header a {
@ -54,6 +55,11 @@
color: #fff; color: #fff;
} }
#qunit-testrunner-toolbar label {
display: inline-block;
padding: 0 .5em 0 .1em;
}
#qunit-banner { #qunit-banner {
height: 5px; height: 5px;
} }
@ -62,6 +68,7 @@
padding: 0.5em 0 0.5em 2em; padding: 0.5em 0 0.5em 2em;
color: #5E740B; color: #5E740B;
background-color: #eee; background-color: #eee;
overflow: hidden;
} }
#qunit-userAgent { #qunit-userAgent {
@ -71,6 +78,9 @@
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
} }
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */ /** Tests: Pass/Fail */
@ -102,19 +112,24 @@
color: #000; color: #000;
} }
#qunit-tests ol { #qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em; margin-top: 0.5em;
padding: 0.5em; padding: 0.5em;
background-color: #fff; background-color: #fff;
border-radius: 15px; border-radius: 5px;
-moz-border-radius: 15px; -moz-border-radius: 5px;
-webkit-border-radius: 15px; -webkit-border-radius: 5px;
}
box-shadow: inset 0px 2px 13px #999; .qunit-collapsed {
-moz-box-shadow: inset 0px 2px 13px #999; display: none;
-webkit-box-shadow: inset 0px 2px 13px #999;
} }
#qunit-tests table { #qunit-tests table {
@ -157,8 +172,7 @@
#qunit-tests b.failed { color: #710909; } #qunit-tests b.failed { color: #710909; }
#qunit-tests li li { #qunit-tests li li {
margin: 0.5em; padding: 5px;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff; background-color: #fff;
border-bottom: none; border-bottom: none;
list-style-position: inside; list-style-position: inside;
@ -167,9 +181,9 @@
/*** Passing Styles */ /*** Passing Styles */
#qunit-tests li li.pass { #qunit-tests li li.pass {
color: #5E740B; color: #3c510c;
background-color: #fff; background-color: #fff;
border-left: 26px solid #C6E746; border-left: 10px solid #C6E746;
} }
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
@ -185,15 +199,15 @@
#qunit-tests li li.fail { #qunit-tests li li.fail {
color: #710909; color: #710909;
background-color: #fff; background-color: #fff;
border-left: 26px solid #EE5757; border-left: 10px solid #EE5757;
white-space: pre; white-space: pre;
} }
#qunit-tests > li:last-child { #qunit-tests > li:last-child {
border-radius: 0 0 15px 15px; border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 15px 15px; -moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 15px; -webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 15px; -webkit-border-bottom-left-radius: 5px;
} }
#qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail { color: #000000; background-color: #EE5757; }
@ -216,6 +230,9 @@
border-bottom: 1px solid white; border-bottom: 1px solid white;
} }
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */ /** Fixture */
@ -223,4 +240,6 @@
position: absolute; position: absolute;
top: -10000px; top: -10000px;
left: -10000px; left: -10000px;
width: 1000px;
height: 1000px;
} }

2828
js/tests/vendor/qunit.js vendored
View File

@ -1,453 +1,226 @@
/** /*!
* QUnit 1.0.0 - A JavaScript Unit Testing Framework * QUnit 1.13.0
* http://qunitjs.com/
* *
* http://docs.jquery.com/QUnit * Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license
* http://jquery.org/license
* *
* Copyright (c) 2011 John Resig, Jörn Zaefferer * Date: 2014-01-04T17:09Z
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/ */
(function(window) { (function( window ) {
var defined = { var QUnit,
setTimeout: typeof window.setTimeout !== "undefined", assert,
sessionStorage: (function() { config,
try { onErrorFnPrev,
return !!sessionStorage.getItem; testId = 0,
} catch(e) { fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
return false; toString = Object.prototype.toString,
} hasOwn = Object.prototype.hasOwnProperty,
})() // Keep a local reference to Date (GH-283)
}; Date = window.Date,
setTimeout = window.setTimeout,
var testId = 0; defined = {
document: typeof window.document !== "undefined",
var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { setTimeout: typeof window.setTimeout !== "undefined",
this.name = name; sessionStorage: (function() {
this.testName = testName; var x = "qunit-test-string";
this.expected = expected; try {
this.testEnvironmentArg = testEnvironmentArg; sessionStorage.setItem( x, x );
this.async = async; sessionStorage.removeItem( x );
this.callback = callback; return true;
this.assertions = []; } catch( e ) {
}; return false;
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++;
tests.appendChild( li );
}
}, },
setup: function() { /**
if (this.module != config.previousModule) { * Provides a normalized error string, correcting an issue
if ( config.previousModule ) { * with IE 7 (and prior) where Error.prototype.toString is
runLoggingCallbacks('moduleDone', QUnit, { * not properly implemented
name: config.previousModule, *
failed: config.moduleStats.bad, * Based on http://es5.github.com/#x15.11.4.4
passed: config.moduleStats.all - config.moduleStats.bad, *
total: config.moduleStats.all * @param {String|Error} error
} ); * @return {String} error message
*/
errorString = function( error ) {
var name, message,
errorString = error.toString();
if ( errorString.substring( 0, 7 ) === "[object" ) {
name = error.name ? error.name.toString() : "Error";
message = error.message ? error.message.toString() : "";
if ( name && message ) {
return name + ": " + message;
} else if ( name ) {
return name;
} else if ( message ) {
return message;
} else {
return "Error";
} }
config.previousModule = this.module;
config.moduleStats = { all: 0, bad: 0 };
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);
}
runLoggingCallbacks( 'testStart', QUnit, {
name: this.testName,
module: this.module
});
// allow utility functions to access the current test environment
// TODO why??
QUnit.current_testEnvironment = this.testEnvironment;
try {
if ( !config.pollution ) {
saveGlobal();
}
this.testEnvironment.setup.call(this.testEnvironment);
} catch(e) {
QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
}
},
run: function() {
if ( this.async ) {
QUnit.stop();
}
if ( config.notrycatch ) {
this.callback.call(this.testEnvironment);
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) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
start();
}
}
},
teardown: function() {
try {
this.testEnvironment.teardown.call(this.testEnvironment);
checkPollution();
} catch(e) {
QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
}
},
finish: function() {
if ( this.expected && this.expected != this.assertions.length ) {
QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
}
var 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");
for ( var i = 0; i < this.assertions.length; i++ ) {
var assertion = this.assertions[i];
var li = document.createElement("li");
li.className = assertion.result ? "pass" : "fail";
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
ol.appendChild( li );
if ( assertion.result ) {
good++;
} else {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
// store result when possible
if ( QUnit.config.reorder && defined.sessionStorage ) {
if (bad) {
sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
} else {
sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
}
}
if (bad == 0) {
ol.style.display = "none";
}
var 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) {
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, "") });
}
});
var li = id(this.id);
li.className = bad ? "fail" : "pass";
li.removeChild( li.firstChild );
li.appendChild( b );
li.appendChild( a );
li.appendChild( ol );
} else { } else {
for ( var i = 0; i < this.assertions.length; i++ ) { return errorString;
if ( !this.assertions[i].result ) { }
bad++; },
config.stats.bad++; /**
config.moduleStats.bad++; * Makes a clone of an object using only Array or Object as base,
} * and copies over the own enumerable properties.
*
* @param {Object} obj
* @return {Object} New object with only the own properties (recursively).
*/
objectValues = function( obj ) {
// Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
/*jshint newcap: false */
var key, val,
vals = QUnit.is( "array", obj ) ? [] : {};
for ( key in obj ) {
if ( hasOwn.call( obj, key ) ) {
val = obj[key];
vals[key] = val === Object(val) ? objectValues(val) : val;
} }
} }
return vals;
};
try {
QUnit.reset();
} catch(e) {
fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
}
runLoggingCallbacks( 'testDone', QUnit, { // Root QUnit object.
name: this.testName, // `QUnit` initialized at top of scope
module: this.module, QUnit = {
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length
} );
},
queue: function() {
var test = this;
synchronize(function() {
test.init();
});
function run() {
// each of these can by async
synchronize(function() {
test.setup();
});
synchronize(function() {
test.run();
});
synchronize(function() {
test.teardown();
});
synchronize(function() {
test.finish();
});
}
// 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) {
run();
} else {
synchronize(run);
};
}
};
var QUnit = {
// call on start of module test to prepend name to all tests // call on start of module test to prepend name to all tests
module: function(name, testEnvironment) { module: function( name, testEnvironment ) {
config.currentModule = name; config.currentModule = name;
config.currentModuleTestEnviroment = testEnvironment; config.currentModuleTestEnvironment = testEnvironment;
config.modules[name] = true;
}, },
asyncTest: function(testName, expected, callback) { asyncTest: function( testName, expected, callback ) {
if ( arguments.length === 2 ) {
callback = expected;
expected = 0;
}
QUnit.test(testName, expected, callback, true);
},
test: function(testName, expected, callback, async) {
var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
if ( arguments.length === 2 ) { if ( arguments.length === 2 ) {
callback = expected; callback = expected;
expected = null; expected = null;
} }
// is 2nd argument a testEnvironment?
if ( expected && typeof expected === 'object') { QUnit.test( testName, expected, callback, true );
testEnvironmentArg = expected; },
test: function( testName, expected, callback, async ) {
var test,
nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
if ( arguments.length === 2 ) {
callback = expected;
expected = null; expected = null;
} }
if ( config.currentModule ) { if ( config.currentModule ) {
name = '<span class="module-name">' + config.currentModule + "</span>: " + name; nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
} }
if ( !validTest(config.currentModule + ": " + testName) ) { test = new Test({
nameHtml: nameHtml,
testName: testName,
expected: expected,
async: async,
callback: callback,
module: config.currentModule,
moduleTestEnvironment: config.currentModuleTestEnvironment,
stack: sourceFromStacktrace( 2 )
});
if ( !validTest( test ) ) {
return; return;
} }
var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
test.module = config.currentModule;
test.moduleTestEnvironment = config.currentModuleTestEnviroment;
test.queue(); test.queue();
}, },
/** // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
* Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. expect: function( asserts ) {
*/ if (arguments.length === 1) {
expect: function(asserts) { config.current.expected = asserts;
config.current.expected = asserts; } else {
}, return config.current.expected;
/**
* Asserts true.
* @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 );
config.current.assertions.push({
result: a,
message: msg
});
},
/**
* Checks 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)
*/
equal: function(actual, expected, message) {
QUnit.push(expected == actual, actual, expected, message);
},
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);
},
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);
},
notStrictEqual: function(actual, expected, message) {
QUnit.push(expected !== actual, actual, expected, message);
},
raises: function(block, expected, message) {
var actual, ok = false;
if (typeof expected === 'string') {
message = expected;
expected = null;
} }
try {
block();
} catch (e) {
actual = e;
}
if (actual) {
// we don't want to validate thrown error
if (!expected) {
ok = true;
// expected is a regexp
} else if (QUnit.objectType(expected) === "regexp") {
ok = expected.test(actual);
// expected is a constructor
} else if (actual instanceof expected) {
ok = true;
// expected is a validation function which returns true is validation passed
} else if (expected.call({}, actual) === true) {
ok = true;
}
}
QUnit.ok(ok, message);
}, },
start: function(count) { start: function( count ) {
config.semaphore -= count || 1; // QUnit hasn't been initialized yet.
if (config.semaphore > 0) { // Note: RequireJS (et al) may delay onLoad
// don't start until equal number of stop-calls if ( config.semaphore === undefined ) {
QUnit.begin(function() {
// This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
setTimeout(function() {
QUnit.start( count );
});
});
return; return;
} }
if (config.semaphore < 0) {
// ignore if start is called more often then stop 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; config.semaphore = 0;
QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
return;
} }
// A slight delay, to avoid any current callbacks // A slight delay, to avoid any current callbacks
if ( defined.setTimeout ) { if ( defined.setTimeout ) {
window.setTimeout(function() { setTimeout(function() {
if (config.semaphore > 0) { if ( config.semaphore > 0 ) {
return; return;
} }
if ( config.timeout ) { if ( config.timeout ) {
clearTimeout(config.timeout); clearTimeout( config.timeout );
} }
config.blocking = false; config.blocking = false;
process(); process( true );
}, 13); }, 13);
} else { } else {
config.blocking = false; config.blocking = false;
process(); process( true );
} }
}, },
stop: function(count) { stop: function( count ) {
config.semaphore += count || 1; config.semaphore += count || 1;
config.blocking = true; config.blocking = true;
if ( config.testTimeout && defined.setTimeout ) { if ( config.testTimeout && defined.setTimeout ) {
clearTimeout(config.timeout); clearTimeout( config.timeout );
config.timeout = window.setTimeout(function() { config.timeout = setTimeout(function() {
QUnit.ok( false, "Test timed out" ); QUnit.ok( false, "Test timed out" );
config.semaphore = 1; config.semaphore = 1;
QUnit.start(); QUnit.start();
}, config.testTimeout); }, config.testTimeout );
} }
} }
}; };
//We want access to the constructor's prototype // We use the prototype to distinguish between properties that should
// be exposed as globals (and in exports) and those that shouldn't
(function() { (function() {
function F(){}; function F() {}
F.prototype = QUnit; F.prototype = QUnit;
QUnit = new F(); 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; QUnit.constructor = F;
})(); }());
// Backwards compatibility, deprecated /**
QUnit.equals = QUnit.equal; * Config object: Maintain internal state
QUnit.same = QUnit.deepEqual; * Later exposed as QUnit.config
* `config` initialized at top of scope
// Maintain internal state */
var config = { config = {
// The queue of tests to run // The queue of tests to run
queue: [], queue: [],
@ -465,9 +238,28 @@ var config = {
// by default, modify document.title when suite is done // by default, modify document.title when suite is done
altertitle: true, altertitle: true,
urlConfig: ['noglobals', 'notrycatch'], // when enabled, all tests must call expect()
requireExpects: false,
//logging callback queues // 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."
}
],
// Set of all modules.
modules: {},
// logging callback queues
begin: [], begin: [],
done: [], done: [],
log: [], log: [],
@ -477,16 +269,17 @@ var config = {
moduleDone: [] moduleDone: []
}; };
// Load paramaters // Initialize more QUnit.config and QUnit.urlParams
(function() { (function() {
var location = window.location || { search: "", protocol: "file:" }, var i,
location = window.location || { search: "", protocol: "file:" },
params = location.search.slice( 1 ).split( "&" ), params = location.search.slice( 1 ).split( "&" ),
length = params.length, length = params.length,
urlParams = {}, urlParams = {},
current; current;
if ( params[ 0 ] ) { if ( params[ 0 ] ) {
for ( var i = 0; i < length; i++ ) { for ( i = 0; i < length; i++ ) {
current = params[ i ].split( "=" ); current = params[ i ].split( "=" );
current[ 0 ] = decodeURIComponent( current[ 0 ] ); current[ 0 ] = decodeURIComponent( current[ 0 ] );
// allow just a key to turn on a flag, e.g., test.html?noglobals // allow just a key to turn on a flag, e.g., test.html?noglobals
@ -496,44 +289,53 @@ var config = {
} }
QUnit.urlParams = urlParams; QUnit.urlParams = urlParams;
// String search anywhere in moduleName+testName
config.filter = urlParams.filter; 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 // 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' extend( QUnit, {
// object exists, in that case we assume we're in CommonJS
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
extend(window, QUnit);
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, {
config: config, config: config,
// Initialize the configuration options // Initialize the configuration options
init: function() { init: function() {
extend(config, { extend( config, {
stats: { all: 0, bad: 0 }, stats: { all: 0, bad: 0 },
moduleStats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 },
started: +new Date, started: +new Date(),
updateRate: 1000, updateRate: 1000,
blocking: false, blocking: false,
autostart: true, autostart: true,
autorun: false, autorun: false,
filter: "", filter: "",
queue: [], queue: [],
semaphore: 0 semaphore: 1
}); });
var tests = id( "qunit-tests" ), var tests, banner, result,
banner = id( "qunit-banner" ), qunit = id( "qunit" );
result = id( "qunit-testresult" );
if ( qunit ) {
qunit.innerHTML =
"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
"<h2 id='qunit-banner'></h2>" +
"<div id='qunit-testrunner-toolbar'></div>" +
"<h2 id='qunit-userAgent'></h2>" +
"<ol id='qunit-tests'></ol>";
}
tests = id( "qunit-tests" );
banner = id( "qunit-banner" );
result = id( "qunit-testresult" );
if ( tests ) { if ( tests ) {
tests.innerHTML = ""; tests.innerHTML = "";
@ -552,112 +354,101 @@ extend(QUnit, {
result.id = "qunit-testresult"; result.id = "qunit-testresult";
result.className = "result"; result.className = "result";
tests.parentNode.insertBefore( result, tests ); 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.
* Resets the test setup. Useful for tests that modify the DOM. /*
* DEPRECATED: Use multiple tests instead of resetting inside a test.
* If jQuery is available, uses jQuery's html(), otherwise just innerHTML. Use testStart or testDone for custom cleanup.
*/ This method will throw an error in 2.0, and will be removed in 2.1
*/
reset: function() { reset: function() {
if ( window.jQuery ) { var fixture = id( "qunit-fixture" );
jQuery( "#qunit-fixture" ).html( config.fixture ); if ( fixture ) {
} else { fixture.innerHTML = config.fixture;
var main = id( 'qunit-fixture' );
if ( main ) {
main.innerHTML = config.fixture;
}
}
},
/**
* Trigger an event on an element.
*
* @example triggerEvent( document.body, "click" );
*
* @param DOMElement elem
* @param String type
*/
triggerEvent: function( elem, type, event ) {
if ( document.createEvent ) {
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 );
} else if ( elem.fireEvent ) {
elem.fireEvent("on"+type);
} }
}, },
// Safe object type checking // Safe object type checking
is: function( type, obj ) { is: function( type, obj ) {
return QUnit.objectType( obj ) == type; return QUnit.objectType( obj ) === type;
}, },
objectType: function( obj ) { objectType: function( obj ) {
if (typeof obj === "undefined") { if ( typeof obj === "undefined" ) {
return "undefined"; return "undefined";
// consider: typeof null === object
}
if (obj === null) {
return "null";
} }
var type = Object.prototype.toString.call( obj ) // Consider: typeof null === object
.match(/^\[object\s(.*)\]$/)[1] || ''; if ( obj === null ) {
return "null";
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();
} }
if (typeof obj === "object") {
return "object"; var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
type = match && match[1] || "";
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";
} }
return undefined; return undefined;
}, },
push: function(result, actual, expected, message) { push: function( result, actual, expected, message ) {
var details = { if ( !config.current ) {
result: result, throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
message: message,
actual: actual,
expected: expected
};
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(); var output, source,
if (source) { details = {
details.source = source; module: config.current.module,
output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>'; name: config.current.testName,
result: result,
message: message,
actual: actual,
expected: expected
};
message = escapeText( message ) || ( result ? "okay" : "failed" );
message = "<span class='test-message'>" + message + "</span>";
output = message;
if ( !result ) {
expected = escapeText( QUnit.jsDump.parse(expected) );
actual = escapeText( 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>";
} }
}
output += "</table>";
runLoggingCallbacks( 'log', QUnit, details ); source = sourceFromStacktrace();
if ( source ) {
details.source = source;
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
}
output += "</table>";
}
runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({ config.current.assertions.push({
result: !!result, result: !!result,
@ -665,257 +456,507 @@ extend(QUnit, {
}); });
}, },
pushFailure: function( message, source, actual ) {
if ( !config.current ) {
throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
}
var output,
details = {
module: config.current.module,
name: config.current.testName,
result: false,
message: message
};
message = escapeText( 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>" + escapeText( actual ) + "</pre></td></tr>";
}
if ( source ) {
details.source = source;
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
}
output += "</table>";
runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
result: false,
message: output
});
},
url: function( params ) { url: function( params ) {
params = extend( extend( {}, QUnit.urlParams ), params ); params = extend( extend( {}, QUnit.urlParams ), params );
var querystring = "?", var key,
key; querystring = "?";
for ( key in params ) { for ( key in params ) {
querystring += encodeURIComponent( key ) + "=" + if ( hasOwn.call( params, key ) ) {
encodeURIComponent( params[ key ] ) + "&"; querystring += encodeURIComponent( key ) + "=" +
encodeURIComponent( params[ key ] ) + "&";
}
} }
return window.location.pathname + querystring.slice( 0, -1 ); return window.location.protocol + "//" + window.location.host +
window.location.pathname + querystring.slice( 0, -1 );
}, },
extend: extend, extend: extend,
id: id, id: id,
addEvent: addEvent addEvent: addEvent,
addClass: addClass,
hasClass: hasClass,
removeClass: removeClass
// 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 * @deprecated: Created for backwards compatibility with test runner that set the hook function
//QUnit object, which is a deprecated way of using the callbacks. * into QUnit.{hook}, instead of invoking it and passing the hook function.
extend(QUnit.constructor.prototype, { * 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 // Logging callbacks; all receive a single argument with the listed properties
// run test/logs.html for any related changes // run test/logs.html for any related changes
begin: registerLoggingCallback('begin'), begin: registerLoggingCallback( "begin" ),
// done: { failed, passed, total, runtime } // done: { failed, passed, total, runtime }
done: registerLoggingCallback('done'), done: registerLoggingCallback( "done" ),
// log: { result, actual, expected, message } // log: { result, actual, expected, message }
log: registerLoggingCallback('log'), log: registerLoggingCallback( "log" ),
// testStart: { name } // testStart: { name }
testStart: registerLoggingCallback('testStart'), testStart: registerLoggingCallback( "testStart" ),
// testDone: { name, failed, passed, total }
testDone: registerLoggingCallback('testDone'), // testDone: { name, failed, passed, total, runtime }
testDone: registerLoggingCallback( "testDone" ),
// moduleStart: { name } // moduleStart: { name }
moduleStart: registerLoggingCallback('moduleStart'), moduleStart: registerLoggingCallback( "moduleStart" ),
// moduleDone: { name, failed, passed, total } // moduleDone: { name, failed, passed, total }
moduleDone: registerLoggingCallback('moduleDone') moduleDone: registerLoggingCallback( "moduleDone" )
}); });
if ( typeof document === "undefined" || document.readyState === "complete" ) { if ( !defined.document || document.readyState === "complete" ) {
config.autorun = true; config.autorun = true;
} }
QUnit.load = function() { QUnit.load = function() {
runLoggingCallbacks( 'begin', QUnit, {} ); runLoggingCallbacks( "begin", QUnit, {} );
// Initialize the config, saving the execution queue // Initialize the config, saving the execution queue
var oldconfig = extend({}, config); var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
numModules = 0,
moduleNames = [],
moduleFilterHtml = "",
urlConfigHtml = "",
oldconfig = extend( {}, config );
QUnit.init(); QUnit.init();
extend(config, oldconfig); extend(config, oldconfig);
config.blocking = false; config.blocking = false;
var urlConfigHtml = '', len = config.urlConfig.length; 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>';
}
var userAgent = id("qunit-userAgent"); 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-" + escapeText( val.id ) +
"' name='" + escapeText( val.id ) +
"' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
" title='" + escapeText( val.tooltip ) +
"'><label for='qunit-urlconfig-" + escapeText( val.id ) +
"' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
}
for ( i in config.modules ) {
if ( config.modules.hasOwnProperty( i ) ) {
moduleNames.push(i);
}
}
numModules = moduleNames.length;
moduleNames.sort( function( a, b ) {
return a.localeCompare( b );
});
moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
( config.module === undefined ? "selected='selected'" : "" ) +
">< All Modules ></option>";
for ( i = 0; i < numModules; i++) {
moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " +
( config.module === moduleNames[i] ? "selected='selected'" : "" ) +
">" + escapeText(moduleNames[i]) + "</option>";
}
moduleFilterHtml += "</select>";
// `userAgent` initialized at top of scope
userAgent = id( "qunit-userAgent" );
if ( userAgent ) { if ( userAgent ) {
userAgent.innerHTML = navigator.userAgent; userAgent.innerHTML = navigator.userAgent;
} }
var banner = id("qunit-header");
// `banner` initialized at top of scope
banner = id( "qunit-header" );
if ( banner ) { if ( banner ) {
banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml; banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
addEvent( banner, "change", function( event ) {
var params = {};
params[ event.target.name ] = event.target.checked ? true : undefined;
window.location = QUnit.url( params );
});
} }
var toolbar = id("qunit-testrunner-toolbar"); // `toolbar` initialized at top of scope
toolbar = id( "qunit-testrunner-toolbar" );
if ( toolbar ) { if ( toolbar ) {
var filter = document.createElement("input"); // `filter` initialized at top of scope
filter = document.createElement( "input" );
filter.type = "checkbox"; filter.type = "checkbox";
filter.id = "qunit-filter-pass"; filter.id = "qunit-filter-pass";
addEvent( filter, "click", function() { addEvent( filter, "click", function() {
var ol = document.getElementById("qunit-tests"); var tmp,
ol = id( "qunit-tests" );
if ( filter.checked ) { if ( filter.checked ) {
ol.className = ol.className + " hidepass"; ol.className = ol.className + " hidepass";
} else { } else {
var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
ol.className = tmp.replace(/ hidepass /, " "); ol.className = tmp.replace( / hidepass /, " " );
} }
if ( defined.sessionStorage ) { if ( defined.sessionStorage ) {
if (filter.checked) { if (filter.checked) {
sessionStorage.setItem("qunit-filter-passed-tests", "true"); sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
} else { } 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; filter.checked = true;
var ol = document.getElementById("qunit-tests"); // `ol` initialized at top of scope
ol = id( "qunit-tests" );
ol.className = ol.className + " hidepass"; ol.className = ol.className + " hidepass";
} }
toolbar.appendChild( filter ); toolbar.appendChild( filter );
var label = document.createElement("label"); // `label` initialized at top of scope
label.setAttribute("for", "qunit-filter-pass"); label = document.createElement( "label" );
label.setAttribute( "for", "qunit-filter-pass" );
label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
label.innerHTML = "Hide passed tests"; label.innerHTML = "Hide passed tests";
toolbar.appendChild( label ); toolbar.appendChild( label );
urlConfigCheckboxesContainer = document.createElement("span");
urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
// For oldIE support:
// * Add handlers to the individual elements instead of the container
// * Use "click" instead of "change"
// * Fallback from event.target to event.srcElement
addEvents( urlConfigCheckboxes, "click", function( event ) {
var params = {},
target = event.target || event.srcElement;
params[ target.name ] = target.checked ? true : undefined;
window.location = QUnit.url( params );
});
toolbar.appendChild( urlConfigCheckboxesContainer );
if (numModules > 1) {
moduleFilter = document.createElement( "span" );
moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
moduleFilter.innerHTML = moduleFilterHtml;
addEvent( moduleFilter.lastChild, "change", function() {
var selectBox = moduleFilter.getElementsByTagName("select")[0],
selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
window.location = QUnit.url({
module: ( selectedModule === "" ) ? undefined : selectedModule,
// Remove any existing filters
filter: undefined,
testNumber: undefined
});
});
toolbar.appendChild(moduleFilter);
}
} }
var main = id('qunit-fixture'); // `main` initialized at top of scope
main = id( "qunit-fixture" );
if ( main ) { if ( main ) {
config.fixture = main.innerHTML; config.fixture = main.innerHTML;
} }
if (config.autostart) { if ( config.autostart ) {
QUnit.start(); QUnit.start();
} }
}; };
addEvent(window, "load", QUnit.load); if ( defined.document ) {
addEvent( window, "load", QUnit.load );
}
// `onErrorFnPrev` initialized at top of scope
// Preserve other handlers
onErrorFnPrev = window.onerror;
// Cover uncaught exceptions
// Returning true will suppress the default browser handler,
// returning false will let it run.
window.onerror = function ( error, filePath, linerNr ) {
var ret = false;
if ( onErrorFnPrev ) {
ret = onErrorFnPrev( error, filePath, linerNr );
}
// Treat return value as window.onerror itself does,
// Only do our handling if not suppressed.
if ( ret !== true ) {
if ( QUnit.config.current ) {
if ( QUnit.config.current.ignoreGlobalErrors ) {
return true;
}
QUnit.pushFailure( error, filePath + ":" + linerNr );
} else {
QUnit.test( "global failure", extend( function() {
QUnit.pushFailure( error, filePath + ":" + linerNr );
}, { validTest: validTest } ) );
}
return false;
}
return ret;
};
function done() { function done() {
config.autorun = true; config.autorun = true;
// Log the last module results // Log the last module results
if ( config.currentModule ) { if ( config.previousModule ) {
runLoggingCallbacks( 'moduleDone', QUnit, { runLoggingCallbacks( "moduleDone", QUnit, {
name: config.currentModule, name: config.previousModule,
failed: config.moduleStats.bad, failed: config.moduleStats.bad,
passed: config.moduleStats.all - config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad,
total: config.moduleStats.all total: config.moduleStats.all
} ); });
} }
delete config.previousModule;
var banner = id("qunit-banner"), var i, key,
tests = id("qunit-tests"), banner = id( "qunit-banner" ),
runtime = +new Date - config.started, tests = id( "qunit-tests" ),
runtime = +new Date() - config.started,
passed = config.stats.all - config.stats.bad, passed = config.stats.all - config.stats.bad,
html = [ html = [
'Tests completed in ', "Tests completed in ",
runtime, runtime,
' milliseconds.<br/>', " milliseconds.<br/>",
'<span class="passed">', "<span class='passed'>",
passed, passed,
'</span> tests of <span class="total">', "</span> assertions of <span class='total'>",
config.stats.all, config.stats.all,
'</span> passed, <span class="failed">', "</span> passed, <span class='failed'>",
config.stats.bad, config.stats.bad,
'</span> failed.' "</span> failed."
].join(''); ].join( "" );
if ( banner ) { if ( banner ) {
banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
} }
if ( tests ) { if ( tests ) {
id( "qunit-testresult" ).innerHTML = html; id( "qunit-testresult" ).innerHTML = html;
} }
if ( config.altertitle && typeof document !== "undefined" && document.title ) { if ( config.altertitle && defined.document && document.title ) {
// show ✖ for good, ✔ for bad suite result in title // show ✖ for good, ✔ for bad suite result in title
// use escape sequences in case file gets loaded with non-utf-8-charset // use escape sequences in case file gets loaded with non-utf-8-charset
document.title = [ document.title = [
(config.stats.bad ? "\u2716" : "\u2714"), ( config.stats.bad ? "\u2716" : "\u2714" ),
document.title.replace(/^[\u2714\u2716] /i, "") document.title.replace( /^[\u2714\u2716] /i, "" )
].join(" "); ].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 );
}
}
}
// scroll back to top to show results
if ( window.scrollTo ) {
window.scrollTo(0, 0);
}
runLoggingCallbacks( "done", QUnit, {
failed: config.stats.bad, failed: config.stats.bad,
passed: passed, passed: passed,
total: config.stats.all, total: config.stats.all,
runtime: runtime runtime: runtime
} ); });
} }
function validTest( name ) { /** @return Boolean: true if this test should be ran */
var filter = config.filter, function validTest( test ) {
run = false; var include,
filter = config.filter && config.filter.toLowerCase(),
module = config.module && config.module.toLowerCase(),
fullName = (test.module + ": " + test.testName).toLowerCase();
// Internally-generated tests are always valid
if ( test.callback && test.callback.validTest === validTest ) {
delete test.callback.validTest;
return true;
}
if ( config.testNumber ) {
return test.testNumber === config.testNumber;
}
if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
return false;
}
if ( !filter ) { if ( !filter ) {
return true; return true;
} }
var not = filter.charAt( 0 ) === "!"; include = filter.charAt( 0 ) !== "!";
if ( not ) { if ( !include ) {
filter = filter.slice( 1 ); filter = filter.slice( 1 );
} }
if ( name.indexOf( filter ) !== -1 ) { // If the filter matches, we need to honour include
return !not; if ( fullName.indexOf( filter ) !== -1 ) {
return include;
} }
if ( not ) { // Otherwise, do the opposite
run = true; return !include;
}
return run;
} }
// so far supports only Firefox, Chrome and Opera (buggy) // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
// could be extended in the future to use something like https://github.com/csnover/TraceKit // Later Safari and IE10 are supposed to support error.stack as well
function sourceFromStacktrace() { // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) {
offset = offset === undefined ? 3 : offset;
var stack, include, i;
if ( e.stacktrace ) {
// Opera
return e.stacktrace.split( "\n" )[ offset + 3 ];
} else if ( e.stack ) {
// Firefox, Chrome
stack = e.stack.split( "\n" );
if (/^error$/i.test( stack[0] ) ) {
stack.shift();
}
if ( fileName ) {
include = [];
for ( i = offset; i < stack.length; i++ ) {
if ( stack[ i ].indexOf( fileName ) !== -1 ) {
break;
}
include.push( stack[ i ] );
}
if ( include.length ) {
return include.join( "\n" );
}
}
return stack[ offset ];
} else if ( e.sourceURL ) {
// Safari, PhantomJS
// hopefully one day Safari provides actual stacktraces
// exclude useless self-reference for generated Error objects
if ( /qunit.js$/.test( e.sourceURL ) ) {
return;
}
// for actual exceptions, this is useful
return e.sourceURL + ":" + e.line;
}
}
function sourceFromStacktrace( offset ) {
try { try {
throw new Error(); throw new Error();
} catch ( e ) { } catch ( e ) {
if (e.stacktrace) { return extractStacktrace( e, offset );
// Opera
return e.stacktrace.split("\n")[6];
} else if (e.stack) {
// Firefox, Chrome
return e.stack.split("\n")[4];
} else if (e.sourceURL) {
// Safari, PhantomJS
// TODO sourceURL points at the 'throw new Error' line above, useless
//return e.sourceURL + ":" + e.line;
}
} }
} }
function escapeInnerText(s) { /**
if (!s) { * Escape text for attribute or text content.
*/
function escapeText( s ) {
if ( !s ) {
return ""; return "";
} }
s = s + ""; s = s + "";
return s.replace(/[\&<>]/g, function(s) { // Both single quotes and double quotes (for attributes)
switch(s) { return s.replace( /['"<>&]/g, function( s ) {
case "&": return "&amp;"; switch( s ) {
case "<": return "&lt;"; case "'":
case ">": return "&gt;"; return "&#039;";
default: return s; case "\"":
return "&quot;";
case "<":
return "&lt;";
case ">":
return "&gt;";
case "&":
return "&amp;";
} }
}); });
} }
function synchronize( callback ) { function synchronize( callback, last ) {
config.queue.push( callback ); config.queue.push( callback );
if ( config.autorun && !config.blocking ) { if ( config.autorun && !config.blocking ) {
process(); process( last );
} }
} }
function process() { function process( last ) {
var start = (new Date()).getTime(); function next() {
process( last );
}
var start = new Date().getTime();
config.depth = config.depth ? config.depth + 1 : 1;
while ( config.queue.length && !config.blocking ) { while ( config.queue.length && !config.blocking ) {
if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
config.queue.shift()(); config.queue.shift()();
} else { } else {
window.setTimeout( process, 13 ); setTimeout( next, 13 );
break; break;
} }
} }
if (!config.blocking && !config.queue.length) { config.depth--;
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
done(); done();
} }
} }
@ -925,33 +966,44 @@ function saveGlobal() {
if ( config.noglobals ) { if ( config.noglobals ) {
for ( var key in window ) { for ( var key in window ) {
config.pollution.push( key ); if ( hasOwn.call( window, key ) ) {
// in Opera sometimes DOM element ids show up here, ignore them
if ( /^qunit-test-output/.test( key ) ) {
continue;
}
config.pollution.push( key );
}
} }
} }
} }
function checkPollution( name ) { function checkPollution() {
var old = config.pollution; var newGlobals,
deletedGlobals,
old = config.pollution;
saveGlobal(); saveGlobal();
var newGlobals = diff( config.pollution, old ); newGlobals = diff( config.pollution, old );
if ( newGlobals.length > 0 ) { if ( newGlobals.length > 0 ) {
ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
} }
var deletedGlobals = diff( old, config.pollution ); deletedGlobals = diff( old, config.pollution );
if ( deletedGlobals.length > 0 ) { if ( deletedGlobals.length > 0 ) {
ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
} }
} }
// returns a new Array with the elements that are in a but not in b // returns a new Array with the elements that are in a but not in b
function diff( a, b ) { function diff( a, b ) {
var result = a.slice(); var i, j,
for ( var i = 0; i < result.length; i++ ) { result = a.slice();
for ( var j = 0; j < b.length; j++ ) {
for ( i = 0; i < result.length; i++ ) {
for ( j = 0; j < b.length; j++ ) {
if ( result[i] === b[j] ) { if ( result[i] === b[j] ) {
result.splice(i, 1); result.splice( i, 1 );
i--; i--;
break; break;
} }
@ -960,444 +1012,100 @@ function diff( a, b ) {
return result; return result;
} }
function fail(message, exception, callback) { function extend( a, b ) {
if ( typeof console !== "undefined" && console.error && console.warn ) {
console.error(message);
console.error(exception);
console.warn(callback.toString());
} else if ( window.opera && opera.postError ) {
opera.postError(message, exception, callback.toString);
}
}
function extend(a, b) {
for ( var prop in b ) { for ( var prop in b ) {
if ( b[prop] === undefined ) { if ( hasOwn.call( b, prop ) ) {
delete a[prop]; // Avoid "Member not found" error in IE8 caused by messing with window.constructor
} else { if ( !( prop === "constructor" && a === window ) ) {
a[prop] = b[prop]; if ( b[ prop ] === undefined ) {
delete a[ prop ];
} else {
a[ prop ] = b[ prop ];
}
}
} }
} }
return a; return a;
} }
function addEvent(elem, type, fn) { /**
* @param {HTMLElement} elem
* @param {string} type
* @param {Function} fn
*/
function addEvent( elem, type, fn ) {
if ( elem.addEventListener ) { if ( elem.addEventListener ) {
// Standards-based browsers
elem.addEventListener( type, fn, false ); elem.addEventListener( type, fn, false );
} else if ( elem.attachEvent ) { } else if ( elem.attachEvent ) {
// support: IE <9
elem.attachEvent( "on" + type, fn ); elem.attachEvent( "on" + type, fn );
} else { } else {
fn();
// Caller must ensure support for event listeners is present
throw new Error( "addEvent() was called in a context without event listener support" );
} }
} }
function id(name) { /**
return !!(typeof document !== "undefined" && document && document.getElementById) && * @param {Array|NodeList} elems
document.getElementById( name ); * @param {string} type
* @param {Function} fn
*/
function addEvents( elems, type, fn ) {
var i = elems.length;
while ( i-- ) {
addEvent( elems[i], type, fn );
}
} }
function registerLoggingCallback(key){ function hasClass( elem, name ) {
return function(callback){ return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
}
function addClass( elem, name ) {
if ( !hasClass( elem, name ) ) {
elem.className += (elem.className ? " " : "") + name;
}
}
function removeClass( elem, name ) {
var set = " " + elem.className + " ";
// Class name may appear multiple times
while ( set.indexOf(" " + name + " ") > -1 ) {
set = set.replace(" " + name + " " , " ");
}
// If possible, trim it for prettiness, but not necessarily
elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
}
function id( name ) {
return defined.document && document.getElementById && document.getElementById( name );
}
function registerLoggingCallback( key ) {
return function( callback ) {
config[key].push( callback ); config[key].push( callback );
}; };
} }
// Supports deprecated method of completely overwriting logging callbacks // Supports deprecated method of completely overwriting logging callbacks
function runLoggingCallbacks(key, scope, args) { function runLoggingCallbacks( key, scope, args ) {
//debugger; var i, callbacks;
var callbacks; if ( QUnit.hasOwnProperty( key ) ) {
if ( QUnit.hasOwnProperty(key) ) { QUnit[ key ].call(scope, args );
QUnit[key].call(scope, args);
} else { } else {
callbacks = config[key]; callbacks = config[ key ];
for( var i = 0; i < callbacks.length; i++ ) { for ( i = 0; i < callbacks.length; i++ ) {
callbacks[i].call( scope, args ); callbacks[ i ].call( scope, args );
} }
} }
} }
// Test for equality any JavaScript type. // from jquery.js
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = function () {
var innerEquiv; // the real equiv function
var callers = []; // stack to decide between skip/abort functions
var parents = []; // stack to avoiding loops from circular referencing
// Call the o related callback with the given arguments.
function bindCallbacks(o, callbacks, args) {
var prop = QUnit.objectType(o);
if (prop) {
if (QUnit.objectType(callbacks[prop]) === "function") {
return callbacks[prop].apply(callbacks, args);
} else {
return callbacks[prop]; // or undefined
}
}
}
var callbacks = function () {
// for string, boolean, number and null
function useStrictEquality(b, a) {
if (b instanceof a.constructor || a instanceof b.constructor) {
// to catch short annotaion VS 'new' annotation of a
// declaration
// e.g. var i = 1;
// var j = new Number(1);
return a == b;
} else {
return a === b;
}
}
return {
"string" : useStrictEquality,
"boolean" : useStrictEquality,
"number" : useStrictEquality,
"null" : useStrictEquality,
"undefined" : useStrictEquality,
"nan" : function(b) {
return isNaN(b);
},
"date" : function(b, a) {
return QUnit.objectType(b) === "date"
&& a.valueOf() === b.valueOf();
},
"regexp" : function(b, a) {
return QUnit.objectType(b) === "regexp"
&& a.source === b.source && // the regex itself
a.global === b.global && // and its modifers
// (gmi) ...
a.ignoreCase === b.ignoreCase
&& a.multiline === b.multiline;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function" : function() {
var caller = callers[callers.length - 1];
return caller !== Object && typeof caller !== "undefined";
},
"array" : function(b, a) {
var i, j, loop;
var len;
// b could be an object literal here
if (!(QUnit.objectType(b) === "array")) {
return false;
}
len = a.length;
if (len !== b.length) { // safe and faster
return false;
}
// track reference to avoid circular references
parents.push(a);
for (i = 0; i < len; i++) {
loop = false;
for (j = 0; j < parents.length; j++) {
if (parents[j] === a[i]) {
loop = true;// dont rewalk array
}
}
if (!loop && !innerEquiv(a[i], b[i])) {
parents.pop();
return false;
}
}
parents.pop();
return true;
},
"object" : function(b, a) {
var i, j, loop;
var eq = true; // unless we can proove it
var aProperties = [], bProperties = []; // collection of
// strings
// comparing constructors is more strict than using
// instanceof
if (a.constructor !== b.constructor) {
return false;
}
// stack constructor before traversing properties
callers.push(a.constructor);
// track reference to avoid circular references
parents.push(a);
for (i in a) { // be strict: don't ensures hasOwnProperty
// and go deep
loop = false;
for (j = 0; j < parents.length; j++) {
if (parents[j] === a[i])
loop = true; // don't go down the same path
// twice
}
aProperties.push(i); // collect a's properties
if (!loop && !innerEquiv(a[i], b[i])) {
eq = false;
break;
}
}
callers.pop(); // unstack, we are done
parents.pop();
for (i in b) {
bProperties.push(i); // collect b's properties
}
// Ensures identical properties name
return eq
&& innerEquiv(aProperties.sort(), bProperties
.sort());
}
};
}();
innerEquiv = function() { // can take multiple arguments
var args = Array.prototype.slice.apply(arguments);
if (args.length < 2) {
return true; // end transition
}
return (function(a, b) {
if (a === b) {
return true; // catch the most you can
} else if (a === null || b === null || typeof a === "undefined"
|| typeof b === "undefined"
|| QUnit.objectType(a) !== QUnit.objectType(b)) {
return false; // don't lose time with error prone cases
} else {
return bindCallbacks(a, callbacks, [ b, a ]);
}
// apply transition with (1..n) arguments
})(args[0], args[1])
&& arguments.callee.apply(this, args.splice(1,
args.length - 1));
};
return innerEquiv;
}();
/**
* jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
* http://flesler.blogspot.com Licensed under BSD
* (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
*
* @projectDescription Advanced and extensible data dumping for Javascript.
* @version 1.0.0
* @author Ariel Flesler
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
*/
QUnit.jsDump = (function() {
function quote( str ) {
return '"' + str.toString().replace(/"/g, '\\"') + '"';
};
function literal( o ) {
return o + '';
};
function join( pre, arr, post ) {
var s = jsDump.separator(),
base = jsDump.indent(),
inner = jsDump.indent(1);
if ( arr.join )
arr = arr.join( ',' + s + inner );
if ( !arr )
return pre + post;
return [ pre, inner + arr, base + post ].join(s);
};
function array( arr, stack ) {
var i = arr.length, ret = Array(i);
this.up();
while ( i-- )
ret[i] = this.parse( arr[i] , undefined , stack);
this.down();
return join( '[', ret, ']' );
};
var reName = /^function (\w+)/;
var jsDump = {
parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
stack = stack || [ ];
var parser = this.parsers[ type || this.typeOf(obj) ];
type = typeof parser;
var inStack = inArray(obj, stack);
if (inStack != -1) {
return 'recursion('+(inStack - stack.length)+')';
}
//else
if (type == 'function') {
stack.push(obj);
var res = parser.call( this, obj, stack );
stack.pop();
return res;
}
// else
return (type == 'string') ? parser : this.parsers.error;
},
typeOf:function( obj ) {
var type;
if ( obj === null ) {
type = "null";
} else if (typeof obj === "undefined") {
type = "undefined";
} else if (QUnit.is("RegExp", obj)) {
type = "regexp";
} else if (QUnit.is("Date", obj)) {
type = "date";
} else if (QUnit.is("Function", obj)) {
type = "function";
} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
type = "window";
} else if (obj.nodeType === 9) {
type = "document";
} else if (obj.nodeType) {
type = "node";
} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
type = "array";
} else {
type = typeof obj;
}
return type;
},
separator:function() {
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
},
indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
if ( !this.multiline )
return '';
var chr = this.indentChar;
if ( this.HTML )
chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
return Array( this._depth_ + (extra||0) ).join(chr);
},
up:function( a ) {
this._depth_ += a || 1;
},
down:function( a ) {
this._depth_ -= a || 1;
},
setParser:function( name, parser ) {
this.parsers[name] = parser;
},
// The next 3 are exposed so you can use them
quote:quote,
literal:literal,
join:join,
//
_depth_: 1,
// This is the list of parsers, to modify them, use jsDump.setParser
parsers:{
window: '[Window]',
document: '[Document]',
error:'[ERROR]', //when no parser is found, shouldn't happen
unknown: '[Unknown]',
'null':'null',
'undefined':'undefined',
'function':function( fn ) {
var ret = 'function',
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
if ( name )
ret += ' ' + name;
ret += '(';
ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
},
array: array,
nodelist: array,
arguments: array,
object:function( map, stack ) {
var ret = [ ];
QUnit.jsDump.up();
for ( var key in map ) {
var val = map[key];
ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
}
QUnit.jsDump.down();
return join( '{', ret, '}' );
},
node:function( node ) {
var open = QUnit.jsDump.HTML ? '&lt;' : '<',
close = QUnit.jsDump.HTML ? '&gt;' : '>';
var tag = node.nodeName.toLowerCase(),
ret = open + tag;
for ( var a in QUnit.jsDump.DOMAttrs ) {
var val = node[QUnit.jsDump.DOMAttrs[a]];
if ( val )
ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
}
return ret + close + open + '/' + tag + close;
},
functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
var l = fn.length;
if ( !l ) return '';
var args = Array(l);
while ( l-- )
args[l] = String.fromCharCode(97+l);//97 is 'a'
return ' ' + args.join(', ') + ' ';
},
key:quote, //object calls it internally, the key part of an item in a map
functionCode:'[code]', //function calls it internally, it's the content of the function
attribute:quote, //node calls it internally, it's an html attribute value
string:quote,
date:quote,
regexp:literal, //regex
number:literal,
'boolean':literal
},
DOMAttrs:{//attributes to dump from nodes, name=>realName
id:'id',
name:'name',
'class':'className'
},
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
indentChar:' ',//indentation unit
multiline:true //if true, items in a collection, are separated by a \n, else just a space.
};
return jsDump;
})();
// from Sizzle.js
function getText( elems ) {
var ret = "", elem;
for ( var i = 0; elems[i]; i++ ) {
elem = elems[i];
// Get the text from text nodes and CDATA nodes
if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
ret += elem.nodeValue;
// Traverse everything else, except comment nodes
} else if ( elem.nodeType !== 8 ) {
ret += getText( elem.childNodes );
}
}
return ret;
};
//from jquery.js
function inArray( elem, array ) { function inArray( elem, array ) {
if ( array.indexOf ) { if ( array.indexOf ) {
return array.indexOf( elem ); return array.indexOf( elem );
@ -1412,6 +1120,930 @@ function inArray( elem, array ) {
return -1; return -1;
} }
function Test( settings ) {
extend( this, settings );
this.assertions = [];
this.testNumber = ++Test.count;
}
Test.count = 0;
Test.prototype = {
init: function() {
var a, b, li,
tests = id( "qunit-tests" );
if ( tests ) {
b = document.createElement( "strong" );
b.innerHTML = this.nameHtml;
// `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 (
// Emit moduleStart when we're switching from one module to another
this.module !== config.previousModule ||
// They could be equal (both undefined) but if the previousModule property doesn't
// yet exist it means this is the first test in a suite that isn't wrapped in a
// module, in which case we'll just emit a moduleStart event for 'undefined'.
// Without this, reporters can get testStart before moduleStart which is a problem.
!hasOwn.call( config, "previousModule" )
) {
if ( hasOwn.call( config, "previousModule" ) ) {
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, {
name: this.module
});
}
config.current = this;
this.testEnvironment = extend({
setup: function() {},
teardown: function() {}
}, this.moduleTestEnvironment );
this.started = +new Date();
runLoggingCallbacks( "testStart", QUnit, {
name: this.testName,
module: this.module
});
/*jshint camelcase:false */
/**
* Expose the current test environment.
*
* @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
*/
QUnit.current_testEnvironment = this.testEnvironment;
/*jshint camelcase:true */
if ( !config.pollution ) {
saveGlobal();
}
if ( config.notrycatch ) {
this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
return;
}
try {
this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
} catch( e ) {
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
}
},
run: function() {
config.current = this;
var running = id( "qunit-testresult" );
if ( running ) {
running.innerHTML = "Running: <br/>" + this.nameHtml;
}
if ( this.async ) {
QUnit.stop();
}
this.callbackStarted = +new Date();
if ( config.notrycatch ) {
this.callback.call( this.testEnvironment, QUnit.assert );
this.callbackRuntime = +new Date() - this.callbackStarted;
return;
}
try {
this.callback.call( this.testEnvironment, QUnit.assert );
this.callbackRuntime = +new Date() - this.callbackStarted;
} catch( e ) {
this.callbackRuntime = +new Date() - this.callbackStarted;
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
// else next test will carry the responsibility
saveGlobal();
// Restart the tests if they're blocking
if ( config.blocking ) {
QUnit.start();
}
}
},
teardown: function() {
config.current = this;
if ( config.notrycatch ) {
if ( typeof this.callbackRuntime === "undefined" ) {
this.callbackRuntime = +new Date() - this.callbackStarted;
}
this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
return;
} else {
try {
this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
} catch( e ) {
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
}
}
checkPollution();
},
finish: function() {
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 i, assertion, a, b, time, li, ol,
test = this,
good = 0,
bad = 0,
tests = id( "qunit-tests" );
this.runtime = +new Date() - this.started;
config.stats.all += this.assertions.length;
config.moduleStats.all += this.assertions.length;
if ( tests ) {
ol = document.createElement( "ol" );
ol.className = "qunit-assert-list";
for ( i = 0; i < this.assertions.length; i++ ) {
assertion = this.assertions[i];
li = document.createElement( "li" );
li.className = assertion.result ? "pass" : "fail";
li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
ol.appendChild( li );
if ( assertion.result ) {
good++;
} else {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
// store result when possible
if ( QUnit.config.reorder && defined.sessionStorage ) {
if ( bad ) {
sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
} else {
sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
}
}
if ( bad === 0 ) {
addClass( ol, "qunit-collapsed" );
}
// `b` initialized at top of scope
b = document.createElement( "strong" );
b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
addEvent(b, "click", function() {
var next = b.parentNode.lastChild,
collapsed = hasClass( next, "qunit-collapsed" );
( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
});
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({ testNumber: test.testNumber });
}
});
// `time` initialized at top of scope
time = document.createElement( "span" );
time.className = "runtime";
time.innerHTML = this.runtime + " ms";
// `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( time );
li.appendChild( ol );
} else {
for ( i = 0; i < this.assertions.length; i++ ) {
if ( !this.assertions[i].result ) {
bad++;
config.stats.bad++;
config.moduleStats.bad++;
}
}
}
runLoggingCallbacks( "testDone", QUnit, {
name: this.testName,
module: this.module,
failed: bad,
passed: this.assertions.length - bad,
total: this.assertions.length,
runtime: this.runtime,
// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
duration: this.runtime,
});
QUnit.reset();
config.current = undefined;
},
queue: function() {
var bad,
test = this;
synchronize(function() {
test.init();
});
function run() {
// each of these can by async
synchronize(function() {
test.setup();
});
synchronize(function() {
test.run();
});
synchronize(function() {
test.teardown();
});
synchronize(function() {
test.finish();
});
}
// `bad` initialized at top of scope
// defer when previous test run passed, if storage is available
bad = QUnit.config.reorder && defined.sessionStorage &&
+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
if ( bad ) {
run();
} else {
synchronize( run, true );
}
}
};
// `assert` initialized at top of scope
// Assert helpers
// All of these must either call QUnit.push() or manually do:
// - runLoggingCallbacks( "log", .. );
// - config.current.assertions.push({ .. });
assert = QUnit.assert = {
/**
* Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function( result, msg ) {
if ( !config.current ) {
throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
}
result = !!result;
msg = msg || ( result ? "okay" : "failed" );
var source,
details = {
module: config.current.module,
name: config.current.testName,
result: result,
message: msg
};
msg = "<span class='test-message'>" + escapeText( msg ) + "</span>";
if ( !result ) {
source = sourceFromStacktrace( 2 );
if ( source ) {
details.source = source;
msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" +
escapeText( source ) +
"</pre></td></tr></table>";
}
}
runLoggingCallbacks( "log", QUnit, details );
config.current.assertions.push({
result: result,
message: msg
});
},
/**
* Assert that the first two arguments are equal, with an optional message.
* Prints out both actual and expected values.
* @name equal
* @function
* @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
*/
equal: function( actual, expected, message ) {
/*jshint eqeqeq:false */
QUnit.push( expected == actual, actual, expected, message );
},
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) {
/*jshint eqeqeq:false */
QUnit.push( expected != actual, actual, expected, message );
},
/**
* @name propEqual
* @function
*/
propEqual: function( actual, expected, message ) {
actual = objectValues(actual);
expected = objectValues(expected);
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name notPropEqual
* @function
*/
notPropEqual: function( actual, expected, message ) {
actual = objectValues(actual);
expected = objectValues(expected);
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 );
},
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) {
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
},
/**
* @name strictEqual
* @function
*/
strictEqual: 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 );
},
"throws": function( block, expected, message ) {
var actual,
expectedOutput = expected,
ok = false;
// 'expected' is optional
if ( typeof expected === "string" ) {
message = expected;
expected = null;
}
config.current.ignoreGlobalErrors = true;
try {
block.call( config.current.testEnvironment );
} catch (e) {
actual = e;
}
config.current.ignoreGlobalErrors = false;
if ( actual ) {
// we don't want to validate thrown error
if ( !expected ) {
ok = true;
expectedOutput = null;
// expected is a regexp
} else if ( QUnit.objectType( expected ) === "regexp" ) {
ok = expected.test( errorString( actual ) );
// expected is a constructor
} else if ( actual instanceof expected ) {
ok = true;
// expected is a validation function which returns true is validation passed
} else if ( expected.call( {}, actual ) === true ) {
expectedOutput = null;
ok = true;
}
QUnit.push( ok, actual, expectedOutput, message );
} else {
QUnit.pushFailure( message, null, "No exception was thrown." );
}
}
};
/**
* @deprecated since 1.8.0
* Kept assertion helpers in root for backwards compatibility.
*/
extend( QUnit.constructor.prototype, assert );
/**
* @deprecated since 1.9.0
* Kept to avoid TypeErrors for undefined methods.
*/
QUnit.constructor.prototype.raises = function() {
QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" );
};
/**
* @deprecated since 1.0.0, replaced with error pushes since 1.3.0
* Kept to avoid TypeErrors for undefined methods.
*/
QUnit.constructor.prototype.equals = function() {
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
};
QUnit.constructor.prototype.same = function() {
QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
};
// Test for equality any JavaScript type.
// Author: Philippe Rathé <prathe@gmail.com>
QUnit.equiv = (function() {
// Call the o related callback with the given arguments.
function bindCallbacks( o, callbacks, args ) {
var prop = QUnit.objectType( o );
if ( prop ) {
if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
return callbacks[ prop ].apply( callbacks, args );
} else {
return callbacks[ prop ]; // or undefined
}
}
}
// the real equiv function
var innerEquiv,
// stack to decide between skip/abort functions
callers = [],
// stack to avoiding loops from circular referencing
parents = [],
parentsB = [],
getProto = Object.getPrototypeOf || function ( obj ) {
/*jshint camelcase:false */
return obj.__proto__;
},
callbacks = (function () {
// for string, boolean, number and null
function useStrictEquality( b, a ) {
/*jshint eqeqeq:false */
if ( b instanceof a.constructor || a instanceof b.constructor ) {
// to catch short annotation VS 'new' annotation of a
// declaration
// e.g. var i = 1;
// var j = new Number(1);
return a == b;
} else {
return a === b;
}
}
return {
"string": useStrictEquality,
"boolean": useStrictEquality,
"number": useStrictEquality,
"null": useStrictEquality,
"undefined": useStrictEquality,
"nan": function( b ) {
return isNaN( b );
},
"date": function( b, a ) {
return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
},
"regexp": function( b, a ) {
return QUnit.objectType( b ) === "regexp" &&
// the regex itself
a.source === b.source &&
// and its modifiers
a.global === b.global &&
// (gmi) ...
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline &&
a.sticky === b.sticky;
},
// - skip when the property is a method of an instance (OOP)
// - abort otherwise,
// initial === would have catch identical references anyway
"function": function() {
var caller = callers[callers.length - 1];
return caller !== Object && typeof caller !== "undefined";
},
"array": function( b, a ) {
var i, j, len, loop, aCircular, bCircular;
// b could be an object literal here
if ( QUnit.objectType( b ) !== "array" ) {
return false;
}
len = a.length;
if ( len !== b.length ) {
// safe and faster
return false;
}
// track reference to avoid circular references
parents.push( a );
parentsB.push( b );
for ( i = 0; i < len; i++ ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
aCircular = parents[j] === a[i];
bCircular = parentsB[j] === b[i];
if ( aCircular || bCircular ) {
if ( a[i] === b[i] || aCircular && bCircular ) {
loop = true;
} else {
parents.pop();
parentsB.pop();
return false;
}
}
}
if ( !loop && !innerEquiv(a[i], b[i]) ) {
parents.pop();
parentsB.pop();
return false;
}
}
parents.pop();
parentsB.pop();
return true;
},
"object": function( b, a ) {
/*jshint forin:false */
var i, j, loop, aCircular, bCircular,
// Default to true
eq = true,
aProperties = [],
bProperties = [];
// comparing constructors is more strict than using
// instanceof
if ( a.constructor !== b.constructor ) {
// Allow objects with no prototype to be equivalent to
// objects with Object as their constructor.
if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
return false;
}
}
// stack constructor before traversing properties
callers.push( a.constructor );
// track reference to avoid circular references
parents.push( a );
parentsB.push( b );
// be strict: don't ensure hasOwnProperty and go deep
for ( i in a ) {
loop = false;
for ( j = 0; j < parents.length; j++ ) {
aCircular = parents[j] === a[i];
bCircular = parentsB[j] === b[i];
if ( aCircular || bCircular ) {
if ( a[i] === b[i] || aCircular && bCircular ) {
loop = true;
} else {
eq = false;
break;
}
}
}
aProperties.push(i);
if ( !loop && !innerEquiv(a[i], b[i]) ) {
eq = false;
break;
}
}
parents.pop();
parentsB.pop();
callers.pop(); // unstack, we are done
for ( i in b ) {
bProperties.push( i ); // collect b's properties
}
// Ensures identical properties name
return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
}
};
}());
innerEquiv = function() { // can take multiple arguments
var args = [].slice.apply( arguments );
if ( args.length < 2 ) {
return true; // end transition
}
return (function( a, b ) {
if ( a === b ) {
return true; // catch the most you can
} else if ( a === null || b === null || typeof a === "undefined" ||
typeof b === "undefined" ||
QUnit.objectType(a) !== QUnit.objectType(b) ) {
return false; // don't lose time with error prone cases
} else {
return bindCallbacks(a, callbacks, [ b, a ]);
}
// apply transition with (1..n) arguments
}( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) );
};
return innerEquiv;
}());
/**
* jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
* http://flesler.blogspot.com Licensed under BSD
* (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
*
* @projectDescription Advanced and extensible data dumping for Javascript.
* @version 1.0.0
* @author Ariel Flesler
* @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
*/
QUnit.jsDump = (function() {
function quote( str ) {
return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
}
function literal( o ) {
return o + "";
}
function join( pre, arr, post ) {
var s = jsDump.separator(),
base = jsDump.indent(),
inner = jsDump.indent(1);
if ( arr.join ) {
arr = arr.join( "," + s + inner );
}
if ( !arr ) {
return pre + post;
}
return [ pre, inner + arr, base + post ].join(s);
}
function array( arr, stack ) {
var i = arr.length, ret = new Array(i);
this.up();
while ( i-- ) {
ret[i] = this.parse( arr[i] , undefined , stack);
}
this.down();
return join( "[", ret, "]" );
}
var reName = /^function (\w+)/,
jsDump = {
// type is used mostly internally, you can fix a (custom)type in advance
parse: function( obj, type, stack ) {
stack = stack || [ ];
var inStack, res,
parser = this.parsers[ type || this.typeOf(obj) ];
type = typeof parser;
inStack = inArray( obj, stack );
if ( inStack !== -1 ) {
return "recursion(" + (inStack - stack.length) + ")";
}
if ( type === "function" ) {
stack.push( obj );
res = parser.call( this, obj, stack );
stack.pop();
return res;
}
return ( type === "string" ) ? parser : this.parsers.error;
},
typeOf: function( obj ) {
var type;
if ( obj === null ) {
type = "null";
} else if ( typeof obj === "undefined" ) {
type = "undefined";
} else if ( QUnit.is( "regexp", obj) ) {
type = "regexp";
} else if ( QUnit.is( "date", obj) ) {
type = "date";
} else if ( QUnit.is( "function", obj) ) {
type = "function";
} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
type = "window";
} else if ( obj.nodeType === 9 ) {
type = "document";
} else if ( obj.nodeType ) {
type = "node";
} else if (
// native arrays
toString.call( obj ) === "[object Array]" ||
// NodeList objects
( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
) {
type = "array";
} else if ( obj.constructor === Error.prototype.constructor ) {
type = "error";
} else {
type = typeof obj;
}
return type;
},
separator: function() {
return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
},
// extra can be a number, shortcut for increasing-calling-decreasing
indent: function( extra ) {
if ( !this.multiline ) {
return "";
}
var chr = this.indentChar;
if ( this.HTML ) {
chr = chr.replace( /\t/g, " " ).replace( / /g, "&nbsp;" );
}
return new Array( this.depth + ( extra || 0 ) ).join(chr);
},
up: function( a ) {
this.depth += a || 1;
},
down: function( a ) {
this.depth -= a || 1;
},
setParser: function( name, parser ) {
this.parsers[name] = parser;
},
// The next 3 are exposed so you can use them
quote: quote,
literal: literal,
join: join,
//
depth: 1,
// This is the list of parsers, to modify them, use jsDump.setParser
parsers: {
window: "[Window]",
document: "[Document]",
error: function(error) {
return "Error(\"" + error.message + "\")";
},
unknown: "[Unknown]",
"null": "null",
"undefined": "undefined",
"function": function( fn ) {
var ret = "function",
// functions never have name in IE
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
if ( name ) {
ret += " " + name;
}
ret += "( ";
ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
},
array: array,
nodelist: array,
"arguments": array,
object: function( map, stack ) {
/*jshint forin:false */
var ret = [ ], keys, key, val, i;
QUnit.jsDump.up();
keys = [];
for ( key in map ) {
keys.push( key );
}
keys.sort();
for ( i = 0; i < keys.length; i++ ) {
key = keys[ i ];
val = map[ key ];
ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
}
QUnit.jsDump.down();
return join( "{", ret, "}" );
},
node: function( node ) {
var len, i, val,
open = QUnit.jsDump.HTML ? "&lt;" : "<",
close = QUnit.jsDump.HTML ? "&gt;" : ">",
tag = node.nodeName.toLowerCase(),
ret = open + tag,
attrs = node.attributes;
if ( attrs ) {
for ( i = 0, len = attrs.length; i < len; i++ ) {
val = attrs[i].nodeValue;
// IE6 includes all attributes in .attributes, even ones not explicitly set.
// Those have values like undefined, null, 0, false, "" or "inherit".
if ( val && val !== "inherit" ) {
ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
}
}
}
ret += close;
// Show content of TextNode or CDATASection
if ( node.nodeType === 3 || node.nodeType === 4 ) {
ret += node.nodeValue;
}
return ret + open + "/" + tag + close;
},
// function calls it internally, it's the arguments part of the function
functionArgs: function( fn ) {
var args,
l = fn.length;
if ( !l ) {
return "";
}
args = new Array(l);
while ( l-- ) {
// 97 is 'a'
args[l] = String.fromCharCode(97+l);
}
return " " + args.join( ", " ) + " ";
},
// object calls it internally, the key part of an item in a map
key: quote,
// function calls it internally, it's the content of the function
functionCode: "[code]",
// node calls it internally, it's an html attribute value
attribute: quote,
string: quote,
date: quote,
regexp: literal,
number: literal,
"boolean": literal
},
// if true, entities are escaped ( <, >, \t, space and \n )
HTML: false,
// indentation unit
indentChar: " ",
// if true, items in a collection, are separated by a \n, else just a space.
multiline: true
};
return jsDump;
}());
/* /*
* Javascript Diff Algorithm * Javascript Diff Algorithm
* By John Resig (http://ejohn.org/) * By John Resig (http://ejohn.org/)
@ -1424,67 +2056,75 @@ function inArray( elem, array ) {
* *
* Usage: QUnit.diff(expected, actual) * Usage: QUnit.diff(expected, actual)
* *
* QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
*/ */
QUnit.diff = (function() { QUnit.diff = (function() {
function diff(o, n) { /*jshint eqeqeq:false, eqnull:true */
var ns = {}; function diff( o, n ) {
var os = {}; var i,
ns = {},
os = {};
for (var i = 0; i < n.length; i++) { for ( i = 0; i < n.length; i++ ) {
if (ns[n[i]] == null) if ( !hasOwn.call( ns, n[i] ) ) {
ns[n[i]] = { ns[ n[i] ] = {
rows: [], rows: [],
o: null o: null
}; };
ns[n[i]].rows.push(i); }
ns[ n[i] ].rows.push( i );
} }
for (var i = 0; i < o.length; i++) { for ( i = 0; i < o.length; i++ ) {
if (os[o[i]] == null) if ( !hasOwn.call( os, o[i] ) ) {
os[o[i]] = { os[ o[i] ] = {
rows: [], rows: [],
n: null n: null
}; };
os[o[i]].rows.push(i); }
os[ o[i] ].rows.push( i );
} }
for (var i in ns) { for ( i in ns ) {
if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { if ( hasOwn.call( ns, i ) ) {
n[ns[i].rows[0]] = { if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
text: n[ns[i].rows[0]], n[ ns[i].rows[0] ] = {
row: os[i].rows[0] text: n[ ns[i].rows[0] ],
}; row: os[i].rows[0]
o[os[i].rows[0]] = { };
text: o[os[i].rows[0]], o[ os[i].rows[0] ] = {
row: ns[i].rows[0] text: o[ os[i].rows[0] ],
}; row: ns[i].rows[0]
};
}
} }
} }
for (var i = 0; i < n.length - 1; i++) { for ( i = 0; i < n.length - 1; i++ ) {
if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
n[i + 1] == o[n[i].row + 1]) { n[ i + 1 ] == o[ n[i].row + 1 ] ) {
n[i + 1] = {
text: n[i + 1], n[ i + 1 ] = {
text: n[ i + 1 ],
row: n[i].row + 1 row: n[i].row + 1
}; };
o[n[i].row + 1] = { o[ n[i].row + 1 ] = {
text: o[n[i].row + 1], text: o[ n[i].row + 1 ],
row: i + 1 row: i + 1
}; };
} }
} }
for (var i = n.length - 1; i > 0; i--) { for ( i = n.length - 1; i > 0; i-- ) {
if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
n[i - 1] == o[n[i].row - 1]) { n[ i - 1 ] == o[ n[i].row - 1 ]) {
n[i - 1] = {
text: n[i - 1], n[ i - 1 ] = {
text: n[ i - 1 ],
row: n[i].row - 1 row: n[i].row - 1
}; };
o[n[i].row - 1] = { o[ n[i].row - 1 ] = {
text: o[n[i].row - 1], text: o[ n[i].row - 1 ],
row: i - 1 row: i - 1
}; };
} }
@ -1496,49 +2136,52 @@ QUnit.diff = (function() {
}; };
} }
return function(o, n) { return function( o, n ) {
o = o.replace(/\s+$/, ''); o = o.replace( /\s+$/, "" );
n = n.replace(/\s+$/, ''); n = n.replace( /\s+$/, "" );
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
var str = ""; var i, pre,
str = "",
out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
oSpace = o.match(/\s+/g),
nSpace = n.match(/\s+/g);
var oSpace = o.match(/\s+/g); if ( oSpace == null ) {
if (oSpace == null) { oSpace = [ " " ];
oSpace = [" "];
} }
else { else {
oSpace.push(" "); oSpace.push( " " );
}
var nSpace = n.match(/\s+/g);
if (nSpace == null) {
nSpace = [" "];
}
else {
nSpace.push(" ");
} }
if (out.n.length == 0) { if ( nSpace == null ) {
for (var i = 0; i < out.o.length; i++) { nSpace = [ " " ];
str += '<del>' + out.o[i] + oSpace[i] + "</del>"; }
else {
nSpace.push( " " );
}
if ( out.n.length === 0 ) {
for ( i = 0; i < out.o.length; i++ ) {
str += "<del>" + out.o[i] + oSpace[i] + "</del>";
} }
} }
else { else {
if (out.n[0].text == null) { if ( out.n[0].text == null ) {
for (n = 0; n < out.o.length && out.o[n].text == null; n++) { for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
str += '<del>' + out.o[n] + oSpace[n] + "</del>"; str += "<del>" + out.o[n] + oSpace[n] + "</del>";
} }
} }
for (var i = 0; i < out.n.length; i++) { for ( i = 0; i < out.n.length; i++ ) {
if (out.n[i].text == null) { if (out.n[i].text == null) {
str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
} }
else { else {
var pre = ""; // `pre` initialized at top of scope
pre = "";
for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
} }
str += " " + out.n[i].text + nSpace[i] + pre; str += " " + out.n[i].text + nSpace[i] + pre;
} }
@ -1547,6 +2190,21 @@ QUnit.diff = (function() {
return str; return str;
}; };
})(); }());
})(this); // For browser, export only select globals
if ( typeof window !== "undefined" ) {
extend( window, QUnit.constructor.prototype );
window.QUnit = QUnit;
}
// For CommonJS environments, export everything
if ( typeof module !== "undefined" && module.exports ) {
module.exports = QUnit;
}
// Get a reference to the global object, like window in browsers
}( (function() {
return this;
})() ));

View File

@ -5,7 +5,9 @@
// Reset the box-sizing // Reset the box-sizing
*, * {
.box-sizing(border-box);
}
*:before, *:before,
*:after { *:after {
.box-sizing(border-box); .box-sizing(border-box);

View File

@ -58,7 +58,7 @@
//** Unit-less `line-height` for use in components like buttons. //** Unit-less `line-height` for use in components like buttons.
@line-height-base: 1.428571429; // 20/14 @line-height-base: 1.428571429; // 20/14
//** Computed "line-height" (`font-size` &times; `line-height`) for use with `margin`, `padding`, etc. //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px @line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px
//** By default, this inherits from the `<body>`. //** By default, this inherits from the `<body>`.
@ -548,7 +548,7 @@
@modal-content-bg: #fff; @modal-content-bg: #fff;
//** Modal content border color //** Modal content border color
@modal-content-border-color: rgba(0,0,0,.2); @modal-content-border-color: rgba(0,0,0,.2);
//** Modal content border color <strong>for IE8</strong> //** Modal content border color **for IE8**
@modal-content-fallback-border-color: #999; @modal-content-fallback-border-color: #999;
//** Modal backdrop background color //** Modal backdrop background color

View File

@ -25,7 +25,7 @@
} }
], ],
"devDependencies": { "devDependencies": {
"browserstack-runner": "~0.0.13", "browserstack-runner": "~0.0.14",
"btoa": "~1.1.1", "btoa": "~1.1.1",
"grunt": "~0.4.2", "grunt": "~0.4.2",
"grunt-banner": "~0.2.0", "grunt-banner": "~0.2.0",
@ -38,7 +38,7 @@
"grunt-contrib-jade": "~0.9.1", "grunt-contrib-jade": "~0.9.1",
"grunt-contrib-jshint": "~0.8.0", "grunt-contrib-jshint": "~0.8.0",
"grunt-contrib-less": "~0.9.0", "grunt-contrib-less": "~0.9.0",
"grunt-contrib-qunit": "~0.3.0", "grunt-contrib-qunit": "~0.4.0",
"grunt-contrib-uglify": "~0.2.7", "grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-watch": "~0.5.3", "grunt-contrib-watch": "~0.5.3",
"grunt-csscomb": "~2.0.1", "grunt-csscomb": "~2.0.1",

View File

@ -3,35 +3,31 @@
{ {
browserName: "safari", browserName: "safari",
platform: "OS X 10.8" platform: "OS X 10.9"
}, },
# { # Safari 7 (which requires Mavericks) is not currently supported by Sauce Labs # FIXME: keeps timing out frequently for unknown reasons
# browserName: "safari", # {
# browserName: "chrome",
# platform: "OS X 10.9" # platform: "OS X 10.9"
# }, # },
{ {
browserName: "chrome", browserName: "firefox",
platform: "OS X 10.9" platform: "OS X 10.9"
}, },
# { # FIXME: currently fails 1 tooltip test
# browserName: "firefox",
# platform: "OS X 10.9"
# },
# Mac Opera not currently supported by Sauce Labs # Mac Opera not currently supported by Sauce Labs
# { # FIXME: currently fails 1 tooltip test {
# browserName: "internet explorer", browserName: "internet explorer",
# version: "11", version: "11",
# platform: "Windows 8.1" platform: "Windows 8.1"
# }, },
{
# { browserName: "internet explorer",
# browserName: "internet explorer", version: "10",
# version: "10", platform: "Windows 8"
# platform: "Windows 8" },
# },
# { # {
# browserName: "internet explorer", # browserName: "internet explorer",
# version: "9", # version: "9",