diff --git a/.gitignore b/.gitignore
index 1cc5b6e..90e2c4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,5 @@ src/*.map
*.diff
*.patch
.DS_Store
-settings.json
\ No newline at end of file
+settings.json
+.idea/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3c5e48..4c35228 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 1.3.3
+### Enhancements & Features
+- Update dependencies to 2018 versions, eliminating 72 vulnerabilities.
+- The old, unused copy of Modernizr in the demo has been removed.
+
+## 1.3.2
+### Bug fixes
+- Fix jQuery and Bootstrap versions in package.json, fix license info in package.json
+
+### Enhancements & Features
+- Use left and right single guillemets in pagination.
+- Migrated test suite to Qunit 2.8.0.
+
## 1.3.1
### Enhancements & Features
diff --git a/Gruntfile.js b/Gruntfile.js
index e584c33..35b94fa 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,6 +1,5 @@
/*jshint node:true*/
-module.exports = function (grunt)
-{
+module.exports = function (grunt) {
"use strict";
/* Hint: Using grunt-strip-code to remove comments from the release file */
@@ -9,8 +8,8 @@ module.exports = function (grunt)
pkg: grunt.file.readJSON('package.json'),
fontawesome: 'fa',
banner: '/*! <%= "\\r\\n * " + pkg.title %> v<%= pkg.version %> - <%= grunt.template.today("mm/dd/yyyy") + "\\r\\n" %>' +
- ' * Copyright (c) 2014-<%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <%= (pkg.homepage ? "(" + pkg.homepage + ")" : "") + "\\r\\n" %>' +
- ' * Licensed under <%= pkg.licenses[0].type + " " + pkg.licenses[0].url + "\\r\\n */\\r\\n" %>',
+ ' * Copyright © 2014-2015 Rafael J. Staib; Copyright © 2018-<%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <%= (pkg.homepage ? "(" + pkg.homepage + ")" : "") + "\\r\\n" %>' +
+ ' * Licensed under the <%= pkg.license + " license. See LICENSE.txt for more details." + "\\r\\n */\\r\\n" %>',
folders: {
dist: "dist",
docs: "docs",
@@ -57,20 +56,17 @@ module.exports = function (grunt)
separator: '\r\n\r\n',
banner: '<%= banner %>;(function ($, window, undefined)\r\n{\r\n /*jshint validthis: true */\r\n "use strict";\r\n\r\n',
footer: '\r\n})(jQuery, window);',
- process: function(src, filepath)
- {
+ process: function (src, filepath) {
var result = src.trim().replace(/(.+?\r\n)/gm, ' $1'),
end = [0, ""],
lastChar = result[result.length - 1];
- if (lastChar === ";")
- {
+ if (lastChar === ";") {
end = (result[result.length - 2] === ")") ?
- (result[result.length - 2] === "}") ?
- [3, " });"] : [2, ");"] : [2, " };"];
+ (result[result.length - 2] === "}") ?
+ [3, " });"] : [2, ");"] : [2, " };"];
}
- else if (lastChar === "}")
- {
+ else if (lastChar === "}") {
end = [1, " }"];
}
@@ -106,6 +102,7 @@ module.exports = function (grunt)
default: {
options: {
'adjoining-classes': false,
+ 'order-alphabetical': false,
'important': false,
'outline-none': false,
'overqualified-elements': false
@@ -118,13 +115,14 @@ module.exports = function (grunt)
curly: true,
eqeqeq: true,
immed: true,
- latedef: true,
+ latedef: 'nofunc',
newcap: true,
noarg: true,
sub: true,
undef: true,
eqnull: true,
browser: true,
+ reporterOutput: "",
globals: {
jQuery: true,
$: true,
@@ -210,7 +208,7 @@ module.exports = function (grunt)
files: [
{
flatten: true,
- expand: true,
+ expand: true,
src: ['<%= folders.dist %>/*.js', '<%= folders.dist %>/*.css'], dest: '/'
}
]
@@ -247,8 +245,7 @@ module.exports = function (grunt)
grunt.loadNpmTasks('grunt-nuget');
grunt.loadNpmTasks('grunt-regex-replace');
- grunt.registerMultiTask('version', 'sets version tag', function ()
- {
+ grunt.registerMultiTask('version', 'sets version tag', function () {
var pkg = grunt.file.readJSON(this.data.src);
pkg["version"] = this.data.options.version;
grunt.file.write(this.data.src, JSON.stringify(pkg, null, 4));
diff --git a/LICENSE.txt b/LICENSE.txt
index 6c71285..12bcaaa 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,7 @@
The MIT License (MIT)
-Copyright (c) 2014-2015 Rafael J. Staib
+Copyright © 2014-2015 Rafael J. Staib
+Copyright © 2018 Deciso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 9ef1416..44a933d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-jQuery Bootgrid Plugin [](https://travis-ci.org/rstaib/jquery-bootgrid)    
+jQuery Bootgrid Plugin [](https://travis-ci.org/opnsense/jquery-bootgrid)    
============
Nice, sleek and intuitive. A grid control especially designed for bootstrap.
@@ -53,10 +53,45 @@ Instructions will follow soon!
I'm always happy to help answer your questions. The best way to get quick answers is to go to [stackoverflow.com](http://stackoverflow.com) and tag your questions always with **jquery-bootgrid**.
+## Building
+
+### Environment
+
+jquery-bootgrid uses npm to install its own dependencies, and mono to build a NuGet package.
+
+You should install both using your package manager. On macOS, this works as follows:
+```
+brew install npm
+brew install mono
+```
+
+### Build dependencies
+
+From the folder that jquery-bootgrid resides in, run:
+```
+npm install .
+sudo npm install -g grunt
+```
+
+### Build jquery-bootgrid itself
+```
+grunt
+```
+
+If you are done with your modifications, you should increase the version number in boewer.json and package.json,
+update the changelog, and then run:
+```
+grunt release
+```
+
+
## Contributing
-Instructions will follow soon!
+No instructions yet.
## License
-Copyright (c) 2014-2015 Rafael J. Staib Licensed under the [MIT license](https://github.com/rstaib/jquery-bootgrid/blob/master/LICENSE.txt).
+Copyright © 2014-2015 Rafael J. Staib
+Copyright © 2018 Deciso B.V.
+
+Licensed under the [MIT license](https://github.com/opnsense/jquery-bootgrid/blob/master/LICENSE.txt).
diff --git a/bower.json b/bower.json
index 856e916..52513c4 100644
--- a/bower.json
+++ b/bower.json
@@ -13,7 +13,7 @@
"accessibility",
"bootstrap"
],
- "version": "1.3.1",
+ "version": "1.3.3",
"authors": [
{
"name": "Rafael Staib",
diff --git a/demo/index.htm b/demo/index.htm
index ae13225..5d0c37b 100644
--- a/demo/index.htm
+++ b/demo/index.htm
@@ -6,7 +6,6 @@
jQuery Bootgrid Demo
-
"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=v.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(v.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(v.call(arguments)))};return e}),r.flexbox=function(){return G("flexWrap")},r.flexboxlegacy=function(){return G("boxDirection")},r.rgba=function(){return A("background-color:rgba(150,255,150,.5)"),D(j.backgroundColor,"rgba")},r.hsla=function(){return A("background-color:hsla(120,40%,100%,.5)"),D(j.backgroundColor,"rgba")||D(j.backgroundColor,"hsla")},r.multiplebgs=function(){return A("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},r.backgroundsize=function(){return G("backgroundSize")},r.borderimage=function(){return G("borderImage")},r.borderradius=function(){return G("borderRadius")},r.boxshadow=function(){return G("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){return B("opacity:.55"),/^0.55$/.test(j.opacity)},r.cssanimations=function(){return G("animationName")},r.csscolumns=function(){return G("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return A((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),D(j.backgroundImage,"gradient")},r.cssreflections=function(){return G("boxReflect")},r.csstransforms=function(){return!!G("transform")},r.csstransforms3d=function(){var a=!!G("perspective");return a&&"webkitPerspective"in g.style&&x("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},r.csstransitions=function(){return G("transition")},r.fontface=function(){var a;return x('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},r.generatedcontent=function(){var a;return x(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a};for(var H in r)z(r,H)&&(w=H.toLowerCase(),e[w]=r[H](),u.push((e[w]?"":"no-")+w));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},A(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=x,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+u.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;fjQuery Bootgrid Demo
-
@-webkit-viewport {
width: device-width;
@@ -133,6 +132,7 @@
diff --git a/dist/jQuery.Bootgrid.1.3.1.nupkg b/dist/jQuery.Bootgrid.1.3.1.nupkg
deleted file mode 100644
index c430a89..0000000
Binary files a/dist/jQuery.Bootgrid.1.3.1.nupkg and /dev/null differ
diff --git a/dist/jQuery.Bootgrid.1.3.3.nupkg b/dist/jQuery.Bootgrid.1.3.3.nupkg
new file mode 100644
index 0000000..173c84c
Binary files /dev/null and b/dist/jQuery.Bootgrid.1.3.3.nupkg differ
diff --git a/dist/jquery.bootgrid-1.3.1.zip b/dist/jquery.bootgrid-1.3.1.zip
deleted file mode 100644
index 61cbe12..0000000
Binary files a/dist/jquery.bootgrid-1.3.1.zip and /dev/null differ
diff --git a/dist/jquery.bootgrid-1.3.3.zip b/dist/jquery.bootgrid-1.3.3.zip
new file mode 100644
index 0000000..adc2289
Binary files /dev/null and b/dist/jquery.bootgrid-1.3.3.zip differ
diff --git a/dist/jquery.bootgrid.css b/dist/jquery.bootgrid.css
index 13512d9..c9c6bba 100644
--- a/dist/jquery.bootgrid.css
+++ b/dist/jquery.bootgrid.css
@@ -1,7 +1,7 @@
/*!
- * jQuery Bootgrid v1.3.1 - 09/11/2015
- * Copyright (c) 2014-2015 Rafael Staib (http://www.jquery-bootgrid.com)
- * Licensed under MIT http://www.opensource.org/licenses/MIT
+ * jQuery Bootgrid v1.3.3 - 11/06/2018
+ * Copyright © 2014-2015 Rafael J. Staib; Copyright © 2018-2018 Deciso B.V. (http://www.jquery-bootgrid.com)
+ * Licensed under the MIT license. See LICENSE.txt for more details.
*/
.bootgrid-header,
.bootgrid-footer {
diff --git a/dist/jquery.bootgrid.fa.js b/dist/jquery.bootgrid.fa.js
index c120aa0..1bfa4ec 100644
--- a/dist/jquery.bootgrid.fa.js
+++ b/dist/jquery.bootgrid.fa.js
@@ -1,19 +1,19 @@
/*!
- * jQuery Bootgrid v1.3.1 - 09/11/2015
- * Copyright (c) 2014-2015 Rafael Staib (http://www.jquery-bootgrid.com)
- * Licensed under MIT http://www.opensource.org/licenses/MIT
+ * jQuery Bootgrid v1.3.3 - 11/06/2018
+ * Copyright © 2014-2015 Rafael J. Staib; Copyright © 2018-2018 Deciso B.V. (http://www.jquery-bootgrid.com)
+ * Licensed under the MIT license. See LICENSE.txt for more details.
*/
;(function ($, window, undefined)
{
/*jshint validthis: true */
"use strict";
- $.extend($.fn.bootgrid.Constructor.defaults.css, {
- icon: "icon fa",
- iconColumns: "fa-th-list",
- iconDown: "fa-sort-desc",
- iconRefresh: "fa-refresh",
- iconSearch: "fa-search",
- iconUp: "fa-sort-asc"
+$.extend($.fn.bootgrid.Constructor.defaults.css, {
+ icon: "icon fa",
+ iconColumns: "fa-th-list",
+ iconDown: "fa-sort-desc",
+ iconRefresh: "fa-refresh",
+ iconSearch: "fa-search",
+ iconUp: "fa-sort-asc"
});
})(jQuery, window);
\ No newline at end of file
diff --git a/dist/jquery.bootgrid.fa.min.js b/dist/jquery.bootgrid.fa.min.js
index 5e78cfb..89d0b40 100644
--- a/dist/jquery.bootgrid.fa.min.js
+++ b/dist/jquery.bootgrid.fa.min.js
@@ -1,6 +1 @@
-/*!
- * jQuery Bootgrid v1.3.1 - 09/11/2015
- * Copyright (c) 2014-2015 Rafael Staib (http://www.jquery-bootgrid.com)
- * Licensed under MIT http://www.opensource.org/licenses/MIT
- */
-!function(a,b,c){"use strict";a.extend(a.fn.bootgrid.Constructor.defaults.css,{icon:"icon fa",iconColumns:"fa-th-list",iconDown:"fa-sort-desc",iconRefresh:"fa-refresh",iconSearch:"fa-search",iconUp:"fa-sort-asc"})}(jQuery,window);
\ No newline at end of file
+!function(o,c,n){"use strict";o.extend(o.fn.bootgrid.Constructor.defaults.css,{icon:"icon fa",iconColumns:"fa-th-list",iconDown:"fa-sort-desc",iconRefresh:"fa-refresh",iconSearch:"fa-search",iconUp:"fa-sort-asc"})}(jQuery,window);
\ No newline at end of file
diff --git a/dist/jquery.bootgrid.js b/dist/jquery.bootgrid.js
index 4f27d44..f0eca3e 100644
--- a/dist/jquery.bootgrid.js
+++ b/dist/jquery.bootgrid.js
@@ -1,2042 +1,2042 @@
/*!
- * jQuery Bootgrid v1.3.1 - 09/11/2015
- * Copyright (c) 2014-2015 Rafael Staib (http://www.jquery-bootgrid.com)
- * Licensed under MIT http://www.opensource.org/licenses/MIT
+ * jQuery Bootgrid v1.3.3 - 11/06/2018
+ * Copyright © 2014-2015 Rafael J. Staib; Copyright © 2018-2018 Deciso B.V. (http://www.jquery-bootgrid.com)
+ * Licensed under the MIT license. See LICENSE.txt for more details.
*/
;(function ($, window, undefined)
{
/*jshint validthis: true */
"use strict";
- // GRID INTERNAL FIELDS
- // ====================
+// GRID INTERNAL FIELDS
+// ====================
- var namespace = ".rs.jquery.bootgrid";
+var namespace = ".rs.jquery.bootgrid";
- // GRID INTERNAL FUNCTIONS
- // =====================
+// GRID INTERNAL FUNCTIONS
+// =====================
- function appendRow(row)
- {
- var that = this;
-
- function exists(item)
- {
- return that.identifier && item[that.identifier] === row[that.identifier];
- }
-
- if (!this.rows.contains(exists))
- {
- this.rows.push(row);
- return true;
- }
-
- return false;
- }
+function appendRow(row)
+{
+ var that = this;
- function findFooterAndHeaderItems(selector)
+ function exists(item)
{
- var footer = (this.footer) ? this.footer.find(selector) : $(),
- header = (this.header) ? this.header.find(selector) : $();
- return $.merge(footer, header);
+ return that.identifier && item[that.identifier] === row[that.identifier];
}
- function getParams(context)
+ if (!this.rows.contains(exists))
{
- return (context) ? $.extend({}, this.cachedParams, { ctx: context }) :
- this.cachedParams;
+ this.rows.push(row);
+ return true;
}
- function getRequest()
- {
- var request = {
- current: this.current,
- rowCount: this.rowCount,
- sort: this.sortDictionary,
- searchPhrase: this.searchPhrase
- },
- post = this.options.post;
+ return false;
+}
- post = ($.isFunction(post)) ? post() : post;
- return this.options.requestHandler($.extend(true, request, post));
- }
+function findFooterAndHeaderItems(selector)
+{
+ var footer = (this.footer) ? this.footer.find(selector) : $(),
+ header = (this.header) ? this.header.find(selector) : $();
+ return $.merge(footer, header);
+}
- function getCssSelector(css)
- {
- return "." + $.trim(css).replace(/\s+/gm, ".");
- }
+function getParams(context)
+{
+ return (context) ? $.extend({}, this.cachedParams, { ctx: context }) :
+ this.cachedParams;
+}
+
+function getRequest()
+{
+ var request = {
+ current: this.current,
+ rowCount: this.rowCount,
+ sort: this.sortDictionary,
+ searchPhrase: this.searchPhrase
+ },
+ post = this.options.post;
+
+ post = ($.isFunction(post)) ? post() : post;
+ return this.options.requestHandler($.extend(true, request, post));
+}
+
+function getCssSelector(css)
+{
+ return "." + $.trim(css).replace(/\s+/gm, ".");
+}
+
+function getUrl()
+{
+ var url = this.options.url;
+ return ($.isFunction(url)) ? url() : url;
+}
+
+function init()
+{
+ this.element.trigger("initialize" + namespace);
+
+ loadColumns.call(this); // Loads columns from HTML thead tag
+ this.selection = this.options.selection && this.identifier != null;
+ loadRows.call(this); // Loads rows from HTML tbody tag if ajax is false
+ prepareTable.call(this);
+ renderTableHeader.call(this);
+ renderSearchField.call(this);
+ renderActions.call(this);
+ loadData.call(this);
- function getUrl()
+ this.element.trigger("initialized" + namespace);
+}
+
+function highlightAppendedRows(rows)
+{
+ if (this.options.highlightRows)
{
- var url = this.options.url;
- return ($.isFunction(url)) ? url() : url;
+ // todo: implement
}
+}
- function init()
- {
- this.element.trigger("initialize" + namespace);
-
- loadColumns.call(this); // Loads columns from HTML thead tag
- this.selection = this.options.selection && this.identifier != null;
- loadRows.call(this); // Loads rows from HTML tbody tag if ajax is false
- prepareTable.call(this);
- renderTableHeader.call(this);
- renderSearchField.call(this);
- renderActions.call(this);
- loadData.call(this);
+function isVisible(column)
+{
+ return column.visible;
+}
- this.element.trigger("initialized" + namespace);
- }
+function loadColumns()
+{
+ var that = this,
+ firstHeadRow = this.element.find("thead > tr").first(),
+ sorted = false;
- function highlightAppendedRows(rows)
+ /*jshint -W018*/
+ firstHeadRow.children().each(function ()
{
- if (this.options.highlightRows)
+ var $this = $(this),
+ data = $this.data(),
+ column = {
+ id: data.columnId,
+ identifier: that.identifier == null && data.identifier || false,
+ converter: that.options.converters[data.converter || data.type] || that.options.converters["string"],
+ text: $this.text(),
+ align: data.align || "left",
+ headerAlign: data.headerAlign || "left",
+ cssClass: data.cssClass || "",
+ headerCssClass: data.headerCssClass || "",
+ formatter: that.options.formatters[data.formatter] || null,
+ order: (!sorted && (data.order === "asc" || data.order === "desc")) ? data.order : null,
+ searchable: !(data.searchable === false), // default: true
+ sortable: !(data.sortable === false), // default: true
+ visible: !(data.visible === false), // default: true
+ visibleInSelection: !(data.visibleInSelection === false), // default: true
+ width: ($.isNumeric(data.width)) ? data.width + "px" :
+ (typeof(data.width) === "string") ? data.width : null
+ };
+ that.columns.push(column);
+ if (column.order != null)
{
- // todo: implement
+ that.sortDictionary[column.id] = column.order;
}
- }
-
- function isVisible(column)
- {
- return column.visible;
- }
- function loadColumns()
- {
- var that = this,
- firstHeadRow = this.element.find("thead > tr").first(),
- sorted = false;
+ // Prevents multiple identifiers
+ if (column.identifier)
+ {
+ that.identifier = column.id;
+ that.converter = column.converter;
+ }
- /*jshint -W018*/
- firstHeadRow.children().each(function ()
+ // ensures that only the first order will be applied in case of multi sorting is disabled
+ if (!that.options.multiSort && column.order !== null)
{
- var $this = $(this),
- data = $this.data(),
- column = {
- id: data.columnId,
- identifier: that.identifier == null && data.identifier || false,
- converter: that.options.converters[data.converter || data.type] || that.options.converters["string"],
- text: $this.text(),
- align: data.align || "left",
- headerAlign: data.headerAlign || "left",
- cssClass: data.cssClass || "",
- headerCssClass: data.headerCssClass || "",
- formatter: that.options.formatters[data.formatter] || null,
- order: (!sorted && (data.order === "asc" || data.order === "desc")) ? data.order : null,
- searchable: !(data.searchable === false), // default: true
- sortable: !(data.sortable === false), // default: true
- visible: !(data.visible === false), // default: true
- visibleInSelection: !(data.visibleInSelection === false), // default: true
- width: ($.isNumeric(data.width)) ? data.width + "px" :
- (typeof(data.width) === "string") ? data.width : null
- };
- that.columns.push(column);
- if (column.order != null)
- {
- that.sortDictionary[column.id] = column.order;
- }
+ sorted = true;
+ }
+ });
+ /*jshint +W018*/
+}
+
+/*
+response = {
+ current: 1,
+ rowCount: 10,
+ rows: [{}, {}],
+ sort: [{ "columnId": "asc" }],
+ total: 101
+}
+*/
+
+function loadData()
+{
+ var that = this;
- // Prevents multiple identifiers
- if (column.identifier)
- {
- that.identifier = column.id;
- that.converter = column.converter;
- }
+ this.element._bgBusyAria(true).trigger("load" + namespace);
+ showLoading.call(this);
+
+ function containsPhrase(row)
+ {
+ var column,
+ searchPattern = new RegExp(that.searchPhrase, (that.options.caseSensitive) ? "g" : "gi");
- // ensures that only the first order will be applied in case of multi sorting is disabled
- if (!that.options.multiSort && column.order !== null)
+ for (var i = 0; i < that.columns.length; i++)
+ {
+ column = that.columns[i];
+ if (column.searchable && column.visible &&
+ column.converter.to(row[column.id]).search(searchPattern) > -1)
{
- sorted = true;
+ return true;
}
- });
- /*jshint +W018*/
- }
+ }
- /*
- response = {
- current: 1,
- rowCount: 10,
- rows: [{}, {}],
- sort: [{ "columnId": "asc" }],
- total: 101
+ return false;
}
- */
- function loadData()
+ function update(rows, total)
{
- var that = this;
+ that.currentRows = rows;
+ setTotals.call(that, total);
- this.element._bgBusyAria(true).trigger("load" + namespace);
- showLoading.call(this);
-
- function containsPhrase(row)
+ if (!that.options.keepSelection)
{
- var column,
- searchPattern = new RegExp(that.searchPhrase, (that.options.caseSensitive) ? "g" : "gi");
-
- for (var i = 0; i < that.columns.length; i++)
- {
- column = that.columns[i];
- if (column.searchable && column.visible &&
- column.converter.to(row[column.id]).search(searchPattern) > -1)
- {
- return true;
- }
- }
-
- return false;
+ that.selectedRows = [];
}
- function update(rows, total)
- {
- that.currentRows = rows;
- setTotals.call(that, total);
+ renderRows.call(that, rows);
+ renderInfos.call(that);
+ renderPagination.call(that);
- if (!that.options.keepSelection)
- {
- that.selectedRows = [];
- }
+ that.element._bgBusyAria(false).trigger("loaded" + namespace);
+ }
- renderRows.call(that, rows);
- renderInfos.call(that);
- renderPagination.call(that);
+ if (this.options.ajax)
+ {
+ var request = getRequest.call(this),
+ url = getUrl.call(this);
- that.element._bgBusyAria(false).trigger("loaded" + namespace);
+ if (url == null || typeof url !== "string" || url.length === 0)
+ {
+ throw new Error("Url setting must be a none empty string or a function that returns one.");
}
- if (this.options.ajax)
+ // aborts the previous ajax request if not already finished or failed
+ if (this.xqr)
{
- var request = getRequest.call(this),
- url = getUrl.call(this);
-
- if (url == null || typeof url !== "string" || url.length === 0)
- {
- throw new Error("Url setting must be a none empty string or a function that returns one.");
- }
+ this.xqr.abort();
+ }
- // aborts the previous ajax request if not already finished or failed
- if (this.xqr)
+ var settings = {
+ url: url,
+ data: request,
+ success: function(response)
{
- this.xqr.abort();
- }
+ that.xqr = null;
- var settings = {
- url: url,
- data: request,
- success: function(response)
+ if (typeof (response) === "string")
{
- that.xqr = null;
+ response = $.parseJSON(response);
+ }
- if (typeof (response) === "string")
- {
- response = $.parseJSON(response);
- }
+ response = that.options.responseHandler(response);
- response = that.options.responseHandler(response);
+ that.current = response.current;
+ update(response.rows, response.total);
+ },
+ error: function (jqXHR, textStatus, errorThrown)
+ {
+ that.xqr = null;
- that.current = response.current;
- update(response.rows, response.total);
- },
- error: function (jqXHR, textStatus, errorThrown)
+ if (textStatus !== "abort")
{
- that.xqr = null;
-
- if (textStatus !== "abort")
- {
- renderNoResultsRow.call(that); // overrides loading mask
- that.element._bgBusyAria(false).trigger("loaded" + namespace);
- }
+ renderNoResultsRow.call(that); // overrides loading mask
+ that.element._bgBusyAria(false).trigger("loaded" + namespace);
}
- };
- settings = $.extend(this.options.ajaxSettings, settings);
-
- this.xqr = $.ajax(settings);
- }
- else
- {
- var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows,
- total = rows.length;
- if (this.rowCount !== -1)
- {
- rows = rows.page(this.current, this.rowCount);
}
+ };
+ settings = $.extend(this.options.ajaxSettings, settings);
- // todo: improve the following comment
- // setTimeout decouples the initialization so that adding event handlers happens before
- window.setTimeout(function () { update(rows, total); }, 10);
+ this.xqr = $.ajax(settings);
+ }
+ else
+ {
+ var rows = (this.searchPhrase.length > 0) ? this.rows.where(containsPhrase) : this.rows,
+ total = rows.length;
+ if (this.rowCount !== -1)
+ {
+ rows = rows.page(this.current, this.rowCount);
}
+
+ // todo: improve the following comment
+ // setTimeout decouples the initialization so that adding event handlers happens before
+ window.setTimeout(function () { update(rows, total); }, 10);
}
+}
- function loadRows()
+function loadRows()
+{
+ if (!this.options.ajax)
{
- if (!this.options.ajax)
+ var that = this,
+ rows = this.element.find("tbody > tr");
+
+ rows.each(function ()
{
- var that = this,
- rows = this.element.find("tbody > tr");
+ var $this = $(this),
+ cells = $this.children("td"),
+ row = {};
- rows.each(function ()
+ $.each(that.columns, function (i, column)
{
- var $this = $(this),
- cells = $this.children("td"),
- row = {};
+ row[column.id] = column.converter.from(cells.eq(i).text());
+ });
- $.each(that.columns, function (i, column)
- {
- row[column.id] = column.converter.from(cells.eq(i).text());
- });
+ appendRow.call(that, row);
+ });
- appendRow.call(that, row);
- });
+ setTotals.call(this, this.rows.length);
+ sortRows.call(this);
+ }
+}
- setTotals.call(this, this.rows.length);
- sortRows.call(this);
- }
+function setTotals(total)
+{
+ this.total = total;
+ this.totalPages = (this.rowCount === -1) ? 1 :
+ Math.ceil(this.total / this.rowCount);
+}
+
+function prepareTable()
+{
+ var tpl = this.options.templates,
+ wrapper = (this.element.parent().hasClass(this.options.css.responsiveTable)) ?
+ this.element.parent() : this.element;
+
+ this.element.addClass(this.options.css.table);
+
+ // checks whether there is an tbody element; otherwise creates one
+ if (this.element.children("tbody").length === 0)
+ {
+ this.element.append(tpl.body);
}
- function setTotals(total)
+ if (this.options.navigation & 1)
{
- this.total = total;
- this.totalPages = (this.rowCount === -1) ? 1 :
- Math.ceil(this.total / this.rowCount);
+ this.header = $(tpl.header.resolve(getParams.call(this, { id: this.element._bgId() + "-header" })));
+ wrapper.before(this.header);
}
- function prepareTable()
+ if (this.options.navigation & 2)
{
- var tpl = this.options.templates,
- wrapper = (this.element.parent().hasClass(this.options.css.responsiveTable)) ?
- this.element.parent() : this.element;
+ this.footer = $(tpl.footer.resolve(getParams.call(this, { id: this.element._bgId() + "-footer" })));
+ wrapper.after(this.footer);
+ }
+}
- this.element.addClass(this.options.css.table);
+function renderActions()
+{
+ if (this.options.navigation !== 0)
+ {
+ var css = this.options.css,
+ selector = getCssSelector(css.actions),
+ actionItems = findFooterAndHeaderItems.call(this, selector);
- // checks whether there is an tbody element; otherwise creates one
- if (this.element.children("tbody").length === 0)
+ if (actionItems.length > 0)
{
- this.element.append(tpl.body);
- }
+ var that = this,
+ tpl = this.options.templates,
+ actions = $(tpl.actions.resolve(getParams.call(this)));
- if (this.options.navigation & 1)
- {
- this.header = $(tpl.header.resolve(getParams.call(this, { id: this.element._bgId() + "-header" })));
- wrapper.before(this.header);
- }
+ // Refresh Button
+ if (this.options.ajax)
+ {
+ var refreshIcon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconRefresh })),
+ refresh = $(tpl.actionButton.resolve(getParams.call(this,
+ { content: refreshIcon, text: this.options.labels.refresh })))
+ .on("click" + namespace, function (e)
+ {
+ // todo: prevent multiple fast clicks (fast click detection)
+ e.stopPropagation();
+ that.current = 1;
+ loadData.call(that);
+ });
+ actions.append(refresh);
+ }
- if (this.options.navigation & 2)
- {
- this.footer = $(tpl.footer.resolve(getParams.call(this, { id: this.element._bgId() + "-footer" })));
- wrapper.after(this.footer);
+ // Row count selection
+ renderRowCountSelection.call(this, actions);
+
+ // Column selection
+ renderColumnSelection.call(this, actions);
+
+ replacePlaceHolder.call(this, actionItems, actions);
}
}
+}
- function renderActions()
+function renderColumnSelection(actions)
+{
+ if (this.options.columnSelection && this.columns.length > 1)
{
- if (this.options.navigation !== 0)
- {
- var css = this.options.css,
- selector = getCssSelector(css.actions),
- actionItems = findFooterAndHeaderItems.call(this, selector);
+ var that = this,
+ css = this.options.css,
+ tpl = this.options.templates,
+ icon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconColumns })),
+ dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: icon }))),
+ selector = getCssSelector(css.dropDownItem),
+ checkboxSelector = getCssSelector(css.dropDownItemCheckbox),
+ itemsSelector = getCssSelector(css.dropDownMenuItems);
- if (actionItems.length > 0)
+ $.each(this.columns, function (i, column)
+ {
+ if (column.visibleInSelection)
{
- var that = this,
- tpl = this.options.templates,
- actions = $(tpl.actions.resolve(getParams.call(this)));
-
- // Refresh Button
- if (this.options.ajax)
- {
- var refreshIcon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconRefresh })),
- refresh = $(tpl.actionButton.resolve(getParams.call(this,
- { content: refreshIcon, text: this.options.labels.refresh })))
- .on("click" + namespace, function (e)
+ var item = $(tpl.actionDropDownCheckboxItem.resolve(getParams.call(that,
+ { name: column.id, label: column.text, checked: column.visible })))
+ .on("click" + namespace, selector, function (e)
+ {
+ e.stopPropagation();
+
+ var $this = $(this),
+ checkbox = $this.find(checkboxSelector);
+ if (!checkbox.prop("disabled"))
{
- // todo: prevent multiple fast clicks (fast click detection)
- e.stopPropagation();
- that.current = 1;
+ column.visible = checkbox.prop("checked");
+ var enable = that.columns.where(isVisible).length > 1;
+ $this.parents(itemsSelector).find(selector + ":has(" + checkboxSelector + ":checked)")
+ ._bgEnableAria(enable).find(checkboxSelector)._bgEnableField(enable);
+
+ that.element.find("tbody").empty(); // Fixes an column visualization bug
+ renderTableHeader.call(that);
loadData.call(that);
- });
- actions.append(refresh);
- }
-
- // Row count selection
- renderRowCountSelection.call(this, actions);
-
- // Column selection
- renderColumnSelection.call(this, actions);
-
- replacePlaceHolder.call(this, actionItems, actions);
+ }
+ });
+ dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item);
}
- }
+ });
+ actions.append(dropDown);
}
+}
- function renderColumnSelection(actions)
+function renderInfos()
+{
+ if (this.options.navigation !== 0)
{
- if (this.options.columnSelection && this.columns.length > 1)
- {
- var that = this,
- css = this.options.css,
- tpl = this.options.templates,
- icon = tpl.icon.resolve(getParams.call(this, { iconCss: css.iconColumns })),
- dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: icon }))),
- selector = getCssSelector(css.dropDownItem),
- checkboxSelector = getCssSelector(css.dropDownItemCheckbox),
- itemsSelector = getCssSelector(css.dropDownMenuItems);
+ var selector = getCssSelector(this.options.css.infos),
+ infoItems = findFooterAndHeaderItems.call(this, selector);
- $.each(this.columns, function (i, column)
- {
- if (column.visibleInSelection)
- {
- var item = $(tpl.actionDropDownCheckboxItem.resolve(getParams.call(that,
- { name: column.id, label: column.text, checked: column.visible })))
- .on("click" + namespace, selector, function (e)
- {
- e.stopPropagation();
-
- var $this = $(this),
- checkbox = $this.find(checkboxSelector);
- if (!checkbox.prop("disabled"))
- {
- column.visible = checkbox.prop("checked");
- var enable = that.columns.where(isVisible).length > 1;
- $this.parents(itemsSelector).find(selector + ":has(" + checkboxSelector + ":checked)")
- ._bgEnableAria(enable).find(checkboxSelector)._bgEnableField(enable);
-
- that.element.find("tbody").empty(); // Fixes an column visualization bug
- renderTableHeader.call(that);
- loadData.call(that);
- }
- });
- dropDown.find(getCssSelector(css.dropDownMenuItems)).append(item);
- }
- });
- actions.append(dropDown);
+ if (infoItems.length > 0)
+ {
+ var end = (this.current * this.rowCount),
+ infos = $(this.options.templates.infos.resolve(getParams.call(this, {
+ end: (this.total === 0 || end === -1 || end > this.total) ? this.total : end,
+ start: (this.total === 0) ? 0 : (end - this.rowCount + 1),
+ total: this.total
+ })));
+
+ replacePlaceHolder.call(this, infoItems, infos);
}
}
+}
+
+function renderNoResultsRow()
+{
+ var tbody = this.element.children("tbody").first(),
+ tpl = this.options.templates,
+ count = this.columns.where(isVisible).length;
+
+ if (this.selection)
+ {
+ count = count + 1;
+ }
+ tbody.html(tpl.noResults.resolve(getParams.call(this, { columns: count })));
+}
- function renderInfos()
+function renderPagination()
+{
+ if (this.options.navigation !== 0)
{
- if (this.options.navigation !== 0)
+ var selector = getCssSelector(this.options.css.pagination),
+ paginationItems = findFooterAndHeaderItems.call(this, selector)._bgShowAria(this.rowCount !== -1);
+
+ if (this.rowCount !== -1 && paginationItems.length > 0)
{
- var selector = getCssSelector(this.options.css.infos),
- infoItems = findFooterAndHeaderItems.call(this, selector);
+ var tpl = this.options.templates,
+ current = this.current,
+ totalPages = this.totalPages,
+ pagination = $(tpl.pagination.resolve(getParams.call(this))),
+ offsetRight = totalPages - current,
+ offsetLeft = (this.options.padding - current) * -1,
+ startWith = ((offsetRight >= this.options.padding) ?
+ Math.max(offsetLeft, 1) :
+ Math.max((offsetLeft - this.options.padding + offsetRight), 1)),
+ maxCount = this.options.padding * 2 + 1,
+ count = (totalPages >= maxCount) ? maxCount : totalPages;
+
+ renderPaginationItem.call(this, pagination, "first", "«", "first")
+ ._bgEnableAria(current > 1);
+ renderPaginationItem.call(this, pagination, "prev", "‹", "prev")
+ ._bgEnableAria(current > 1);
+
+ for (var i = 0; i < count; i++)
+ {
+ var pos = i + startWith;
+ renderPaginationItem.call(this, pagination, pos, pos, "page-" + pos)
+ ._bgEnableAria()._bgSelectAria(pos === current);
+ }
- if (infoItems.length > 0)
+ if (count === 0)
{
- var end = (this.current * this.rowCount),
- infos = $(this.options.templates.infos.resolve(getParams.call(this, {
- end: (this.total === 0 || end === -1 || end > this.total) ? this.total : end,
- start: (this.total === 0) ? 0 : (end - this.rowCount + 1),
- total: this.total
- })));
-
- replacePlaceHolder.call(this, infoItems, infos);
+ renderPaginationItem.call(this, pagination, 1, 1, "page-" + 1)
+ ._bgEnableAria(false)._bgSelectAria();
}
- }
- }
- function renderNoResultsRow()
- {
- var tbody = this.element.children("tbody").first(),
- tpl = this.options.templates,
- count = this.columns.where(isVisible).length;
+ renderPaginationItem.call(this, pagination, "next", "›", "next")
+ ._bgEnableAria(totalPages > current);
+ renderPaginationItem.call(this, pagination, "last", "»", "last")
+ ._bgEnableAria(totalPages > current);
- if (this.selection)
- {
- count = count + 1;
+ replacePlaceHolder.call(this, paginationItems, pagination);
}
- tbody.html(tpl.noResults.resolve(getParams.call(this, { columns: count })));
}
+}
- function renderPagination()
- {
- if (this.options.navigation !== 0)
- {
- var selector = getCssSelector(this.options.css.pagination),
- paginationItems = findFooterAndHeaderItems.call(this, selector)._bgShowAria(this.rowCount !== -1);
-
- if (this.rowCount !== -1 && paginationItems.length > 0)
+function renderPaginationItem(list, page, text, markerCss)
+{
+ var that = this,
+ tpl = this.options.templates,
+ css = this.options.css,
+ values = getParams.call(this, { css: markerCss, text: text, page: page }),
+ item = $(tpl.paginationItem.resolve(values))
+ .on("click" + namespace, getCssSelector(css.paginationButton), function (e)
{
- var tpl = this.options.templates,
- current = this.current,
- totalPages = this.totalPages,
- pagination = $(tpl.pagination.resolve(getParams.call(this))),
- offsetRight = totalPages - current,
- offsetLeft = (this.options.padding - current) * -1,
- startWith = ((offsetRight >= this.options.padding) ?
- Math.max(offsetLeft, 1) :
- Math.max((offsetLeft - this.options.padding + offsetRight), 1)),
- maxCount = this.options.padding * 2 + 1,
- count = (totalPages >= maxCount) ? maxCount : totalPages;
-
- renderPaginationItem.call(this, pagination, "first", "«", "first")
- ._bgEnableAria(current > 1);
- renderPaginationItem.call(this, pagination, "prev", "<", "prev")
- ._bgEnableAria(current > 1);
-
- for (var i = 0; i < count; i++)
- {
- var pos = i + startWith;
- renderPaginationItem.call(this, pagination, pos, pos, "page-" + pos)
- ._bgEnableAria()._bgSelectAria(pos === current);
- }
+ e.stopPropagation();
+ e.preventDefault();
- if (count === 0)
+ var $this = $(this),
+ parent = $this.parent();
+ if (!parent.hasClass("active") && !parent.hasClass("disabled"))
{
- renderPaginationItem.call(this, pagination, 1, 1, "page-" + 1)
- ._bgEnableAria(false)._bgSelectAria();
+ var commandList = {
+ first: 1,
+ prev: that.current - 1,
+ next: that.current + 1,
+ last: that.totalPages
+ };
+ var command = $this.data("page");
+ that.current = commandList[command] || command;
+ loadData.call(that);
}
+ $this.trigger("blur");
+ });
- renderPaginationItem.call(this, pagination, "next", ">", "next")
- ._bgEnableAria(totalPages > current);
- renderPaginationItem.call(this, pagination, "last", "»", "last")
- ._bgEnableAria(totalPages > current);
+ list.append(item);
+ return item;
+}
- replacePlaceHolder.call(this, paginationItems, pagination);
- }
- }
- }
+function renderRowCountSelection(actions)
+{
+ var that = this,
+ rowCountList = this.options.rowCount;
- function renderPaginationItem(list, page, text, markerCss)
+ function getText(value)
{
- var that = this,
- tpl = this.options.templates,
- css = this.options.css,
- values = getParams.call(this, { css: markerCss, text: text, page: page }),
- item = $(tpl.paginationItem.resolve(values))
- .on("click" + namespace, getCssSelector(css.paginationButton), function (e)
- {
- e.stopPropagation();
- e.preventDefault();
-
- var $this = $(this),
- parent = $this.parent();
- if (!parent.hasClass("active") && !parent.hasClass("disabled"))
- {
- var commandList = {
- first: 1,
- prev: that.current - 1,
- next: that.current + 1,
- last: that.totalPages
- };
- var command = $this.data("page");
- that.current = commandList[command] || command;
- loadData.call(that);
- }
- $this.trigger("blur");
- });
-
- list.append(item);
- return item;
+ return (value === -1) ? that.options.labels.all : value;
}
- function renderRowCountSelection(actions)
+ if ($.isArray(rowCountList))
{
- var that = this,
- rowCountList = this.options.rowCount;
-
- function getText(value)
- {
- return (value === -1) ? that.options.labels.all : value;
- }
+ var css = this.options.css,
+ tpl = this.options.templates,
+ dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: getText(this.rowCount) }))),
+ menuSelector = getCssSelector(css.dropDownMenu),
+ menuTextSelector = getCssSelector(css.dropDownMenuText),
+ menuItemsSelector = getCssSelector(css.dropDownMenuItems),
+ menuItemSelector = getCssSelector(css.dropDownItemButton);
- if ($.isArray(rowCountList))
+ $.each(rowCountList, function (index, value)
{
- var css = this.options.css,
- tpl = this.options.templates,
- dropDown = $(tpl.actionDropDown.resolve(getParams.call(this, { content: getText(this.rowCount) }))),
- menuSelector = getCssSelector(css.dropDownMenu),
- menuTextSelector = getCssSelector(css.dropDownMenuText),
- menuItemsSelector = getCssSelector(css.dropDownMenuItems),
- menuItemSelector = getCssSelector(css.dropDownItemButton);
+ var item = $(tpl.actionDropDownItem.resolve(getParams.call(that,
+ { text: getText(value), action: value })))
+ ._bgSelectAria(value === that.rowCount)
+ .on("click" + namespace, menuItemSelector, function (e)
+ {
+ e.preventDefault();
- $.each(rowCountList, function (index, value)
- {
- var item = $(tpl.actionDropDownItem.resolve(getParams.call(that,
- { text: getText(value), action: value })))
- ._bgSelectAria(value === that.rowCount)
- .on("click" + namespace, menuItemSelector, function (e)
+ var $this = $(this),
+ newRowCount = $this.data("action");
+ if (newRowCount !== that.rowCount)
{
- e.preventDefault();
-
- var $this = $(this),
- newRowCount = $this.data("action");
- if (newRowCount !== that.rowCount)
+ // todo: sophisticated solution needed for calculating which page is selected
+ that.current = 1; // that.rowCount === -1 ---> All
+ that.rowCount = newRowCount;
+ $this.parents(menuItemsSelector).children().each(function ()
{
- // todo: sophisticated solution needed for calculating which page is selected
- that.current = 1; // that.rowCount === -1 ---> All
- that.rowCount = newRowCount;
- $this.parents(menuItemsSelector).children().each(function ()
- {
- var $item = $(this),
- currentRowCount = $item.find(menuItemSelector).data("action");
- $item._bgSelectAria(currentRowCount === newRowCount);
- });
- $this.parents(menuSelector).find(menuTextSelector).text(getText(newRowCount));
- loadData.call(that);
- }
- });
- dropDown.find(menuItemsSelector).append(item);
- });
- actions.append(dropDown);
- }
+ var $item = $(this),
+ currentRowCount = $item.find(menuItemSelector).data("action");
+ $item._bgSelectAria(currentRowCount === newRowCount);
+ });
+ $this.parents(menuSelector).find(menuTextSelector).text(getText(newRowCount));
+ loadData.call(that);
+ }
+ });
+ dropDown.find(menuItemsSelector).append(item);
+ });
+ actions.append(dropDown);
}
+}
- function renderRows(rows)
+function renderRows(rows)
+{
+ if (rows.length > 0)
{
- if (rows.length > 0)
+ var that = this,
+ css = this.options.css,
+ tpl = this.options.templates,
+ tbody = this.element.children("tbody").first(),
+ allRowsSelected = true,
+ html = "";
+
+ $.each(rows, function (index, row)
{
- var that = this,
- css = this.options.css,
- tpl = this.options.templates,
- tbody = this.element.children("tbody").first(),
- allRowsSelected = true,
- html = "";
+ var cells = "",
+ rowAttr = " data-row-id=\"" + ((that.identifier == null) ? index : row[that.identifier]) + "\"",
+ rowCss = "";
- $.each(rows, function (index, row)
+ if (that.selection)
{
- var cells = "",
- rowAttr = " data-row-id=\"" + ((that.identifier == null) ? index : row[that.identifier]) + "\"",
- rowCss = "";
-
- if (that.selection)
- {
- var selected = ($.inArray(row[that.identifier], that.selectedRows) !== -1),
- selectBox = tpl.select.resolve(getParams.call(that,
- { type: "checkbox", value: row[that.identifier], checked: selected }));
- cells += tpl.cell.resolve(getParams.call(that, { content: selectBox, css: css.selectCell }));
- allRowsSelected = (allRowsSelected && selected);
- if (selected)
- {
- rowCss += css.selected;
- rowAttr += " aria-selected=\"true\"";
- }
- }
-
- var status = row.status != null && that.options.statusMapping[row.status];
- if (status)
+ var selected = ($.inArray(row[that.identifier], that.selectedRows) !== -1),
+ selectBox = tpl.select.resolve(getParams.call(that,
+ { type: "checkbox", value: row[that.identifier], checked: selected }));
+ cells += tpl.cell.resolve(getParams.call(that, { content: selectBox, css: css.selectCell }));
+ allRowsSelected = (allRowsSelected && selected);
+ if (selected)
{
- rowCss += status;
+ rowCss += css.selected;
+ rowAttr += " aria-selected=\"true\"";
}
+ }
- $.each(that.columns, function (j, column)
- {
- if (column.visible)
- {
- var value = ($.isFunction(column.formatter)) ?
- column.formatter.call(that, column, row) :
- column.converter.to(row[column.id]),
- cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : "";
- cells += tpl.cell.resolve(getParams.call(that, {
- content: (value == null || value === "") ? " " : value,
- css: ((column.align === "right") ? css.right : (column.align === "center") ?
- css.center : css.left) + cssClass,
- style: (column.width == null) ? "" : "width:" + column.width + ";" }));
- }
- });
+ var status = row.status != null && that.options.statusMapping[row.status];
+ if (status)
+ {
+ rowCss += status;
+ }
- if (rowCss.length > 0)
+ $.each(that.columns, function (j, column)
+ {
+ if (column.visible)
{
- rowAttr += " class=\"" + rowCss + "\"";
+ var value = ($.isFunction(column.formatter)) ?
+ column.formatter.call(that, column, row) :
+ column.converter.to(row[column.id]),
+ cssClass = (column.cssClass.length > 0) ? " " + column.cssClass : "";
+ cells += tpl.cell.resolve(getParams.call(that, {
+ content: (value == null || value === "") ? " " : value,
+ css: ((column.align === "right") ? css.right : (column.align === "center") ?
+ css.center : css.left) + cssClass,
+ style: (column.width == null) ? "" : "width:" + column.width + ";" }));
}
- html += tpl.row.resolve(getParams.call(that, { attr: rowAttr, cells: cells }));
});
- // sets or clears multi selectbox state
- that.element.find("thead " + getCssSelector(that.options.css.selectBox))
- .prop("checked", allRowsSelected);
+ if (rowCss.length > 0)
+ {
+ rowAttr += " class=\"" + rowCss + "\"";
+ }
+ html += tpl.row.resolve(getParams.call(that, { attr: rowAttr, cells: cells }));
+ });
+
+ // sets or clears multi selectbox state
+ that.element.find("thead " + getCssSelector(that.options.css.selectBox))
+ .prop("checked", allRowsSelected);
- tbody.html(html);
+ tbody.html(html);
- registerRowEvents.call(this, tbody);
- }
- else
- {
- renderNoResultsRow.call(this);
- }
+ registerRowEvents.call(this, tbody);
}
-
- function registerRowEvents(tbody)
+ else
{
- var that = this,
- selectBoxSelector = getCssSelector(this.options.css.selectBox);
-
- if (this.selection)
- {
- tbody.off("click" + namespace, selectBoxSelector)
- .on("click" + namespace, selectBoxSelector, function(e)
- {
- e.stopPropagation();
-
- var $this = $(this),
- id = that.converter.from($this.val());
+ renderNoResultsRow.call(this);
+ }
+}
- if ($this.prop("checked"))
- {
- that.select([id]);
- }
- else
- {
- that.deselect([id]);
- }
- });
- }
+function registerRowEvents(tbody)
+{
+ var that = this,
+ selectBoxSelector = getCssSelector(this.options.css.selectBox);
- tbody.off("click" + namespace, "> tr")
- .on("click" + namespace, "> tr", function(e)
+ if (this.selection && !this.options.rowSelect)
+ {
+ tbody.off("click" + namespace, selectBoxSelector)
+ .on("click" + namespace, selectBoxSelector, function(e)
{
e.stopPropagation();
var $this = $(this),
- id = (that.identifier == null) ? $this.data("row-id") :
- that.converter.from($this.data("row-id") + ""),
- row = (that.identifier == null) ? that.currentRows[id] :
- that.currentRows.first(function (item) { return item[that.identifier] === id; });
+ id = that.converter.from($this.val());
- if (that.selection && that.options.rowSelect)
+ if ($this.prop("checked"))
{
- if ($this.hasClass(that.options.css.selected))
- {
- that.deselect([id]);
- }
- else
- {
- that.select([id]);
- }
+ that.select([id]);
+ }
+ else
+ {
+ that.deselect([id]);
}
-
- that.element.trigger("click" + namespace, [that.columns, row]);
});
}
- function renderSearchField()
- {
- if (this.options.navigation !== 0)
+ tbody.off("click" + namespace, "> tr")
+ .on("click" + namespace, "> tr", function(e)
{
- var css = this.options.css,
- selector = getCssSelector(css.search),
- searchItems = findFooterAndHeaderItems.call(this, selector);
+ e.stopPropagation();
+
+ var $this = $(this),
+ id = (that.identifier == null) ? $this.data("row-id") :
+ that.converter.from($this.data("row-id") + ""),
+ row = (that.identifier == null) ? that.currentRows[id] :
+ that.currentRows.first(function (item) { return item[that.identifier] === id; });
+
+ if (that.selection && that.options.rowSelect)
+ {
+ if ($this.hasClass(that.options.css.selected))
+ {
+ that.deselect([id]);
+ }
+ else
+ {
+ that.select([id]);
+ }
+ }
+
+ that.element.trigger("click" + namespace, [that.columns, row]);
+ });
+}
+
+function renderSearchField()
+{
+ if (this.options.navigation !== 0)
+ {
+ var css = this.options.css,
+ selector = getCssSelector(css.search),
+ searchItems = findFooterAndHeaderItems.call(this, selector);
- if (searchItems.length > 0)
+ if (searchItems.length > 0)
+ {
+ var that = this,
+ tpl = this.options.templates,
+ timer = null, // fast keyup detection
+ currentValue = "",
+ searchFieldSelector = getCssSelector(css.searchField),
+ search = $(tpl.search.resolve(getParams.call(this))),
+ searchField = (search.is(searchFieldSelector)) ? search :
+ search.find(searchFieldSelector);
+
+ searchField.on("keyup" + namespace, function (e)
{
- var that = this,
- tpl = this.options.templates,
- timer = null, // fast keyup detection
- currentValue = "",
- searchFieldSelector = getCssSelector(css.searchField),
- search = $(tpl.search.resolve(getParams.call(this))),
- searchField = (search.is(searchFieldSelector)) ? search :
- search.find(searchFieldSelector);
-
- searchField.on("keyup" + namespace, function (e)
+ e.stopPropagation();
+ var newValue = $(this).val();
+ if (currentValue !== newValue || (e.which === 13 && newValue !== ""))
{
- e.stopPropagation();
- var newValue = $(this).val();
- if (currentValue !== newValue || (e.which === 13 && newValue !== ""))
+ currentValue = newValue;
+ if (e.which === 13 || newValue.length === 0 || newValue.length >= that.options.searchSettings.characters)
{
- currentValue = newValue;
- if (e.which === 13 || newValue.length === 0 || newValue.length >= that.options.searchSettings.characters)
+ window.clearTimeout(timer);
+ timer = window.setTimeout(function ()
{
- window.clearTimeout(timer);
- timer = window.setTimeout(function ()
- {
- executeSearch.call(that, newValue);
- }, that.options.searchSettings.delay);
- }
+ executeSearch.call(that, newValue);
+ }, that.options.searchSettings.delay);
}
- });
+ }
+ });
- replacePlaceHolder.call(this, searchItems, search);
- }
+ replacePlaceHolder.call(this, searchItems, search);
}
}
+}
- function executeSearch(phrase)
+function executeSearch(phrase)
+{
+ if (this.searchPhrase !== phrase)
{
- if (this.searchPhrase !== phrase)
- {
- this.current = 1;
- this.searchPhrase = phrase;
- loadData.call(this);
- }
+ this.current = 1;
+ this.searchPhrase = phrase;
+ loadData.call(this);
}
+}
- function renderTableHeader()
+function renderTableHeader()
+{
+ var that = this,
+ headerRow = this.element.find("thead > tr"),
+ css = this.options.css,
+ tpl = this.options.templates,
+ html = "",
+ sorting = this.options.sorting;
+
+ if (this.selection)
{
- var that = this,
- headerRow = this.element.find("thead > tr"),
- css = this.options.css,
- tpl = this.options.templates,
- html = "",
- sorting = this.options.sorting;
+ var selectBox = (this.options.multiSelect) ?
+ tpl.select.resolve(getParams.call(that, { type: "checkbox", value: "all" })) : "";
+ html += tpl.rawHeaderCell.resolve(getParams.call(that, { content: selectBox,
+ css: css.selectCell }));
+ }
- if (this.selection)
+ $.each(this.columns, function (index, column)
+ {
+ if (column.visible)
{
- var selectBox = (this.options.multiSelect) ?
- tpl.select.resolve(getParams.call(that, { type: "checkbox", value: "all" })) : "";
- html += tpl.rawHeaderCell.resolve(getParams.call(that, { content: selectBox,
- css: css.selectCell }));
+ var sortOrder = that.sortDictionary[column.id],
+ iconCss = ((sorting && sortOrder && sortOrder === "asc") ? css.iconUp :
+ (sorting && sortOrder && sortOrder === "desc") ? css.iconDown : ""),
+ icon = tpl.icon.resolve(getParams.call(that, { iconCss: iconCss })),
+ align = column.headerAlign,
+ cssClass = (column.headerCssClass.length > 0) ? " " + column.headerCssClass : "";
+ html += tpl.headerCell.resolve(getParams.call(that, {
+ column: column, icon: icon, sortable: sorting && column.sortable && css.sortable || "",
+ css: ((align === "right") ? css.right : (align === "center") ?
+ css.center : css.left) + cssClass,
+ style: (column.width == null) ? "" : "width:" + column.width + ";" }));
}
+ });
- $.each(this.columns, function (index, column)
- {
- if (column.visible)
- {
- var sortOrder = that.sortDictionary[column.id],
- iconCss = ((sorting && sortOrder && sortOrder === "asc") ? css.iconUp :
- (sorting && sortOrder && sortOrder === "desc") ? css.iconDown : ""),
- icon = tpl.icon.resolve(getParams.call(that, { iconCss: iconCss })),
- align = column.headerAlign,
- cssClass = (column.headerCssClass.length > 0) ? " " + column.headerCssClass : "";
- html += tpl.headerCell.resolve(getParams.call(that, {
- column: column, icon: icon, sortable: sorting && column.sortable && css.sortable || "",
- css: ((align === "right") ? css.right : (align === "center") ?
- css.center : css.left) + cssClass,
- style: (column.width == null) ? "" : "width:" + column.width + ";" }));
- }
- });
+ headerRow.html(html);
- headerRow.html(html);
+ if (sorting)
+ {
+ var sortingSelector = getCssSelector(css.sortable);
+ headerRow.off("click" + namespace, sortingSelector)
+ .on("click" + namespace, sortingSelector, function (e)
+ {
+ e.preventDefault();
- if (sorting)
- {
- var sortingSelector = getCssSelector(css.sortable);
- headerRow.off("click" + namespace, sortingSelector)
- .on("click" + namespace, sortingSelector, function (e)
- {
- e.preventDefault();
+ setTableHeaderSortDirection.call(that, $(this));
+ sortRows.call(that);
+ loadData.call(that);
+ });
+ }
- setTableHeaderSortDirection.call(that, $(this));
- sortRows.call(that);
- loadData.call(that);
- });
- }
+ // todo: create a own function for that piece of code
+ if (this.selection && this.options.multiSelect)
+ {
+ var selectBoxSelector = getCssSelector(css.selectBox);
+ headerRow.off("click" + namespace, selectBoxSelector)
+ .on("click" + namespace, selectBoxSelector, function(e)
+ {
+ e.stopPropagation();
- // todo: create a own function for that piece of code
- if (this.selection && this.options.multiSelect)
- {
- var selectBoxSelector = getCssSelector(css.selectBox);
- headerRow.off("click" + namespace, selectBoxSelector)
- .on("click" + namespace, selectBoxSelector, function(e)
+ if ($(this).prop("checked"))
{
- e.stopPropagation();
-
- if ($(this).prop("checked"))
- {
- that.select();
- }
- else
- {
- that.deselect();
- }
- });
- }
+ that.select();
+ }
+ else
+ {
+ that.deselect();
+ }
+ });
}
+}
- function setTableHeaderSortDirection(element)
- {
- var css = this.options.css,
- iconSelector = getCssSelector(css.icon),
- columnId = element.data("column-id") || element.parents("th").first().data("column-id"),
- sortOrder = this.sortDictionary[columnId],
- icon = element.find(iconSelector);
+function setTableHeaderSortDirection(element)
+{
+ var css = this.options.css,
+ iconSelector = getCssSelector(css.icon),
+ columnId = element.data("column-id") || element.parents("th").first().data("column-id"),
+ sortOrder = this.sortDictionary[columnId],
+ icon = element.find(iconSelector);
- if (!this.options.multiSort)
- {
- element.parents("tr").first().find(iconSelector).removeClass(css.iconDown + " " + css.iconUp);
- this.sortDictionary = {};
- }
+ if (!this.options.multiSort)
+ {
+ element.parents("tr").first().find(iconSelector).removeClass(css.iconDown + " " + css.iconUp);
+ this.sortDictionary = {};
+ }
- if (sortOrder && sortOrder === "asc")
- {
- this.sortDictionary[columnId] = "desc";
- icon.removeClass(css.iconUp).addClass(css.iconDown);
- }
- else if (sortOrder && sortOrder === "desc")
+ if (sortOrder && sortOrder === "asc")
+ {
+ this.sortDictionary[columnId] = "desc";
+ icon.removeClass(css.iconUp).addClass(css.iconDown);
+ }
+ else if (sortOrder && sortOrder === "desc")
+ {
+ if (this.options.multiSort)
{
- if (this.options.multiSort)
+ var newSort = {};
+ for (var key in this.sortDictionary)
{
- var newSort = {};
- for (var key in this.sortDictionary)
+ if (key !== columnId)
{
- if (key !== columnId)
- {
- newSort[key] = this.sortDictionary[key];
- }
+ newSort[key] = this.sortDictionary[key];
}
- this.sortDictionary = newSort;
- icon.removeClass(css.iconDown);
- }
- else
- {
- this.sortDictionary[columnId] = "asc";
- icon.removeClass(css.iconDown).addClass(css.iconUp);
}
+ this.sortDictionary = newSort;
+ icon.removeClass(css.iconDown);
}
else
{
this.sortDictionary[columnId] = "asc";
- icon.addClass(css.iconUp);
+ icon.removeClass(css.iconDown).addClass(css.iconUp);
}
}
-
- function replacePlaceHolder(placeholder, element)
+ else
{
- placeholder.each(function (index, item)
- {
- // todo: check how append is implemented. Perhaps cloning here is superfluous.
- $(item).before(element.clone(true)).remove();
- });
+ this.sortDictionary[columnId] = "asc";
+ icon.addClass(css.iconUp);
}
+}
- function showLoading()
+function replacePlaceHolder(placeholder, element)
+{
+ placeholder.each(function (index, item)
{
- var that = this;
+ // todo: check how append is implemented. Perhaps cloning here is superfluous.
+ $(item).before(element.clone(true)).remove();
+ });
+}
- window.setTimeout(function()
+function showLoading()
+{
+ var that = this;
+
+ window.setTimeout(function()
+ {
+ if (that.element._bgAria("busy") === "true")
{
- if (that.element._bgAria("busy") === "true")
+ var tpl = that.options.templates,
+ thead = that.element.children("thead").first(),
+ tbody = that.element.children("tbody").first(),
+ firstCell = tbody.find("tr > td").first(),
+ padding = (that.element.height() - thead.height()) - (firstCell.height() + 20),
+ count = that.columns.where(isVisible).length;
+
+ if (that.selection)
{
- var tpl = that.options.templates,
- thead = that.element.children("thead").first(),
- tbody = that.element.children("tbody").first(),
- firstCell = tbody.find("tr > td").first(),
- padding = (that.element.height() - thead.height()) - (firstCell.height() + 20),
- count = that.columns.where(isVisible).length;
-
- if (that.selection)
- {
- count = count + 1;
- }
- tbody.html(tpl.loading.resolve(getParams.call(that, { columns: count })));
- if (that.rowCount !== -1 && padding > 0)
- {
- tbody.find("tr > td").css("padding", "20px 0 " + padding + "px");
- }
+ count = count + 1;
}
- }, 250);
- }
+ tbody.html(tpl.loading.resolve(getParams.call(that, { columns: count })));
+ if (that.rowCount !== -1 && padding > 0)
+ {
+ tbody.find("tr > td").css("padding", "20px 0 " + padding + "px");
+ }
+ }
+ }, 250);
+}
+
+function sortRows()
+{
+ var sortArray = [];
- function sortRows()
+ function sort(x, y, current)
{
- var sortArray = [];
+ current = current || 0;
+ var next = current + 1,
+ item = sortArray[current];
- function sort(x, y, current)
+ function sortOrder(value)
{
- current = current || 0;
- var next = current + 1,
- item = sortArray[current];
+ return (item.order === "asc") ? value : value * -1;
+ }
- function sortOrder(value)
- {
- return (item.order === "asc") ? value : value * -1;
- }
+ return (x[item.id] > y[item.id]) ? sortOrder(1) :
+ (x[item.id] < y[item.id]) ? sortOrder(-1) :
+ (sortArray.length > next) ? sort(x, y, next) : 0;
+ }
- return (x[item.id] > y[item.id]) ? sortOrder(1) :
- (x[item.id] < y[item.id]) ? sortOrder(-1) :
- (sortArray.length > next) ? sort(x, y, next) : 0;
- }
+ if (!this.options.ajax)
+ {
+ var that = this;
- if (!this.options.ajax)
+ for (var key in this.sortDictionary)
{
- var that = this;
-
- for (var key in this.sortDictionary)
+ if (this.options.multiSort || sortArray.length === 0)
{
- if (this.options.multiSort || sortArray.length === 0)
- {
- sortArray.push({
- id: key,
- order: this.sortDictionary[key]
- });
- }
+ sortArray.push({
+ id: key,
+ order: this.sortDictionary[key]
+ });
}
+ }
- if (sortArray.length > 0)
- {
- this.rows.sort(sort);
- }
+ if (sortArray.length > 0)
+ {
+ this.rows.sort(sort);
}
}
+ }
- // GRID PUBLIC CLASS DEFINITION
- // ====================
+// GRID PUBLIC CLASS DEFINITION
+// ====================
+
+/**
+ * Represents the jQuery Bootgrid plugin.
+ *
+ * @class Grid
+ * @constructor
+ * @param element {Object} The corresponding DOM element.
+ * @param options {Object} The options to override default settings.
+ * @chainable
+ **/
+var Grid = function(element, options)
+{
+ this.element = $(element);
+ this.origin = this.element.clone();
+ this.options = $.extend(true, {}, Grid.defaults, this.element.data(), options);
+ // overrides rowCount explicitly because deep copy ($.extend) leads to strange behaviour
+ var rowCount = this.options.rowCount = this.element.data().rowCount || options.rowCount || this.options.rowCount;
+ this.columns = [];
+ this.current = 1;
+ this.currentRows = [];
+ this.identifier = null; // The first column ID that is marked as identifier
+ this.selection = false;
+ this.converter = null; // The converter for the column that is marked as identifier
+ this.rowCount = ($.isArray(rowCount)) ? rowCount[0] : rowCount;
+ this.rows = [];
+ this.searchPhrase = "";
+ this.selectedRows = [];
+ this.sortDictionary = {};
+ this.total = 0;
+ this.totalPages = 0;
+ this.cachedParams = {
+ lbl: this.options.labels,
+ css: this.options.css,
+ ctx: {}
+ };
+ this.header = null;
+ this.footer = null;
+ this.xqr = null;
+
+ // todo: implement cache
+};
+
+/**
+ * An object that represents the default settings.
+ *
+ * @static
+ * @class defaults
+ * @for Grid
+ * @example
+ * // Global approach
+ * $.bootgrid.defaults.selection = true;
+ * @example
+ * // Initialization approach
+ * $("#bootgrid").bootgrid({ selection = true });
+ **/
+Grid.defaults = {
+ navigation: 3, // it's a flag: 0 = none, 1 = top, 2 = bottom, 3 = both (top and bottom)
+ padding: 2, // page padding (pagination)
+ columnSelection: true,
+ rowCount: [10, 25, 50, -1], // rows per page int or array of int (-1 represents "All")
/**
- * Represents the jQuery Bootgrid plugin.
+ * Enables row selection (to enable multi selection see also `multiSelect`). Default value is `false`.
*
- * @class Grid
- * @constructor
- * @param element {Object} The corresponding DOM element.
- * @param options {Object} The options to override default settings.
- * @chainable
+ * @property selection
+ * @type Boolean
+ * @default false
+ * @for defaults
+ * @since 1.0.0
**/
- var Grid = function(element, options)
- {
- this.element = $(element);
- this.origin = this.element.clone();
- this.options = $.extend(true, {}, Grid.defaults, this.element.data(), options);
- // overrides rowCount explicitly because deep copy ($.extend) leads to strange behaviour
- var rowCount = this.options.rowCount = this.element.data().rowCount || options.rowCount || this.options.rowCount;
- this.columns = [];
- this.current = 1;
- this.currentRows = [];
- this.identifier = null; // The first column ID that is marked as identifier
- this.selection = false;
- this.converter = null; // The converter for the column that is marked as identifier
- this.rowCount = ($.isArray(rowCount)) ? rowCount[0] : rowCount;
- this.rows = [];
- this.searchPhrase = "";
- this.selectedRows = [];
- this.sortDictionary = {};
- this.total = 0;
- this.totalPages = 0;
- this.cachedParams = {
- lbl: this.options.labels,
- css: this.options.css,
- ctx: {}
- };
- this.header = null;
- this.footer = null;
- this.xqr = null;
+ selection: false,
- // todo: implement cache
- };
+ /**
+ * Enables multi selection (`selection` must be set to `true` as well). Default value is `false`.
+ *
+ * @property multiSelect
+ * @type Boolean
+ * @default false
+ * @for defaults
+ * @since 1.0.0
+ **/
+ multiSelect: false,
/**
- * An object that represents the default settings.
+ * Enables entire row click selection (`selection` must be set to `true` as well). Default value is `false`.
*
- * @static
- * @class defaults
- * @for Grid
- * @example
- * // Global approach
- * $.bootgrid.defaults.selection = true;
- * @example
- * // Initialization approach
- * $("#bootgrid").bootgrid({ selection = true });
+ * @property rowSelect
+ * @type Boolean
+ * @default false
+ * @for defaults
+ * @since 1.1.0
**/
- Grid.defaults = {
- navigation: 3, // it's a flag: 0 = none, 1 = top, 2 = bottom, 3 = both (top and bottom)
- padding: 2, // page padding (pagination)
- columnSelection: true,
- rowCount: [10, 25, 50, -1], // rows per page int or array of int (-1 represents "All")
+ rowSelect: false,
- /**
- * Enables row selection (to enable multi selection see also `multiSelect`). Default value is `false`.
- *
- * @property selection
- * @type Boolean
- * @default false
- * @for defaults
- * @since 1.0.0
- **/
- selection: false,
+ /**
+ * Defines whether the row selection is saved internally on filtering, paging and sorting
+ * (even if the selected rows are not visible).
+ *
+ * @property keepSelection
+ * @type Boolean
+ * @default false
+ * @for defaults
+ * @since 1.1.0
+ **/
+ keepSelection: false,
- /**
- * Enables multi selection (`selection` must be set to `true` as well). Default value is `false`.
- *
- * @property multiSelect
- * @type Boolean
- * @default false
- * @for defaults
- * @since 1.0.0
- **/
- multiSelect: false,
+ highlightRows: false, // highlights new rows (find the page of the first new row)
+ sorting: true,
+ multiSort: false,
+ /**
+ * General search settings to configure the search field behaviour.
+ *
+ * @property searchSettings
+ * @type Object
+ * @for defaults
+ * @since 1.2.0
+ **/
+ searchSettings: {
/**
- * Enables entire row click selection (`selection` must be set to `true` as well). Default value is `false`.
+ * The time in milliseconds to wait before search gets executed.
*
- * @property rowSelect
- * @type Boolean
- * @default false
- * @for defaults
- * @since 1.1.0
+ * @property delay
+ * @type Number
+ * @default 250
+ * @for searchSettings
**/
- rowSelect: false,
-
+ delay: 250,
+
/**
- * Defines whether the row selection is saved internally on filtering, paging and sorting
- * (even if the selected rows are not visible).
+ * The characters to type before the search gets executed.
*
- * @property keepSelection
- * @type Boolean
- * @default false
- * @for defaults
- * @since 1.1.0
+ * @property characters
+ * @type Number
+ * @default 1
+ * @for searchSettings
**/
- keepSelection: false,
+ characters: 1
+ },
- highlightRows: false, // highlights new rows (find the page of the first new row)
- sorting: true,
- multiSort: false,
+ /**
+ * Defines whether the data shall be loaded via an asynchronous HTTP (Ajax) request.
+ *
+ * @property ajax
+ * @type Boolean
+ * @default false
+ * @for defaults
+ **/
+ ajax: false,
+ /**
+ * Ajax request settings that shall be used for server-side communication.
+ * All setting except data, error, success and url can be overridden.
+ * For the full list of settings go to http://api.jquery.com/jQuery.ajax/.
+ *
+ * @property ajaxSettings
+ * @type Object
+ * @for defaults
+ * @since 1.2.0
+ **/
+ ajaxSettings: {
/**
- * General search settings to configure the search field behaviour.
+ * Specifies the HTTP method which shall be used when sending data to the server.
+ * Go to http://api.jquery.com/jQuery.ajax/ for more details.
+ * This setting is overriden for backward compatibility.
*
- * @property searchSettings
- * @type Object
- * @for defaults
- * @since 1.2.0
+ * @property method
+ * @type String
+ * @default "POST"
+ * @for ajaxSettings
**/
- searchSettings: {
- /**
- * The time in milliseconds to wait before search gets executed.
- *
- * @property delay
- * @type Number
- * @default 250
- * @for searchSettings
- **/
- delay: 250,
-
- /**
- * The characters to type before the search gets executed.
- *
- * @property characters
- * @type Number
- * @default 1
- * @for searchSettings
- **/
- characters: 1
- },
+ method: "POST"
+ },
- /**
- * Defines whether the data shall be loaded via an asynchronous HTTP (Ajax) request.
- *
- * @property ajax
- * @type Boolean
- * @default false
- * @for defaults
- **/
- ajax: false,
+ /**
+ * Enriches the request object with additional properties. Either a `PlainObject` or a `Function`
+ * that returns a `PlainObject` can be passed. Default value is `{}`.
+ *
+ * @property post
+ * @type Object|Function
+ * @default function (request) { return request; }
+ * @for defaults
+ * @deprecated Use instead `requestHandler`
+ **/
+ post: {}, // or use function () { return {}; } (reserved properties are "current", "rowCount", "sort" and "searchPhrase")
- /**
- * Ajax request settings that shall be used for server-side communication.
- * All setting except data, error, success and url can be overridden.
- * For the full list of settings go to http://api.jquery.com/jQuery.ajax/.
- *
- * @property ajaxSettings
- * @type Object
- * @for defaults
- * @since 1.2.0
- **/
- ajaxSettings: {
- /**
- * Specifies the HTTP method which shall be used when sending data to the server.
- * Go to http://api.jquery.com/jQuery.ajax/ for more details.
- * This setting is overriden for backward compatibility.
- *
- * @property method
- * @type String
- * @default "POST"
- * @for ajaxSettings
- **/
- method: "POST"
- },
+ /**
+ * Sets the data URL to a data service (e.g. a REST service). Either a `String` or a `Function`
+ * that returns a `String` can be passed. Default value is `""`.
+ *
+ * @property url
+ * @type String|Function
+ * @default ""
+ * @for defaults
+ **/
+ url: "", // or use function () { return ""; }
- /**
- * Enriches the request object with additional properties. Either a `PlainObject` or a `Function`
- * that returns a `PlainObject` can be passed. Default value is `{}`.
- *
- * @property post
- * @type Object|Function
- * @default function (request) { return request; }
- * @for defaults
- * @deprecated Use instead `requestHandler`
- **/
- post: {}, // or use function () { return {}; } (reserved properties are "current", "rowCount", "sort" and "searchPhrase")
+ /**
+ * Defines whether the search is case sensitive or insensitive.
+ *
+ * @property caseSensitive
+ * @type Boolean
+ * @default true
+ * @for defaults
+ * @since 1.1.0
+ **/
+ caseSensitive: true,
- /**
- * Sets the data URL to a data service (e.g. a REST service). Either a `String` or a `Function`
- * that returns a `String` can be passed. Default value is `""`.
- *
- * @property url
- * @type String|Function
- * @default ""
- * @for defaults
- **/
- url: "", // or use function () { return ""; }
+ // note: The following properties should not be used via data-api attributes
- /**
- * Defines whether the search is case sensitive or insensitive.
- *
- * @property caseSensitive
- * @type Boolean
- * @default true
- * @for defaults
- * @since 1.1.0
- **/
- caseSensitive: true,
+ /**
+ * Transforms the JSON request object in what ever is needed on the server-side implementation.
+ *
+ * @property requestHandler
+ * @type Function
+ * @default function (request) { return request; }
+ * @for defaults
+ * @since 1.1.0
+ **/
+ requestHandler: function (request) { return request; },
+
+ /**
+ * Transforms the response object into the expected JSON response object.
+ *
+ * @property responseHandler
+ * @type Function
+ * @default function (response) { return response; }
+ * @for defaults
+ * @since 1.1.0
+ **/
+ responseHandler: function (response) { return response; },
+
+ /**
+ * A list of converters.
+ *
+ * @property converters
+ * @type Object
+ * @for defaults
+ * @since 1.0.0
+ **/
+ converters: {
+ numeric: {
+ from: function (value) { return +value; }, // converts from string to numeric
+ to: function (value) { return value + ""; } // converts from numeric to string
+ },
+ string: {
+ // default converter
+ from: function (value) { return value; },
+ to: function (value) { return value; }
+ }
+ },
- // note: The following properties should not be used via data-api attributes
+ /**
+ * Contains all css classes.
+ *
+ * @property css
+ * @type Object
+ * @for defaults
+ **/
+ css: {
+ actions: "actions btn-group", // must be a unique class name or constellation of class names within the header and footer
+ center: "text-center",
+ columnHeaderAnchor: "column-header-anchor", // must be a unique class name or constellation of class names within the column header cell
+ columnHeaderText: "text",
+ dropDownItem: "dropdown-item", // must be a unique class name or constellation of class names within the actionDropDown,
+ dropDownItemButton: "dropdown-item-button", // must be a unique class name or constellation of class names within the actionDropDown
+ dropDownItemCheckbox: "dropdown-item-checkbox", // must be a unique class name or constellation of class names within the actionDropDown
+ dropDownMenu: "dropdown btn-group", // must be a unique class name or constellation of class names within the actionDropDown
+ dropDownMenuItems: "dropdown-menu pull-right", // must be a unique class name or constellation of class names within the actionDropDown
+ dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown
+ footer: "bootgrid-footer container-fluid",
+ header: "bootgrid-header container-fluid",
+ icon: "icon glyphicon",
+ iconColumns: "glyphicon-th-list",
+ iconDown: "glyphicon-chevron-down",
+ iconRefresh: "glyphicon-refresh",
+ iconSearch: "glyphicon-search",
+ iconUp: "glyphicon-chevron-up",
+ infos: "infos", // must be a unique class name or constellation of class names within the header and footer,
+ left: "text-left",
+ pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer
+ paginationButton: "button", // must be a unique class name or constellation of class names within the pagination
/**
- * Transforms the JSON request object in what ever is needed on the server-side implementation.
+ * CSS class to select the parent div which activates responsive mode.
*
- * @property requestHandler
- * @type Function
- * @default function (request) { return request; }
- * @for defaults
+ * @property responsiveTable
+ * @type String
+ * @default "table-responsive"
+ * @for css
* @since 1.1.0
**/
- requestHandler: function (request) { return request; },
+ responsiveTable: "table-responsive",
+
+ right: "text-right",
+ search: "search form-group", // must be a unique class name or constellation of class names within the header and footer
+ searchField: "search-field form-control",
+ selectBox: "select-box", // must be a unique class name or constellation of class names within the entire table
+ selectCell: "select-cell", // must be a unique class name or constellation of class names within the entire table
/**
- * Transforms the response object into the expected JSON response object.
+ * CSS class to highlight selected rows.
*
- * @property responseHandler
- * @type Function
- * @default function (response) { return response; }
- * @for defaults
+ * @property selected
+ * @type String
+ * @default "active"
+ * @for css
* @since 1.1.0
**/
- responseHandler: function (response) { return response; },
+ selected: "active",
- /**
- * A list of converters.
- *
- * @property converters
- * @type Object
- * @for defaults
- * @since 1.0.0
- **/
- converters: {
- numeric: {
- from: function (value) { return +value; }, // converts from string to numeric
- to: function (value) { return value + ""; } // converts from numeric to string
- },
- string: {
- // default converter
- from: function (value) { return value; },
- to: function (value) { return value; }
- }
- },
+ sortable: "sortable",
+ table: "bootgrid-table table"
+ },
- /**
- * Contains all css classes.
- *
- * @property css
- * @type Object
- * @for defaults
- **/
- css: {
- actions: "actions btn-group", // must be a unique class name or constellation of class names within the header and footer
- center: "text-center",
- columnHeaderAnchor: "column-header-anchor", // must be a unique class name or constellation of class names within the column header cell
- columnHeaderText: "text",
- dropDownItem: "dropdown-item", // must be a unique class name or constellation of class names within the actionDropDown,
- dropDownItemButton: "dropdown-item-button", // must be a unique class name or constellation of class names within the actionDropDown
- dropDownItemCheckbox: "dropdown-item-checkbox", // must be a unique class name or constellation of class names within the actionDropDown
- dropDownMenu: "dropdown btn-group", // must be a unique class name or constellation of class names within the actionDropDown
- dropDownMenuItems: "dropdown-menu pull-right", // must be a unique class name or constellation of class names within the actionDropDown
- dropDownMenuText: "dropdown-text", // must be a unique class name or constellation of class names within the actionDropDown
- footer: "bootgrid-footer container-fluid",
- header: "bootgrid-header container-fluid",
- icon: "icon glyphicon",
- iconColumns: "glyphicon-th-list",
- iconDown: "glyphicon-chevron-down",
- iconRefresh: "glyphicon-refresh",
- iconSearch: "glyphicon-search",
- iconUp: "glyphicon-chevron-up",
- infos: "infos", // must be a unique class name or constellation of class names within the header and footer,
- left: "text-left",
- pagination: "pagination", // must be a unique class name or constellation of class names within the header and footer
- paginationButton: "button", // must be a unique class name or constellation of class names within the pagination
-
- /**
- * CSS class to select the parent div which activates responsive mode.
- *
- * @property responsiveTable
- * @type String
- * @default "table-responsive"
- * @for css
- * @since 1.1.0
- **/
- responsiveTable: "table-responsive",
-
- right: "text-right",
- search: "search form-group", // must be a unique class name or constellation of class names within the header and footer
- searchField: "search-field form-control",
- selectBox: "select-box", // must be a unique class name or constellation of class names within the entire table
- selectCell: "select-cell", // must be a unique class name or constellation of class names within the entire table
-
- /**
- * CSS class to highlight selected rows.
- *
- * @property selected
- * @type String
- * @default "active"
- * @for css
- * @since 1.1.0
- **/
- selected: "active",
-
- sortable: "sortable",
- table: "bootgrid-table table"
- },
+ /**
+ * A dictionary of formatters.
+ *
+ * @property formatters
+ * @type Object
+ * @for defaults
+ * @since 1.0.0
+ **/
+ formatters: {},
+
+ /**
+ * Contains all labels.
+ *
+ * @property labels
+ * @type Object
+ * @for defaults
+ **/
+ labels: {
+ all: "All",
+ infos: "Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries",
+ loading: "Loading...",
+ noResults: "No results found!",
+ refresh: "Refresh",
+ search: "Search"
+ },
+ /**
+ * Specifies the mapping between status and contextual classes to color rows.
+ *
+ * @property statusMapping
+ * @type Object
+ * @for defaults
+ * @since 1.2.0
+ **/
+ statusMapping: {
/**
- * A dictionary of formatters.
+ * Specifies a successful or positive action.
*
- * @property formatters
- * @type Object
- * @for defaults
- * @since 1.0.0
+ * @property 0
+ * @type String
+ * @for statusMapping
**/
- formatters: {},
+ 0: "success",
/**
- * Contains all labels.
+ * Specifies a neutral informative change or action.
*
- * @property labels
- * @type Object
- * @for defaults
+ * @property 1
+ * @type String
+ * @for statusMapping
**/
- labels: {
- all: "All",
- infos: "Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries",
- loading: "Loading...",
- noResults: "No results found!",
- refresh: "Refresh",
- search: "Search"
- },
+ 1: "info",
/**
- * Specifies the mapping between status and contextual classes to color rows.
+ * Specifies a warning that might need attention.
*
- * @property statusMapping
- * @type Object
- * @for defaults
- * @since 1.2.0
+ * @property 2
+ * @type String
+ * @for statusMapping
**/
- statusMapping: {
- /**
- * Specifies a successful or positive action.
- *
- * @property 0
- * @type String
- * @for statusMapping
- **/
- 0: "success",
-
- /**
- * Specifies a neutral informative change or action.
- *
- * @property 1
- * @type String
- * @for statusMapping
- **/
- 1: "info",
-
- /**
- * Specifies a warning that might need attention.
- *
- * @property 2
- * @type String
- * @for statusMapping
- **/
- 2: "warning",
-
- /**
- * Specifies a dangerous or potentially negative action.
- *
- * @property 3
- * @type String
- * @for statusMapping
- **/
- 3: "danger"
- },
-
+ 2: "warning",
+
/**
- * Contains all templates.
+ * Specifies a dangerous or potentially negative action.
*
- * @property templates
- * @type Object
- * @for defaults
+ * @property 3
+ * @type String
+ * @for statusMapping
**/
- templates: {
- actionButton: "",
- actionDropDown: "",
- actionDropDownItem: "{{ctx.text}}",
- actionDropDownCheckboxItem: "",
- actions: "",
- body: "",
- cell: "{{ctx.content}} | ",
- footer: "",
- header: "",
- headerCell: "{{ctx.column.text}}{{ctx.icon}} | ",
- icon: "",
- infos: "{{lbl.infos}}
",
- loading: "| {{lbl.loading}} |
",
- noResults: "| {{lbl.noResults}} |
",
- pagination: "",
- paginationItem: "",
- rawHeaderCell: "{{ctx.content}} | ", // Used for the multi select box
- row: "{{ctx.cells}}
",
- search: "",
- select: ""
- }
- };
+ 3: "danger"
+ },
/**
- * Appends rows.
+ * Contains all templates.
*
- * @method append
- * @param rows {Array} An array of rows to append
- * @chainable
+ * @property templates
+ * @type Object
+ * @for defaults
**/
- Grid.prototype.append = function(rows)
+ templates: {
+ actionButton: "",
+ actionDropDown: "",
+ actionDropDownItem: "{{ctx.text}}",
+ actionDropDownCheckboxItem: "",
+ actions: "",
+ body: "",
+ cell: "{{ctx.content}} | ",
+ footer: "",
+ header: "",
+ headerCell: "{{ctx.column.text}}{{ctx.icon}} | ",
+ icon: "",
+ infos: "{{lbl.infos}}
",
+ loading: "| {{lbl.loading}} |
",
+ noResults: "| {{lbl.noResults}} |
",
+ pagination: "",
+ paginationItem: "",
+ rawHeaderCell: "{{ctx.content}} | ", // Used for the multi select box
+ row: "{{ctx.cells}}
",
+ search: "",
+ select: ""
+ }
+};
+
+/**
+ * Appends rows.
+ *
+ * @method append
+ * @param rows {Array} An array of rows to append
+ * @chainable
+ **/
+Grid.prototype.append = function(rows)
+{
+ if (this.options.ajax)
{
- if (this.options.ajax)
+ // todo: implement ajax PUT
+ }
+ else
+ {
+ var appendedRows = [];
+ for (var i = 0; i < rows.length; i++)
{
- // todo: implement ajax PUT
- }
- else
- {
- var appendedRows = [];
- for (var i = 0; i < rows.length; i++)
+ if (appendRow.call(this, rows[i]))
{
- if (appendRow.call(this, rows[i]))
- {
- appendedRows.push(rows[i]);
- }
+ appendedRows.push(rows[i]);
}
- sortRows.call(this);
- highlightAppendedRows.call(this, appendedRows);
- loadData.call(this);
- this.element.trigger("appended" + namespace, [appendedRows]);
- }
-
- return this;
- };
-
- /**
- * Removes all rows.
- *
- * @method clear
- * @chainable
- **/
- Grid.prototype.clear = function()
- {
- if (this.options.ajax)
- {
- // todo: implement ajax POST
- }
- else
- {
- var removedRows = $.extend([], this.rows);
- this.rows = [];
- this.current = 1;
- this.total = 0;
- loadData.call(this);
- this.element.trigger("cleared" + namespace, [removedRows]);
}
+ sortRows.call(this);
+ highlightAppendedRows.call(this, appendedRows);
+ loadData.call(this);
+ this.element.trigger("appended" + namespace, [appendedRows]);
+ }
- return this;
- };
+ return this;
+};
- /**
- * Removes the control functionality completely and transforms the current state to the initial HTML structure.
- *
- * @method destroy
- * @chainable
- **/
- Grid.prototype.destroy = function()
+/**
+ * Removes all rows.
+ *
+ * @method clear
+ * @chainable
+ **/
+Grid.prototype.clear = function()
+{
+ if (this.options.ajax)
{
- // todo: this method has to be optimized (the complete initial state must be restored)
- $(window).off(namespace);
- if (this.options.navigation & 1)
- {
- this.header.remove();
- }
- if (this.options.navigation & 2)
- {
- this.footer.remove();
- }
- this.element.before(this.origin).remove();
-
- return this;
- };
-
- /**
- * Resets the state and reloads rows.
- *
- * @method reload
- * @chainable
- **/
- Grid.prototype.reload = function()
+ // todo: implement ajax POST
+ }
+ else
{
- this.current = 1; // reset
+ var removedRows = $.extend([], this.rows);
+ this.rows = [];
+ this.current = 1;
+ this.total = 0;
loadData.call(this);
+ this.element.trigger("cleared" + namespace, [removedRows]);
+ }
- return this;
- };
+ return this;
+};
- /**
- * Removes rows by ids. Removes selected rows if no ids are provided.
- *
- * @method remove
- * @param [rowsIds] {Array} An array of rows ids to remove
- * @chainable
- **/
- Grid.prototype.remove = function(rowIds)
+/**
+ * Removes the control functionality completely and transforms the current state to the initial HTML structure.
+ *
+ * @method destroy
+ * @chainable
+ **/
+Grid.prototype.destroy = function()
+{
+ // todo: this method has to be optimized (the complete initial state must be restored)
+ $(window).off(namespace);
+ if (this.options.navigation & 1)
+ {
+ this.header.remove();
+ }
+ if (this.options.navigation & 2)
+ {
+ this.footer.remove();
+ }
+ this.element.before(this.origin).remove();
+
+ return this;
+};
+
+/**
+ * Resets the state and reloads rows.
+ *
+ * @method reload
+ * @chainable
+ **/
+Grid.prototype.reload = function()
+{
+ this.current = 1; // reset
+ loadData.call(this);
+
+ return this;
+};
+
+/**
+ * Removes rows by ids. Removes selected rows if no ids are provided.
+ *
+ * @method remove
+ * @param [rowsIds] {Array} An array of rows ids to remove
+ * @chainable
+ **/
+Grid.prototype.remove = function(rowIds)
+{
+ if (this.identifier != null)
{
- if (this.identifier != null)
+ var that = this;
+
+ if (this.options.ajax)
+ {
+ // todo: implement ajax DELETE
+ }
+ else
{
- var that = this;
+ rowIds = rowIds || this.selectedRows;
+ var id,
+ removedRows = [];
- if (this.options.ajax)
+ for (var i = 0; i < rowIds.length; i++)
{
- // todo: implement ajax DELETE
- }
- else
- {
- rowIds = rowIds || this.selectedRows;
- var id,
- removedRows = [];
+ id = rowIds[i];
- for (var i = 0; i < rowIds.length; i++)
+ for (var j = 0; j < this.rows.length; j++)
{
- id = rowIds[i];
-
- for (var j = 0; j < this.rows.length; j++)
+ if (this.rows[j][this.identifier] === id)
{
- if (this.rows[j][this.identifier] === id)
- {
- removedRows.push(this.rows[j]);
- this.rows.splice(j, 1);
- break;
- }
+ removedRows.push(this.rows[j]);
+ this.rows.splice(j, 1);
+ break;
}
}
-
- this.current = 1; // reset
- loadData.call(this);
- this.element.trigger("removed" + namespace, [removedRows]);
}
+
+ this.current = 1; // reset
+ loadData.call(this);
+ this.element.trigger("removed" + namespace, [removedRows]);
}
+ }
- return this;
- };
+ return this;
+};
+
+/**
+ * Searches in all rows for a specific phrase (but only in visible cells).
+ * The search filter will be reseted, if no argument is provided.
+ *
+ * @method search
+ * @param [phrase] {String} The phrase to search for
+ * @chainable
+ **/
+Grid.prototype.search = function(phrase)
+{
+ phrase = phrase || "";
- /**
- * Searches in all rows for a specific phrase (but only in visible cells).
- * The search filter will be reseted, if no argument is provided.
- *
- * @method search
- * @param [phrase] {String} The phrase to search for
- * @chainable
- **/
- Grid.prototype.search = function(phrase)
+ if (this.searchPhrase !== phrase)
{
- phrase = phrase || "";
-
- if (this.searchPhrase !== phrase)
- {
- var selector = getCssSelector(this.options.css.searchField),
- searchFields = findFooterAndHeaderItems.call(this, selector);
- searchFields.val(phrase);
- }
+ var selector = getCssSelector(this.options.css.searchField),
+ searchFields = findFooterAndHeaderItems.call(this, selector);
+ searchFields.val(phrase);
+ }
- executeSearch.call(this, phrase);
+ executeSearch.call(this, phrase);
- return this;
- };
+ return this;
+};
- /**
- * Selects rows by ids. Selects all visible rows if no ids are provided.
- * In server-side scenarios only visible rows are selectable.
- *
- * @method select
- * @param [rowsIds] {Array} An array of rows ids to select
- * @chainable
- **/
- Grid.prototype.select = function(rowIds)
+/**
+ * Selects rows by ids. Selects all visible rows if no ids are provided.
+ * In server-side scenarios only visible rows are selectable.
+ *
+ * @method select
+ * @param [rowsIds] {Array} An array of rows ids to select
+ * @chainable
+ **/
+Grid.prototype.select = function(rowIds)
+{
+ if (this.selection)
{
- if (this.selection)
- {
- rowIds = rowIds || this.currentRows.propValues(this.identifier);
+ rowIds = rowIds || this.currentRows.propValues(this.identifier);
- var id, i,
- selectedRows = [];
+ var id, i,
+ selectedRows = [];
- while (rowIds.length > 0 && !(!this.options.multiSelect && selectedRows.length === 1))
+ while (rowIds.length > 0 && !(!this.options.multiSelect && selectedRows.length === 1))
+ {
+ id = rowIds.pop();
+ if ($.inArray(id, this.selectedRows) === -1)
{
- id = rowIds.pop();
- if ($.inArray(id, this.selectedRows) === -1)
+ for (i = 0; i < this.currentRows.length; i++)
{
- for (i = 0; i < this.currentRows.length; i++)
+ if (this.currentRows[i][this.identifier] === id)
{
- if (this.currentRows[i][this.identifier] === id)
- {
- selectedRows.push(this.currentRows[i]);
- this.selectedRows.push(id);
- break;
- }
+ selectedRows.push(this.currentRows[i]);
+ this.selectedRows.push(id);
+ break;
}
}
}
+ }
- if (selectedRows.length > 0)
- {
- var selectBoxSelector = getCssSelector(this.options.css.selectBox),
- selectMultiSelectBox = this.selectedRows.length >= this.currentRows.length;
-
- i = 0;
- while (!this.options.keepSelection && selectMultiSelectBox && i < this.currentRows.length)
- {
- selectMultiSelectBox = ($.inArray(this.currentRows[i++][this.identifier], this.selectedRows) !== -1);
- }
- this.element.find("thead " + selectBoxSelector).prop("checked", selectMultiSelectBox);
+ if (selectedRows.length > 0)
+ {
+ var selectBoxSelector = getCssSelector(this.options.css.selectBox),
+ selectMultiSelectBox = this.selectedRows.length >= this.currentRows.length;
- if (!this.options.multiSelect)
- {
- this.element.find("tbody > tr " + selectBoxSelector + ":checked")
- .trigger("click" + namespace);
- }
+ i = 0;
+ while (!this.options.keepSelection && selectMultiSelectBox && i < this.currentRows.length)
+ {
+ selectMultiSelectBox = ($.inArray(this.currentRows[i++][this.identifier], this.selectedRows) !== -1);
+ }
+ this.element.find("thead " + selectBoxSelector).prop("checked", selectMultiSelectBox);
- for (i = 0; i < this.selectedRows.length; i++)
- {
- this.element.find("tbody > tr[data-row-id=\"" + this.selectedRows[i] + "\"]")
- .addClass(this.options.css.selected)._bgAria("selected", "true")
- .find(selectBoxSelector).prop("checked", true);
- }
+ if (!this.options.multiSelect)
+ {
+ this.element.find("tbody > tr " + selectBoxSelector + ":checked")
+ .trigger("click" + namespace);
+ }
- this.element.trigger("selected" + namespace, [selectedRows]);
+ for (i = 0; i < this.selectedRows.length; i++)
+ {
+ this.element.find("tbody > tr[data-row-id=\"" + this.selectedRows[i] + "\"]")
+ .addClass(this.options.css.selected)._bgAria("selected", "true")
+ .find(selectBoxSelector).prop("checked", true);
}
- }
- return this;
- };
+ this.element.trigger("selected" + namespace, [selectedRows]);
+ }
+ }
- /**
- * Deselects rows by ids. Deselects all visible rows if no ids are provided.
- * In server-side scenarios only visible rows are deselectable.
- *
- * @method deselect
- * @param [rowsIds] {Array} An array of rows ids to deselect
- * @chainable
- **/
- Grid.prototype.deselect = function(rowIds)
+ return this;
+};
+
+/**
+ * Deselects rows by ids. Deselects all visible rows if no ids are provided.
+ * In server-side scenarios only visible rows are deselectable.
+ *
+ * @method deselect
+ * @param [rowsIds] {Array} An array of rows ids to deselect
+ * @chainable
+ **/
+Grid.prototype.deselect = function(rowIds)
+{
+ if (this.selection)
{
- if (this.selection)
- {
- rowIds = rowIds || this.currentRows.propValues(this.identifier);
+ rowIds = rowIds || this.currentRows.propValues(this.identifier);
- var id, i, pos,
- deselectedRows = [];
+ var id, i, pos,
+ deselectedRows = [];
- while (rowIds.length > 0)
+ while (rowIds.length > 0)
+ {
+ id = rowIds.pop();
+ pos = $.inArray(id, this.selectedRows);
+ if (pos !== -1)
{
- id = rowIds.pop();
- pos = $.inArray(id, this.selectedRows);
- if (pos !== -1)
+ for (i = 0; i < this.currentRows.length; i++)
{
- for (i = 0; i < this.currentRows.length; i++)
+ if (this.currentRows[i][this.identifier] === id)
{
- if (this.currentRows[i][this.identifier] === id)
- {
- deselectedRows.push(this.currentRows[i]);
- this.selectedRows.splice(pos, 1);
- break;
- }
+ deselectedRows.push(this.currentRows[i]);
+ this.selectedRows.splice(pos, 1);
+ break;
}
}
}
-
- if (deselectedRows.length > 0)
- {
- var selectBoxSelector = getCssSelector(this.options.css.selectBox);
-
- this.element.find("thead " + selectBoxSelector).prop("checked", false);
- for (i = 0; i < deselectedRows.length; i++)
- {
- this.element.find("tbody > tr[data-row-id=\"" + deselectedRows[i][this.identifier] + "\"]")
- .removeClass(this.options.css.selected)._bgAria("selected", "false")
- .find(selectBoxSelector).prop("checked", false);
- }
-
- this.element.trigger("deselected" + namespace, [deselectedRows]);
- }
}
- return this;
- };
+ if (deselectedRows.length > 0)
+ {
+ var selectBoxSelector = getCssSelector(this.options.css.selectBox);
- /**
- * Sorts the rows by a given sort descriptor dictionary.
- * The sort filter will be reseted, if no argument is provided.
- *
- * @method sort
- * @param [dictionary] {Object} A sort descriptor dictionary that contains the sort information
- * @chainable
- **/
- Grid.prototype.sort = function(dictionary)
- {
- var values = (dictionary) ? $.extend({}, dictionary) : {};
+ this.element.find("thead " + selectBoxSelector).prop("checked", false);
+ for (i = 0; i < deselectedRows.length; i++)
+ {
+ this.element.find("tbody > tr[data-row-id=\"" + deselectedRows[i][this.identifier] + "\"]")
+ .removeClass(this.options.css.selected)._bgAria("selected", "false")
+ .find(selectBoxSelector).prop("checked", false);
+ }
- if (values === this.sortDictionary)
- {
- return this;
+ this.element.trigger("deselected" + namespace, [deselectedRows]);
}
+ }
- this.sortDictionary = values;
- renderTableHeader.call(this);
- sortRows.call(this);
- loadData.call(this);
+ return this;
+};
+
+/**
+ * Sorts the rows by a given sort descriptor dictionary.
+ * The sort filter will be reseted, if no argument is provided.
+ *
+ * @method sort
+ * @param [dictionary] {Object} A sort descriptor dictionary that contains the sort information
+ * @chainable
+ **/
+Grid.prototype.sort = function(dictionary)
+{
+ var values = (dictionary) ? $.extend({}, dictionary) : {};
+ if (values === this.sortDictionary)
+ {
return this;
- };
+ }
- /**
- * Gets a list of the column settings.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getColumnSettings
- * @return {Array} Returns a list of the column settings.
- * @since 1.2.0
- **/
- Grid.prototype.getColumnSettings = function()
- {
- return $.merge([], this.columns);
+ this.sortDictionary = values;
+ renderTableHeader.call(this);
+ sortRows.call(this);
+ loadData.call(this);
+
+ return this;
+};
+
+/**
+ * Gets a list of the column settings.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getColumnSettings
+ * @return {Array} Returns a list of the column settings.
+ * @since 1.2.0
+ **/
+Grid.prototype.getColumnSettings = function()
+{
+ return $.merge([], this.columns);
+};
+
+/**
+ * Gets the current page index.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getCurrentPage
+ * @return {Number} Returns the current page index.
+ * @since 1.2.0
+ **/
+Grid.prototype.getCurrentPage = function()
+{
+ return this.current;
+};
+
+/**
+ * Gets the current rows.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getCurrentPage
+ * @return {Array} Returns the current rows.
+ * @since 1.2.0
+ **/
+Grid.prototype.getCurrentRows = function()
+{
+ return $.merge([], this.currentRows);
+};
+
+/**
+ * Gets a number represents the row count per page.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getRowCount
+ * @return {Number} Returns the row count per page.
+ * @since 1.2.0
+ **/
+Grid.prototype.getRowCount = function()
+{
+ return this.rowCount;
+};
+
+/**
+ * Gets the actual search phrase.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getSearchPhrase
+ * @return {String} Returns the actual search phrase.
+ * @since 1.2.0
+ **/
+Grid.prototype.getSearchPhrase = function()
+{
+ return this.searchPhrase;
+};
+
+/**
+ * Gets the complete list of currently selected rows.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getSelectedRows
+ * @return {Array} Returns all selected rows.
+ * @since 1.2.0
+ **/
+Grid.prototype.getSelectedRows = function()
+{
+ return $.merge([], this.selectedRows);
+};
+
+/**
+ * Gets the sort dictionary which represents the state of column sorting.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getSortDictionary
+ * @return {Object} Returns the sort dictionary.
+ * @since 1.2.0
+ **/
+Grid.prototype.getSortDictionary = function()
+{
+ return $.extend({}, this.sortDictionary);
+};
+
+/**
+ * Gets a number represents the total page count.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getTotalPageCount
+ * @return {Number} Returns the total page count.
+ * @since 1.2.0
+ **/
+Grid.prototype.getTotalPageCount = function()
+{
+ return this.totalPages;
+};
+
+/**
+ * Gets a number represents the total row count.
+ * This method returns only for the first grid instance a value.
+ * Therefore be sure that only one grid instance is catched by your selector.
+ *
+ * @method getTotalRowCount
+ * @return {Number} Returns the total row count.
+ * @since 1.2.0
+ **/
+Grid.prototype.getTotalRowCount = function()
+{
+ return this.total;
};
- /**
- * Gets the current page index.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getCurrentPage
- * @return {Number} Returns the current page index.
- * @since 1.2.0
- **/
- Grid.prototype.getCurrentPage = function()
- {
- return this.current;
- };
+// GRID COMMON TYPE EXTENSIONS
+// ============
- /**
- * Gets the current rows.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getCurrentPage
- * @return {Array} Returns the current rows.
- * @since 1.2.0
- **/
- Grid.prototype.getCurrentRows = function()
+$.fn.extend({
+ _bgAria: function (name, value)
{
- return $.merge([], this.currentRows);
- };
+ return (value) ? this.attr("aria-" + name, value) : this.attr("aria-" + name);
+ },
- /**
- * Gets a number represents the row count per page.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getRowCount
- * @return {Number} Returns the row count per page.
- * @since 1.2.0
- **/
- Grid.prototype.getRowCount = function()
+ _bgBusyAria: function(busy)
{
- return this.rowCount;
- };
+ return (busy == null || busy) ?
+ this._bgAria("busy", "true") :
+ this._bgAria("busy", "false");
+ },
- /**
- * Gets the actual search phrase.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getSearchPhrase
- * @return {String} Returns the actual search phrase.
- * @since 1.2.0
- **/
- Grid.prototype.getSearchPhrase = function()
+ _bgRemoveAria: function (name)
{
- return this.searchPhrase;
- };
+ return this.removeAttr("aria-" + name);
+ },
- /**
- * Gets the complete list of currently selected rows.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getSelectedRows
- * @return {Array} Returns all selected rows.
- * @since 1.2.0
- **/
- Grid.prototype.getSelectedRows = function()
+ _bgEnableAria: function (enable)
{
- return $.merge([], this.selectedRows);
- };
+ return (enable == null || enable) ?
+ this.removeClass("disabled")._bgAria("disabled", "false") :
+ this.addClass("disabled")._bgAria("disabled", "true");
+ },
- /**
- * Gets the sort dictionary which represents the state of column sorting.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getSortDictionary
- * @return {Object} Returns the sort dictionary.
- * @since 1.2.0
- **/
- Grid.prototype.getSortDictionary = function()
+ _bgEnableField: function (enable)
{
- return $.extend({}, this.sortDictionary);
- };
+ return (enable == null || enable) ?
+ this.removeAttr("disabled") :
+ this.attr("disabled", "disable");
+ },
- /**
- * Gets a number represents the total page count.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getTotalPageCount
- * @return {Number} Returns the total page count.
- * @since 1.2.0
- **/
- Grid.prototype.getTotalPageCount = function()
+ _bgShowAria: function (show)
{
- return this.totalPages;
- };
+ return (show == null || show) ?
+ this.show()._bgAria("hidden", "false") :
+ this.hide()._bgAria("hidden", "true");
+ },
- /**
- * Gets a number represents the total row count.
- * This method returns only for the first grid instance a value.
- * Therefore be sure that only one grid instance is catched by your selector.
- *
- * @method getTotalRowCount
- * @return {Number} Returns the total row count.
- * @since 1.2.0
- **/
- Grid.prototype.getTotalRowCount = function()
+ _bgSelectAria: function (select)
{
- return this.total;
- };
-
- // GRID COMMON TYPE EXTENSIONS
- // ============
-
- $.fn.extend({
- _bgAria: function (name, value)
- {
- return (value) ? this.attr("aria-" + name, value) : this.attr("aria-" + name);
- },
-
- _bgBusyAria: function(busy)
- {
- return (busy == null || busy) ?
- this._bgAria("busy", "true") :
- this._bgAria("busy", "false");
- },
-
- _bgRemoveAria: function (name)
- {
- return this.removeAttr("aria-" + name);
- },
-
- _bgEnableAria: function (enable)
- {
- return (enable == null || enable) ?
- this.removeClass("disabled")._bgAria("disabled", "false") :
- this.addClass("disabled")._bgAria("disabled", "true");
- },
+ return (select == null || select) ?
+ this.addClass("active")._bgAria("selected", "true") :
+ this.removeClass("active")._bgAria("selected", "false");
+ },
- _bgEnableField: function (enable)
- {
- return (enable == null || enable) ?
- this.removeAttr("disabled") :
- this.attr("disabled", "disable");
- },
-
- _bgShowAria: function (show)
- {
- return (show == null || show) ?
- this.show()._bgAria("hidden", "false") :
- this.hide()._bgAria("hidden", "true");
- },
-
- _bgSelectAria: function (select)
- {
- return (select == null || select) ?
- this.addClass("active")._bgAria("selected", "true") :
- this.removeClass("active")._bgAria("selected", "false");
- },
+ _bgId: function (id)
+ {
+ return (id) ? this.attr("id", id) : this.attr("id");
+ }
+});
- _bgId: function (id)
+if (!String.prototype.resolve)
+{
+ var formatter = {
+ "checked": function(value)
{
- return (id) ? this.attr("id", id) : this.attr("id");
+ if (typeof value === "boolean")
+ {
+ return (value) ? "checked=\"checked\"" : "";
+ }
+ return value;
}
- });
+ };
- if (!String.prototype.resolve)
+ String.prototype.resolve = function (substitutes, prefixes)
{
- var formatter = {
- "checked": function(value)
+ var result = this;
+ $.each(substitutes, function (key, value)
+ {
+ if (value != null && typeof value !== "function")
{
- if (typeof value === "boolean")
+ if (typeof value === "object")
{
- return (value) ? "checked=\"checked\"" : "";
+ var keys = (prefixes) ? $.extend([], prefixes) : [];
+ keys.push(key);
+ result = result.resolve(value, keys) + "";
}
- return value;
- }
- };
-
- String.prototype.resolve = function (substitutes, prefixes)
- {
- var result = this;
- $.each(substitutes, function (key, value)
- {
- if (value != null && typeof value !== "function")
+ else
{
- if (typeof value === "object")
+ if (formatter && formatter[key] && typeof formatter[key] === "function")
{
- var keys = (prefixes) ? $.extend([], prefixes) : [];
- keys.push(key);
- result = result.resolve(value, keys) + "";
- }
- else
- {
- if (formatter && formatter[key] && typeof formatter[key] === "function")
- {
- value = formatter[key](value);
- }
- key = (prefixes) ? prefixes.join(".") + "." + key : key;
- var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm");
- result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value);
+ value = formatter[key](value);
}
+ key = (prefixes) ? prefixes.join(".") + "." + key : key;
+ var pattern = new RegExp("\\{\\{" + key + "\\}\\}", "gm");
+ result = result.replace(pattern, (value.replace) ? value.replace(/\$/gi, "$") : value);
}
- });
- return result;
- };
- }
+ }
+ });
+ return result;
+ };
+}
- if (!Array.prototype.first)
+if (!Array.prototype.first)
+{
+ Array.prototype.first = function (condition)
{
- Array.prototype.first = function (condition)
+ for (var i = 0; i < this.length; i++)
{
- for (var i = 0; i < this.length; i++)
+ var item = this[i];
+ if (condition(item))
{
- var item = this[i];
- if (condition(item))
- {
- return item;
- }
+ return item;
}
- return null;
- };
- }
+ }
+ return null;
+ };
+}
- if (!Array.prototype.contains)
+if (!Array.prototype.contains)
+{
+ Array.prototype.contains = function (condition)
{
- Array.prototype.contains = function (condition)
+ for (var i = 0; i < this.length; i++)
{
- for (var i = 0; i < this.length; i++)
+ var item = this[i];
+ if (condition(item))
{
- var item = this[i];
- if (condition(item))
- {
- return true;
- }
+ return true;
}
- return false;
- };
- }
+ }
+ return false;
+ };
+}
- if (!Array.prototype.page)
+if (!Array.prototype.page)
+{
+ Array.prototype.page = function (page, size)
{
- Array.prototype.page = function (page, size)
- {
- var skip = (page - 1) * size,
- end = skip + size;
- return (this.length > skip) ?
- (this.length > end) ? this.slice(skip, end) :
- this.slice(skip) : [];
- };
- }
+ var skip = (page - 1) * size,
+ end = skip + size;
+ return (this.length > skip) ?
+ (this.length > end) ? this.slice(skip, end) :
+ this.slice(skip) : [];
+ };
+}
- if (!Array.prototype.where)
+if (!Array.prototype.where)
+{
+ Array.prototype.where = function (condition)
{
- Array.prototype.where = function (condition)
+ var result = [];
+ for (var i = 0; i < this.length; i++)
{
- var result = [];
- for (var i = 0; i < this.length; i++)
+ var item = this[i];
+ if (condition(item))
{
- var item = this[i];
- if (condition(item))
- {
- result.push(item);
- }
+ result.push(item);
}
- return result;
- };
- }
+ }
+ return result;
+ };
+}
- if (!Array.prototype.propValues)
+if (!Array.prototype.propValues)
+{
+ Array.prototype.propValues = function (propName)
{
- Array.prototype.propValues = function (propName)
+ var result = [];
+ for (var i = 0; i < this.length; i++)
{
- var result = [];
- for (var i = 0; i < this.length; i++)
- {
- result.push(this[i][propName]);
- }
- return result;
- };
+ result.push(this[i][propName]);
+ }
+ return result;
+ };
}
- // GRID PLUGIN DEFINITION
- // =====================
+// GRID PLUGIN DEFINITION
+// =====================
- var old = $.fn.bootgrid;
+var old = $.fn.bootgrid;
- $.fn.bootgrid = function (option)
- {
- var args = Array.prototype.slice.call(arguments, 1),
- returnValue = null,
- elements = this.each(function (index)
- {
- var $this = $(this),
- instance = $this.data(namespace),
- options = typeof option === "object" && option;
+$.fn.bootgrid = function (option)
+{
+ var args = Array.prototype.slice.call(arguments, 1),
+ returnValue = null,
+ elements = this.each(function (index)
+ {
+ var $this = $(this),
+ instance = $this.data(namespace),
+ options = typeof option === "object" && option;
- if (!instance && option === "destroy")
- {
- return;
- }
- if (!instance)
+ if (!instance && option === "destroy")
+ {
+ return;
+ }
+ if (!instance)
+ {
+ $this.data(namespace, (instance = new Grid(this, options)));
+ init.call(instance);
+ }
+ if (typeof option === "string")
+ {
+ if (option.indexOf("get") === 0 && index === 0)
{
- $this.data(namespace, (instance = new Grid(this, options)));
- init.call(instance);
+ returnValue = instance[option].apply(instance, args);
}
- if (typeof option === "string")
+ else if (option.indexOf("get") !== 0)
{
- if (option.indexOf("get") === 0 && index === 0)
- {
- returnValue = instance[option].apply(instance, args);
- }
- else if (option.indexOf("get") !== 0)
- {
- return instance[option].apply(instance, args);
- }
+ return instance[option].apply(instance, args);
}
- });
- return (typeof option === "string" && option.indexOf("get") === 0) ? returnValue : elements;
- };
+ }
+ });
+ return (typeof option === "string" && option.indexOf("get") === 0) ? returnValue : elements;
+};
- $.fn.bootgrid.Constructor = Grid;
+$.fn.bootgrid.Constructor = Grid;
- // GRID NO CONFLICT
- // ===============
+// GRID NO CONFLICT
+// ===============
- $.fn.bootgrid.noConflict = function ()
- {
- $.fn.bootgrid = old;
- return this;
- };
+$.fn.bootgrid.noConflict = function ()
+{
+ $.fn.bootgrid = old;
+ return this;
+};
- // GRID DATA-API
- // ============
+// GRID DATA-API
+// ============
$("[data-toggle=\"bootgrid\"]").bootgrid();
})(jQuery, window);
\ No newline at end of file
diff --git a/dist/jquery.bootgrid.min.css b/dist/jquery.bootgrid.min.css
index 358c1ac..6ff3670 100644
--- a/dist/jquery.bootgrid.min.css
+++ b/dist/jquery.bootgrid.min.css
@@ -1,5 +1,5 @@
/*!
- * jQuery Bootgrid v1.3.1 - 09/11/2015
- * Copyright (c) 2014-2015 Rafael Staib (http://www.jquery-bootgrid.com)
- * Licensed under MIT http://www.opensource.org/licenses/MIT
- */.bootgrid-footer,.bootgrid-header{margin:15px 0}.bootgrid-footer a,.bootgrid-header a{outline:0}.bootgrid-footer .search,.bootgrid-header .search{display:inline-block;margin:0 20px 0 0;vertical-align:middle;width:180px}.bootgrid-footer .search .glyphicon,.bootgrid-header .search .glyphicon{top:0}.bootgrid-footer .search .fa,.bootgrid-header .search .fa{display:table-cell}.bootgrid-footer .search .search-field::-ms-clear,.bootgrid-footer .search.search-field::-ms-clear,.bootgrid-header .search .search-field::-ms-clear,.bootgrid-header .search.search-field::-ms-clear{display:none}.bootgrid-footer .pagination,.bootgrid-header .pagination{margin:0!important}.bootgrid-footer .infoBar,.bootgrid-header .actionBar{text-align:right}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu{text-align:left}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item{cursor:pointer;display:block;margin:0;padding:3px 20px;white-space:nowrap}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item:focus,.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item:hover,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item:focus,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox,.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox{margin:0 2px 4px 0;vertical-align:middle}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item.disabled,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item.disabled{cursor:not-allowed}.bootgrid-table{table-layout:fixed}.bootgrid-table a{outline:0}.bootgrid-table th>.column-header-anchor{color:#333;cursor:not-allowed;display:block;position:relative;text-decoration:none}.bootgrid-table th>.column-header-anchor.sortable{cursor:pointer}.bootgrid-table th>.column-header-anchor>.text{display:block;margin:0 16px 0 0;overflow:hidden;-ms-text-overflow:ellipsis;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.bootgrid-table th>.column-header-anchor>.icon{display:block;position:absolute;right:0;top:2px}.bootgrid-table th:active,.bootgrid-table th:hover{background:#fafafa}.bootgrid-table td{overflow:hidden;-ms-text-overflow:ellipsis;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.bootgrid-table td.loading,.bootgrid-table td.no-results{background:#fff;text-align:center}.bootgrid-table td.select-cell,.bootgrid-table th.select-cell{text-align:center;width:30px}.bootgrid-table td.select-cell .select-box,.bootgrid-table th.select-cell .select-box{margin:0;outline:0}.table-responsive .bootgrid-table{table-layout:inherit!important}.table-responsive .bootgrid-table td,.table-responsive .bootgrid-table th>.column-header-anchor>.text{overflow:inherit!important;-ms-text-overflow:inherit!important;-o-text-overflow:inherit!important;text-overflow:inherit!important;white-space:inherit!important}
\ No newline at end of file
+ * jQuery Bootgrid v1.3.3 - 11/06/2018
+ * Copyright © 2014-2015 Rafael J. Staib; Copyright © 2018-2018 Deciso B.V. (http://www.jquery-bootgrid.com)
+ * Licensed under the MIT license. See LICENSE.txt for more details.
+ */.bootgrid-footer,.bootgrid-header{margin:15px 0}.bootgrid-footer a,.bootgrid-header a{outline:0}.bootgrid-footer .search,.bootgrid-header .search{display:inline-block;margin:0 20px 0 0;vertical-align:middle;width:180px}.bootgrid-footer .search .glyphicon,.bootgrid-header .search .glyphicon{top:0}.bootgrid-footer .search .fa,.bootgrid-header .search .fa{display:table-cell}.bootgrid-footer .search .search-field::-ms-clear,.bootgrid-footer .search.search-field::-ms-clear,.bootgrid-header .search .search-field::-ms-clear,.bootgrid-header .search.search-field::-ms-clear{display:none}.bootgrid-footer .pagination,.bootgrid-header .pagination{margin:0!important}.bootgrid-footer .infoBar,.bootgrid-header .actionBar{text-align:right}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu{text-align:left}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item{cursor:pointer;display:block;margin:0;padding:3px 20px;white-space:nowrap}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item:focus,.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item:hover,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item:focus,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox,.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item .dropdown-item-checkbox,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item.dropdown-item-checkbox{margin:0 2px 4px 0;vertical-align:middle}.bootgrid-footer .infoBar .btn-group>.btn-group .dropdown-menu .dropdown-item.disabled,.bootgrid-header .actionBar .btn-group>.btn-group .dropdown-menu .dropdown-item.disabled{cursor:not-allowed}.bootgrid-table{table-layout:fixed}.bootgrid-table a{outline:0}.bootgrid-table th>.column-header-anchor{color:#333;cursor:not-allowed;display:block;position:relative;text-decoration:none}.bootgrid-table th>.column-header-anchor.sortable{cursor:pointer}.bootgrid-table th>.column-header-anchor>.text{display:block;margin:0 16px 0 0;overflow:hidden;-ms-text-overflow:ellipsis;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.bootgrid-table th>.column-header-anchor>.icon{display:block;position:absolute;right:0;top:2px}.bootgrid-table th:active,.bootgrid-table th:hover{background:#fafafa}.bootgrid-table td{overflow:hidden;-ms-text-overflow:ellipsis;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.bootgrid-table td.loading,.bootgrid-table td.no-results{background:#fff;text-align:center}.bootgrid-table td.select-cell,.bootgrid-table th.select-cell{text-align:center;width:30px}.bootgrid-table td.select-cell .select-box,.bootgrid-table th.select-cell .select-box{margin:0;outline:0}.table-responsive .bootgrid-table{table-layout:inherit!important}.table-responsive .bootgrid-table th>.column-header-anchor>.text{overflow:inherit!important;-ms-text-overflow:inherit!important;-o-text-overflow:inherit!important;text-overflow:inherit!important;white-space:inherit!important}.table-responsive .bootgrid-table td{overflow:inherit!important;-ms-text-overflow:inherit!important;-o-text-overflow:inherit!important;text-overflow:inherit!important;white-space:inherit!important}
\ No newline at end of file
diff --git a/dist/jquery.bootgrid.min.js b/dist/jquery.bootgrid.min.js
index b84a714..956ae25 100644
--- a/dist/jquery.bootgrid.min.js
+++ b/dist/jquery.bootgrid.min.js
@@ -1,6 +1 @@
-/*!
- * jQuery Bootgrid v1.3.1 - 09/11/2015
- * Copyright (c) 2014-2015 Rafael Staib (http://www.jquery-bootgrid.com)
- * Licensed under MIT http://www.opensource.org/licenses/MIT
- */
-!function(a,b,c){"use strict";function d(a){function b(b){return c.identifier&&b[c.identifier]===a[c.identifier]}var c=this;return this.rows.contains(b)?!1:(this.rows.push(a),!0)}function e(b){var c=this.footer?this.footer.find(b):a(),d=this.header?this.header.find(b):a();return a.merge(c,d)}function f(b){return b?a.extend({},this.cachedParams,{ctx:b}):this.cachedParams}function g(){var b={current:this.current,rowCount:this.rowCount,sort:this.sortDictionary,searchPhrase:this.searchPhrase},c=this.options.post;return c=a.isFunction(c)?c():c,this.options.requestHandler(a.extend(!0,b,c))}function h(b){return"."+a.trim(b).replace(/\s+/gm,".")}function i(){var b=this.options.url;return a.isFunction(b)?b():b}function j(){this.element.trigger("initialize"+H),m.call(this),this.selection=this.options.selection&&null!=this.identifier,o.call(this),q.call(this),C.call(this),A.call(this),r.call(this),n.call(this),this.element.trigger("initialized"+H)}function k(a){this.options.highlightRows}function l(a){return a.visible}function m(){var b=this,c=this.element.find("thead > tr").first(),d=!1;c.children().each(function(){var c=a(this),e=c.data(),f={id:e.columnId,identifier:null==b.identifier&&e.identifier||!1,converter:b.options.converters[e.converter||e.type]||b.options.converters.string,text:c.text(),align:e.align||"left",headerAlign:e.headerAlign||"left",cssClass:e.cssClass||"",headerCssClass:e.headerCssClass||"",formatter:b.options.formatters[e.formatter]||null,order:d||"asc"!==e.order&&"desc"!==e.order?null:e.order,searchable:!(e.searchable===!1),sortable:!(e.sortable===!1),visible:!(e.visible===!1),visibleInSelection:!(e.visibleInSelection===!1),width:a.isNumeric(e.width)?e.width+"px":"string"==typeof e.width?e.width:null};b.columns.push(f),null!=f.order&&(b.sortDictionary[f.id]=f.order),f.identifier&&(b.identifier=f.id,b.converter=f.converter),b.options.multiSort||null===f.order||(d=!0)})}function n(){function c(a){for(var b,c=new RegExp(e.searchPhrase,e.options.caseSensitive?"g":"gi"),d=0;d-1)return!0;return!1}function d(a,b){e.currentRows=a,p.call(e,b),e.options.keepSelection||(e.selectedRows=[]),y.call(e,a),t.call(e),v.call(e),e.element._bgBusyAria(!1).trigger("loaded"+H)}var e=this;if(this.element._bgBusyAria(!0).trigger("load"+H),F.call(this),this.options.ajax){var f=g.call(this),h=i.call(this);if(null==h||"string"!=typeof h||0===h.length)throw new Error("Url setting must be a none empty string or a function that returns one.");this.xqr&&this.xqr.abort();var j={url:h,data:f,success:function(b){e.xqr=null,"string"==typeof b&&(b=a.parseJSON(b)),b=e.options.responseHandler(b),e.current=b.current,d(b.rows,b.total)},error:function(a,b,c){e.xqr=null,"abort"!==b&&(u.call(e),e.element._bgBusyAria(!1).trigger("loaded"+H))}};j=a.extend(this.options.ajaxSettings,j),this.xqr=a.ajax(j)}else{var k=this.searchPhrase.length>0?this.rows.where(c):this.rows,l=k.length;-1!==this.rowCount&&(k=k.page(this.current,this.rowCount)),b.setTimeout(function(){d(k,l)},10)}}function o(){if(!this.options.ajax){var b=this,c=this.element.find("tbody > tr");c.each(function(){var c=a(this),e=c.children("td"),f={};a.each(b.columns,function(a,b){f[b.id]=b.converter.from(e.eq(a).text())}),d.call(b,f)}),p.call(this,this.rows.length),G.call(this)}}function p(a){this.total=a,this.totalPages=-1===this.rowCount?1:Math.ceil(this.total/this.rowCount)}function q(){var b=this.options.templates,c=this.element.parent().hasClass(this.options.css.responsiveTable)?this.element.parent():this.element;this.element.addClass(this.options.css.table),0===this.element.children("tbody").length&&this.element.append(b.body),1&this.options.navigation&&(this.header=a(b.header.resolve(f.call(this,{id:this.element._bgId()+"-header"}))),c.before(this.header)),2&this.options.navigation&&(this.footer=a(b.footer.resolve(f.call(this,{id:this.element._bgId()+"-footer"}))),c.after(this.footer))}function r(){if(0!==this.options.navigation){var b=this.options.css,c=h(b.actions),d=e.call(this,c);if(d.length>0){var g=this,i=this.options.templates,j=a(i.actions.resolve(f.call(this)));if(this.options.ajax){var k=i.icon.resolve(f.call(this,{iconCss:b.iconRefresh})),l=a(i.actionButton.resolve(f.call(this,{content:k,text:this.options.labels.refresh}))).on("click"+H,function(a){a.stopPropagation(),g.current=1,n.call(g)});j.append(l)}x.call(this,j),s.call(this,j),E.call(this,d,j)}}}function s(b){if(this.options.columnSelection&&this.columns.length>1){var c=this,d=this.options.css,e=this.options.templates,g=e.icon.resolve(f.call(this,{iconCss:d.iconColumns})),i=a(e.actionDropDown.resolve(f.call(this,{content:g}))),j=h(d.dropDownItem),k=h(d.dropDownItemCheckbox),m=h(d.dropDownMenuItems);a.each(this.columns,function(b,g){if(g.visibleInSelection){var o=a(e.actionDropDownCheckboxItem.resolve(f.call(c,{name:g.id,label:g.text,checked:g.visible}))).on("click"+H,j,function(b){b.stopPropagation();var d=a(this),e=d.find(k);if(!e.prop("disabled")){g.visible=e.prop("checked");var f=c.columns.where(l).length>1;d.parents(m).find(j+":has("+k+":checked)")._bgEnableAria(f).find(k)._bgEnableField(f),c.element.find("tbody").empty(),C.call(c),n.call(c)}});i.find(h(d.dropDownMenuItems)).append(o)}}),b.append(i)}}function t(){if(0!==this.options.navigation){var b=h(this.options.css.infos),c=e.call(this,b);if(c.length>0){var d=this.current*this.rowCount,g=a(this.options.templates.infos.resolve(f.call(this,{end:0===this.total||-1===d||d>this.total?this.total:d,start:0===this.total?0:d-this.rowCount+1,total:this.total})));E.call(this,c,g)}}}function u(){var a=this.element.children("tbody").first(),b=this.options.templates,c=this.columns.where(l).length;this.selection&&(c+=1),a.html(b.noResults.resolve(f.call(this,{columns:c})))}function v(){if(0!==this.options.navigation){var b=h(this.options.css.pagination),c=e.call(this,b)._bgShowAria(-1!==this.rowCount);if(-1!==this.rowCount&&c.length>0){var d=this.options.templates,g=this.current,i=this.totalPages,j=a(d.pagination.resolve(f.call(this))),k=i-g,l=-1*(this.options.padding-g),m=k>=this.options.padding?Math.max(l,1):Math.max(l-this.options.padding+k,1),n=2*this.options.padding+1,o=i>=n?n:i;w.call(this,j,"first","«","first")._bgEnableAria(g>1),w.call(this,j,"prev","<","prev")._bgEnableAria(g>1);for(var p=0;o>p;p++){var q=p+m;w.call(this,j,q,q,"page-"+q)._bgEnableAria()._bgSelectAria(q===g)}0===o&&w.call(this,j,1,1,"page-1")._bgEnableAria(!1)._bgSelectAria(),w.call(this,j,"next",">","next")._bgEnableAria(i>g),w.call(this,j,"last","»","last")._bgEnableAria(i>g),E.call(this,c,j)}}}function w(b,c,d,e){var g=this,i=this.options.templates,j=this.options.css,k=f.call(this,{css:e,text:d,page:c}),l=a(i.paginationItem.resolve(k)).on("click"+H,h(j.paginationButton),function(b){b.stopPropagation(),b.preventDefault();var c=a(this),d=c.parent();if(!d.hasClass("active")&&!d.hasClass("disabled")){var e={first:1,prev:g.current-1,next:g.current+1,last:g.totalPages},f=c.data("page");g.current=e[f]||f,n.call(g)}c.trigger("blur")});return b.append(l),l}function x(b){function c(a){return-1===a?d.options.labels.all:a}var d=this,e=this.options.rowCount;if(a.isArray(e)){var g=this.options.css,i=this.options.templates,j=a(i.actionDropDown.resolve(f.call(this,{content:c(this.rowCount)}))),k=h(g.dropDownMenu),l=h(g.dropDownMenuText),m=h(g.dropDownMenuItems),o=h(g.dropDownItemButton);a.each(e,function(b,e){var g=a(i.actionDropDownItem.resolve(f.call(d,{text:c(e),action:e})))._bgSelectAria(e===d.rowCount).on("click"+H,o,function(b){b.preventDefault();var e=a(this),f=e.data("action");f!==d.rowCount&&(d.current=1,d.rowCount=f,e.parents(m).children().each(function(){var b=a(this),c=b.find(o).data("action");b._bgSelectAria(c===f)}),e.parents(k).find(l).text(c(f)),n.call(d))});j.find(m).append(g)}),b.append(j)}}function y(b){if(b.length>0){var c=this,d=this.options.css,e=this.options.templates,g=this.element.children("tbody").first(),i=!0,j="";a.each(b,function(b,g){var h="",k=' data-row-id="'+(null==c.identifier?b:g[c.identifier])+'"',l="";if(c.selection){var m=-1!==a.inArray(g[c.identifier],c.selectedRows),n=e.select.resolve(f.call(c,{type:"checkbox",value:g[c.identifier],checked:m}));h+=e.cell.resolve(f.call(c,{content:n,css:d.selectCell})),i=i&&m,m&&(l+=d.selected,k+=' aria-selected="true"')}var o=null!=g.status&&c.options.statusMapping[g.status];o&&(l+=o),a.each(c.columns,function(b,i){if(i.visible){var j=a.isFunction(i.formatter)?i.formatter.call(c,i,g):i.converter.to(g[i.id]),k=i.cssClass.length>0?" "+i.cssClass:"";h+=e.cell.resolve(f.call(c,{content:null==j||""===j?" ":j,css:("right"===i.align?d.right:"center"===i.align?d.center:d.left)+k,style:null==i.width?"":"width:"+i.width+";"}))}}),l.length>0&&(k+=' class="'+l+'"'),j+=e.row.resolve(f.call(c,{attr:k,cells:h}))}),c.element.find("thead "+h(c.options.css.selectBox)).prop("checked",i),g.html(j),z.call(this,g)}else u.call(this)}function z(b){var c=this,d=h(this.options.css.selectBox);this.selection&&b.off("click"+H,d).on("click"+H,d,function(b){b.stopPropagation();var d=a(this),e=c.converter.from(d.val());d.prop("checked")?c.select([e]):c.deselect([e])}),b.off("click"+H,"> tr").on("click"+H,"> tr",function(b){b.stopPropagation();var d=a(this),e=null==c.identifier?d.data("row-id"):c.converter.from(d.data("row-id")+""),f=null==c.identifier?c.currentRows[e]:c.currentRows.first(function(a){return a[c.identifier]===e});c.selection&&c.options.rowSelect&&(d.hasClass(c.options.css.selected)?c.deselect([e]):c.select([e])),c.element.trigger("click"+H,[c.columns,f])})}function A(){if(0!==this.options.navigation){var c=this.options.css,d=h(c.search),g=e.call(this,d);if(g.length>0){var i=this,j=this.options.templates,k=null,l="",m=h(c.searchField),n=a(j.search.resolve(f.call(this))),o=n.is(m)?n:n.find(m);o.on("keyup"+H,function(c){c.stopPropagation();var d=a(this).val();(l!==d||13===c.which&&""!==d)&&(l=d,(13===c.which||0===d.length||d.length>=i.options.searchSettings.characters)&&(b.clearTimeout(k),k=b.setTimeout(function(){B.call(i,d)},i.options.searchSettings.delay)))}),E.call(this,g,n)}}}function B(a){this.searchPhrase!==a&&(this.current=1,this.searchPhrase=a,n.call(this))}function C(){var b=this,c=this.element.find("thead > tr"),d=this.options.css,e=this.options.templates,g="",i=this.options.sorting;if(this.selection){var j=this.options.multiSelect?e.select.resolve(f.call(b,{type:"checkbox",value:"all"})):"";g+=e.rawHeaderCell.resolve(f.call(b,{content:j,css:d.selectCell}))}if(a.each(this.columns,function(a,c){if(c.visible){var h=b.sortDictionary[c.id],j=i&&h&&"asc"===h?d.iconUp:i&&h&&"desc"===h?d.iconDown:"",k=e.icon.resolve(f.call(b,{iconCss:j})),l=c.headerAlign,m=c.headerCssClass.length>0?" "+c.headerCssClass:"";g+=e.headerCell.resolve(f.call(b,{column:c,icon:k,sortable:i&&c.sortable&&d.sortable||"",css:("right"===l?d.right:"center"===l?d.center:d.left)+m,style:null==c.width?"":"width:"+c.width+";"}))}}),c.html(g),i){var k=h(d.sortable);c.off("click"+H,k).on("click"+H,k,function(c){c.preventDefault(),D.call(b,a(this)),G.call(b),n.call(b)})}if(this.selection&&this.options.multiSelect){var l=h(d.selectBox);c.off("click"+H,l).on("click"+H,l,function(c){c.stopPropagation(),a(this).prop("checked")?b.select():b.deselect()})}}function D(a){var b=this.options.css,c=h(b.icon),d=a.data("column-id")||a.parents("th").first().data("column-id"),e=this.sortDictionary[d],f=a.find(c);if(this.options.multiSort||(a.parents("tr").first().find(c).removeClass(b.iconDown+" "+b.iconUp),this.sortDictionary={}),e&&"asc"===e)this.sortDictionary[d]="desc",f.removeClass(b.iconUp).addClass(b.iconDown);else if(e&&"desc"===e)if(this.options.multiSort){var g={};for(var i in this.sortDictionary)i!==d&&(g[i]=this.sortDictionary[i]);this.sortDictionary=g,f.removeClass(b.iconDown)}else this.sortDictionary[d]="asc",f.removeClass(b.iconDown).addClass(b.iconUp);else this.sortDictionary[d]="asc",f.addClass(b.iconUp)}function E(b,c){b.each(function(b,d){a(d).before(c.clone(!0)).remove()})}function F(){var a=this;b.setTimeout(function(){if("true"===a.element._bgAria("busy")){var b=a.options.templates,c=a.element.children("thead").first(),d=a.element.children("tbody").first(),e=d.find("tr > td").first(),g=a.element.height()-c.height()-(e.height()+20),h=a.columns.where(l).length;a.selection&&(h+=1),d.html(b.loading.resolve(f.call(a,{columns:h}))),-1!==a.rowCount&&g>0&&d.find("tr > td").css("padding","20px 0 "+g+"px")}},250)}function G(){function a(c,d,e){function f(a){return"asc"===h.order?a:-1*a}e=e||0;var g=e+1,h=b[e];return c[h.id]>d[h.id]?f(1):c[h.id]g?a(c,d,g):0}var b=[];if(!this.options.ajax){for(var c in this.sortDictionary)(this.options.multiSort||0===b.length)&&b.push({id:c,order:this.sortDictionary[c]});b.length>0&&this.rows.sort(a)}}var H=".rs.jquery.bootgrid",I=function(b,c){this.element=a(b),this.origin=this.element.clone(),this.options=a.extend(!0,{},I.defaults,this.element.data(),c);var d=this.options.rowCount=this.element.data().rowCount||c.rowCount||this.options.rowCount;this.columns=[],this.current=1,this.currentRows=[],this.identifier=null,this.selection=!1,this.converter=null,this.rowCount=a.isArray(d)?d[0]:d,this.rows=[],this.searchPhrase="",this.selectedRows=[],this.sortDictionary={},this.total=0,this.totalPages=0,this.cachedParams={lbl:this.options.labels,css:this.options.css,ctx:{}},this.header=null,this.footer=null,this.xqr=null};if(I.defaults={navigation:3,padding:2,columnSelection:!0,rowCount:[10,25,50,-1],selection:!1,multiSelect:!1,rowSelect:!1,keepSelection:!1,highlightRows:!1,sorting:!0,multiSort:!1,searchSettings:{delay:250,characters:1},ajax:!1,ajaxSettings:{method:"POST"},post:{},url:"",caseSensitive:!0,requestHandler:function(a){return a},responseHandler:function(a){return a},converters:{numeric:{from:function(a){return+a},to:function(a){return a+""}},string:{from:function(a){return a},to:function(a){return a}}},css:{actions:"actions btn-group",center:"text-center",columnHeaderAnchor:"column-header-anchor",columnHeaderText:"text",dropDownItem:"dropdown-item",dropDownItemButton:"dropdown-item-button",dropDownItemCheckbox:"dropdown-item-checkbox",dropDownMenu:"dropdown btn-group",dropDownMenuItems:"dropdown-menu pull-right",dropDownMenuText:"dropdown-text",footer:"bootgrid-footer container-fluid",header:"bootgrid-header container-fluid",icon:"icon glyphicon",iconColumns:"glyphicon-th-list",iconDown:"glyphicon-chevron-down",iconRefresh:"glyphicon-refresh",iconSearch:"glyphicon-search",iconUp:"glyphicon-chevron-up",infos:"infos",left:"text-left",pagination:"pagination",paginationButton:"button",responsiveTable:"table-responsive",right:"text-right",search:"search form-group",searchField:"search-field form-control",selectBox:"select-box",selectCell:"select-cell",selected:"active",sortable:"sortable",table:"bootgrid-table table"},formatters:{},labels:{all:"All",infos:"Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries",loading:"Loading...",noResults:"No results found!",refresh:"Refresh",search:"Search"},statusMapping:{0:"success",1:"info",2:"warning",3:"danger"},templates:{actionButton:'',actionDropDown:'',actionDropDownItem:'{{ctx.text}}',actionDropDownCheckboxItem:'',actions:'',body:"",cell:'{{ctx.content}} | ',footer:'',header:'',headerCell:'{{ctx.column.text}}{{ctx.icon}} | ',icon:'',infos:'{{lbl.infos}}
',loading:'| {{lbl.loading}} |
',noResults:'| {{lbl.noResults}} |
',pagination:'',paginationItem:'',rawHeaderCell:'{{ctx.content}} | ',row:"{{ctx.cells}}
",search:'',select:''}},I.prototype.append=function(a){if(this.options.ajax);else{for(var b=[],c=0;c0&&(this.options.multiSelect||1!==e.length);)if(c=b.pop(),-1===a.inArray(c,this.selectedRows))for(d=0;d0){var f=h(this.options.css.selectBox),g=this.selectedRows.length>=this.currentRows.length;for(d=0;!this.options.keepSelection&&g&&d tr "+f+":checked").trigger("click"+H),d=0;d tr[data-row-id="'+this.selectedRows[d]+'"]').addClass(this.options.css.selected)._bgAria("selected","true").find(f).prop("checked",!0);this.element.trigger("selected"+H,[e])}}return this},I.prototype.deselect=function(b){if(this.selection){b=b||this.currentRows.propValues(this.identifier);for(var c,d,e,f=[];b.length>0;)if(c=b.pop(),e=a.inArray(c,this.selectedRows),-1!==e)for(d=0;d0){var g=h(this.options.css.selectBox);for(this.element.find("thead "+g).prop("checked",!1),d=0;d tr[data-row-id="'+f[d][this.identifier]+'"]').removeClass(this.options.css.selected)._bgAria("selected","false").find(g).prop("checked",!1);this.element.trigger("deselected"+H,[f])}}return this},I.prototype.sort=function(b){var c=b?a.extend({},b):{};return c===this.sortDictionary?this:(this.sortDictionary=c,C.call(this),G.call(this),n.call(this),this)},I.prototype.getColumnSettings=function(){return a.merge([],this.columns)},I.prototype.getCurrentPage=function(){return this.current},I.prototype.getCurrentRows=function(){return a.merge([],this.currentRows)},I.prototype.getRowCount=function(){return this.rowCount},I.prototype.getSearchPhrase=function(){return this.searchPhrase},I.prototype.getSelectedRows=function(){return a.merge([],this.selectedRows)},I.prototype.getSortDictionary=function(){return a.extend({},this.sortDictionary)},I.prototype.getTotalPageCount=function(){return this.totalPages},I.prototype.getTotalRowCount=function(){return this.total},a.fn.extend({_bgAria:function(a,b){return b?this.attr("aria-"+a,b):this.attr("aria-"+a)},_bgBusyAria:function(a){return null==a||a?this._bgAria("busy","true"):this._bgAria("busy","false")},_bgRemoveAria:function(a){return this.removeAttr("aria-"+a)},_bgEnableAria:function(a){return null==a||a?this.removeClass("disabled")._bgAria("disabled","false"):this.addClass("disabled")._bgAria("disabled","true")},_bgEnableField:function(a){return null==a||a?this.removeAttr("disabled"):this.attr("disabled","disable")},_bgShowAria:function(a){return null==a||a?this.show()._bgAria("hidden","false"):this.hide()._bgAria("hidden","true")},_bgSelectAria:function(a){return null==a||a?this.addClass("active")._bgAria("selected","true"):this.removeClass("active")._bgAria("selected","false")},_bgId:function(a){return a?this.attr("id",a):this.attr("id")}}),!String.prototype.resolve){var J={checked:function(a){return"boolean"==typeof a?a?'checked="checked"':"":a}};String.prototype.resolve=function(b,c){var d=this;return a.each(b,function(b,e){if(null!=e&&"function"!=typeof e)if("object"==typeof e){var f=c?a.extend([],c):[];f.push(b),d=d.resolve(e,f)+""}else{J&&J[b]&&"function"==typeof J[b]&&(e=J[b](e)),b=c?c.join(".")+"."+b:b;var g=new RegExp("\\{\\{"+b+"\\}\\}","gm");d=d.replace(g,e.replace?e.replace(/\$/gi,"$"):e)}}),d}}Array.prototype.first||(Array.prototype.first=function(a){for(var b=0;bc?this.length>d?this.slice(c,d):this.slice(c):[]}),Array.prototype.where||(Array.prototype.where=function(a){for(var b=[],c=0;c tr").first(),o=!1;t.children().each(function(){var t=p(this),e=t.data(),i={id:e.columnId,identifier:null==s.identifier&&e.identifier||!1,converter:s.options.converters[e.converter||e.type]||s.options.converters.string,text:t.text(),align:e.align||"left",headerAlign:e.headerAlign||"left",cssClass:e.cssClass||"",headerCssClass:e.headerCssClass||"",formatter:s.options.formatters[e.formatter]||null,order:o||"asc"!==e.order&&"desc"!==e.order?null:e.order,searchable:!(!1===e.searchable),sortable:!(!1===e.sortable),visible:!(!1===e.visible),visibleInSelection:!(!1===e.visibleInSelection),width:p.isNumeric(e.width)?e.width+"px":"string"==typeof e.width?e.width:null};s.columns.push(i),null!=i.order&&(s.sortDictionary[i.id]=i.order),i.identifier&&(s.identifier=i.id,s.converter=i.converter),s.options.multiSort||null===i.order||(o=!0)})}.call(this),this.selection=this.options.selection&&null!=this.identifier,function(){if(!this.options.ajax){var e=this,t=this.element.find("tbody > tr");t.each(function(){var t=p(this),i=t.children("td"),s={};p.each(e.columns,function(t,e){s[e.id]=e.converter.from(i.eq(t).text())}),o.call(e,s)}),a.call(this,this.rows.length),n.call(this)}}.call(this),function(){var t=this.options.templates,e=this.element.parent().hasClass(this.options.css.responsiveTable)?this.element.parent():this.element;this.element.addClass(this.options.css.table),0===this.element.children("tbody").length&&this.element.append(t.body);1&this.options.navigation&&(this.header=p(t.header.resolve(v.call(this,{id:this.element._bgId()+"-header"}))),e.before(this.header));2&this.options.navigation&&(this.footer=p(t.footer.resolve(v.call(this,{id:this.element._bgId()+"-footer"}))),e.after(this.footer))}.call(this),x.call(this),function(){if(0!==this.options.navigation){var t=this.options.css,e=m(t.search),i=g.call(this,e);if(0=s.options.searchSettings.characters)&&(h.clearTimeout(n),n=h.setTimeout(function(){y.call(s,e)},s.options.searchSettings.delay)))}),C.call(this,i,a)}}}.call(this),function(){if(0!==this.options.navigation){var t=this.options.css,e=m(t.actions),i=g.call(this,e);if(0 tr").on("click"+f,"> tr",function(t){t.stopPropagation();var e=p(this),i=null==o.identifier?e.data("row-id"):o.converter.from(e.data("row-id")+""),s=null==o.identifier?o.currentRows[i]:o.currentRows.first(function(t){return t[o.identifier]===i});o.selection&&o.options.rowSelect&&(e.hasClass(o.options.css.selected)?o.deselect([i]):o.select([i])),o.element.trigger("click"+f,[o.columns,s])})}.call(this,e)}else b.call(this)}.call(o,t),function(){if(0!==this.options.navigation){var t=m(this.options.css.infos),e=g.call(this,t);if(0this.total?this.total:i,start:0===this.total?0:i-this.rowCount+1,total:this.total})));C.call(this,e,s)}}}.call(o),function(){if(0!==this.options.navigation){var t=m(this.options.css.pagination),e=g.call(this,t)._bgShowAria(-1!==this.rowCount);if(-1!==this.rowCount&&0=this.options.padding?Math.max(l,1):Math.max(l-this.options.padding+r,1),c=2*this.options.padding+1,h=c<=o?c:o;w.call(this,n,"first","«","first")._bgEnableAria(1 td").first(),o=r.element.height()-e.height()-(s.height()+20),n=r.columns.where(d).length;r.selection&&(n+=1),i.html(t.loading.resolve(v.call(r,{columns:n}))),-1!==r.rowCount&&0 td").css("padding","20px 0 "+o+"px")}},250)}.call(this),this.options.ajax){var t=function(){var t={current:this.current,rowCount:this.rowCount,sort:this.sortDictionary,searchPhrase:this.searchPhrase},e=this.options.post;return e=p.isFunction(e)?e():e,this.options.requestHandler(p.extend(!0,t,e))}.call(this),i=function(){var t=this.options.url;return p.isFunction(t)?t():t}.call(this);if(null==i||"string"!=typeof i||0===i.length)throw new Error("Url setting must be a none empty string or a function that returns one.");this.xqr&&this.xqr.abort();var s={url:i,data:t,success:function(t){o.xqr=null,"string"==typeof t&&(t=p.parseJSON(t)),t=o.options.responseHandler(t),o.current=t.current,e(t.rows,t.total)},error:function(t,e,i){o.xqr=null,"abort"!==e&&(b.call(o),o.element._bgBusyAria(!1).trigger("loaded"+f))}};s=p.extend(this.options.ajaxSettings,s),this.xqr=p.ajax(s)}else{var n=0 tr"),a=this.options.css,c=this.options.templates,h="",d=this.options.sorting;if(this.selection){var e=this.options.multiSelect?c.select.resolve(v.call(l,{type:"checkbox",value:"all"})):"";h+=c.rawHeaderCell.resolve(v.call(l,{content:e,css:a.selectCell}))}if(p.each(this.columns,function(t,e){if(e.visible){var i=l.sortDictionary[e.id],s=d&&i&&"asc"===i?a.iconUp:d&&i&&"desc"===i?a.iconDown:"",o=c.icon.resolve(v.call(l,{iconCss:s})),n=e.headerAlign,r=0i[n.id]?r(1):e[n.id]o?t(e,i,o):0})}}var c=function(t,e){this.element=p(t),this.origin=this.element.clone(),this.options=p.extend(!0,{},c.defaults,this.element.data(),e);var i=this.options.rowCount=this.element.data().rowCount||e.rowCount||this.options.rowCount;this.columns=[],this.current=1,this.currentRows=[],this.identifier=null,this.selection=!1,this.converter=null,this.rowCount=p.isArray(i)?i[0]:i,this.rows=[],this.searchPhrase="",this.selectedRows=[],this.sortDictionary={},this.total=0,this.totalPages=0,this.cachedParams={lbl:this.options.labels,css:this.options.css,ctx:{}},this.header=null,this.footer=null,this.xqr=null};if(c.defaults={navigation:3,padding:2,columnSelection:!0,rowCount:[10,25,50,-1],selection:!1,multiSelect:!1,rowSelect:!1,keepSelection:!1,highlightRows:!1,sorting:!0,multiSort:!1,searchSettings:{delay:250,characters:1},ajax:!1,ajaxSettings:{method:"POST"},post:{},url:"",caseSensitive:!0,requestHandler:function(t){return t},responseHandler:function(t){return t},converters:{numeric:{from:function(t){return+t},to:function(t){return t+""}},string:{from:function(t){return t},to:function(t){return t}}},css:{actions:"actions btn-group",center:"text-center",columnHeaderAnchor:"column-header-anchor",columnHeaderText:"text",dropDownItem:"dropdown-item",dropDownItemButton:"dropdown-item-button",dropDownItemCheckbox:"dropdown-item-checkbox",dropDownMenu:"dropdown btn-group",dropDownMenuItems:"dropdown-menu pull-right",dropDownMenuText:"dropdown-text",footer:"bootgrid-footer container-fluid",header:"bootgrid-header container-fluid",icon:"icon glyphicon",iconColumns:"glyphicon-th-list",iconDown:"glyphicon-chevron-down",iconRefresh:"glyphicon-refresh",iconSearch:"glyphicon-search",iconUp:"glyphicon-chevron-up",infos:"infos",left:"text-left",pagination:"pagination",paginationButton:"button",responsiveTable:"table-responsive",right:"text-right",search:"search form-group",searchField:"search-field form-control",selectBox:"select-box",selectCell:"select-cell",selected:"active",sortable:"sortable",table:"bootgrid-table table"},formatters:{},labels:{all:"All",infos:"Showing {{ctx.start}} to {{ctx.end}} of {{ctx.total}} entries",loading:"Loading...",noResults:"No results found!",refresh:"Refresh",search:"Search"},statusMapping:{0:"success",1:"info",2:"warning",3:"danger"},templates:{actionButton:'',actionDropDown:'',actionDropDownItem:'{{ctx.text}}',actionDropDownCheckboxItem:'',actions:'',body:"",cell:'| {{ctx.content}} | ',footer:'',header:'',headerCell:'{{ctx.column.text}}{{ctx.icon}} | ',icon:'',infos:'{{lbl.infos}}
',loading:'| {{lbl.loading}} |
',noResults:'| {{lbl.noResults}} |
',pagination:'',paginationItem:'',rawHeaderCell:'{{ctx.content}} | ',row:"{{ctx.cells}}
",search:'',select:''}},c.prototype.append=function(t){if(this.options.ajax);else{for(var e=[],i=0;i=this.currentRows.length;for(i=0;!this.options.keepSelection&&n&&i tr "+o+":checked").trigger("click"+f),i=0;i tr[data-row-id="'+this.selectedRows[i]+'"]').addClass(this.options.css.selected)._bgAria("selected","true").find(o).prop("checked",!0);this.element.trigger("selected"+f,[s])}}return this},c.prototype.deselect=function(t){if(this.selection){t=t||this.currentRows.propValues(this.identifier);for(var e,i,s,o=[];0 tr[data-row-id="'+o[i][this.identifier]+'"]').removeClass(this.options.css.selected)._bgAria("selected","false").find(n).prop("checked",!1);this.element.trigger("deselected"+f,[o])}}return this},c.prototype.sort=function(t){var e=t?p.extend({},t):{};return e===this.sortDictionary||(this.sortDictionary=e,x.call(this),n.call(this),u.call(this)),this},c.prototype.getColumnSettings=function(){return p.merge([],this.columns)},c.prototype.getCurrentPage=function(){return this.current},c.prototype.getCurrentRows=function(){return p.merge([],this.currentRows)},c.prototype.getRowCount=function(){return this.rowCount},c.prototype.getSearchPhrase=function(){return this.searchPhrase},c.prototype.getSelectedRows=function(){return p.merge([],this.selectedRows)},c.prototype.getSortDictionary=function(){return p.extend({},this.sortDictionary)},c.prototype.getTotalPageCount=function(){return this.totalPages},c.prototype.getTotalRowCount=function(){return this.total},p.fn.extend({_bgAria:function(t,e){return e?this.attr("aria-"+t,e):this.attr("aria-"+t)},_bgBusyAria:function(t){return null==t||t?this._bgAria("busy","true"):this._bgAria("busy","false")},_bgRemoveAria:function(t){return this.removeAttr("aria-"+t)},_bgEnableAria:function(t){return null==t||t?this.removeClass("disabled")._bgAria("disabled","false"):this.addClass("disabled")._bgAria("disabled","true")},_bgEnableField:function(t){return null==t||t?this.removeAttr("disabled"):this.attr("disabled","disable")},_bgShowAria:function(t){return null==t||t?this.show()._bgAria("hidden","false"):this.hide()._bgAria("hidden","true")},_bgSelectAria:function(t){return null==t||t?this.addClass("active")._bgAria("selected","true"):this.removeClass("active")._bgAria("selected","false")},_bgId:function(t){return t?this.attr("id",t):this.attr("id")}}),!String.prototype.resolve){var r={checked:function(t){return"boolean"==typeof t?t?'checked="checked"':"":t}};String.prototype.resolve=function(t,o){var n=this;return p.each(t,function(t,e){if(null!=e&&"function"!=typeof e)if("object"==typeof e){var i=o?p.extend([],o):[];i.push(t),n=n.resolve(e,i)+""}else{r&&r[t]&&"function"==typeof r[t]&&(e=r[t](e)),t=o?o.join(".")+"."+t:t;var s=new RegExp("\\{\\{"+t+"\\}\\}","gm");n=n.replace(s,e.replace?e.replace(/\$/gi,"$"):e)}}),n}}Array.prototype.first||(Array.prototype.first=function(t){for(var e=0;ei?this.length>s?this.slice(i,s):this.slice(i):[]}),Array.prototype.where||(Array.prototype.where=function(t){for(var e=[],i=0;ijQuery Bootgrid
Rafael Staib
r_staib
- https://github.com/rstaib/jquery-bootgrid/blob/master/LICENSE.txt
+ https://github.com/opnsense/jquery-bootgrid/blob/master/LICENSE.txt
http://www.jquery-bootgrid.com
http://www.jquery-bootgrid.com/icon.png
true
Nice, sleek and intuitive. A grid control especially designed for bootstrap.
Nice, sleek and intuitive. A grid control especially designed for bootstrap.
- � Copyright 2014-2015, Rafael Staib
+ Copyright © 2014-2015, Rafael Staib; Copyright © 2018, Deciso B.V.
jQuery, Grid, Table, Bootstrap, Accessibility, HTML5, Sorting, Filtering, UI
diff --git a/package.json b/package.json
index 2528243..cb644b0 100644
--- a/package.json
+++ b/package.json
@@ -2,21 +2,27 @@
"name": "jquery-bootgrid",
"namespace": "jquery.bootgrid",
"title": "jQuery Bootgrid",
- "version": "1.3.1",
+ "version": "1.3.3",
"description": "Nice, sleek and intuitive. A grid control especially designed for bootstrap.",
"homepage": "http://www.jquery-bootgrid.com",
"author": {
- "name": "Rafael Staib",
- "email": "me@rafaelstaib.com",
- "url": "http://www.rafaelstaib.com"
+ "name": "Deciso B.V.",
+ "url": "https://www.opnsense.org"
},
- "bugs": "https://github.com/rstaib/jquery-bootgrid/issues",
+ "contributors": [
+ {
+ "name": "Rafael Staib",
+ "email": "me@rafaelstaib.com",
+ "url": "http://www.rafaelstaib.com"
+ }
+ ],
+ "bugs": "https://github.com/opnsense/jquery-bootgrid/issues",
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
- "url": "https://github.com/rstaib/jquery-bootgrid.git"
+ "url": "https://github.com/opnsense/jquery-bootgrid.git"
},
"keywords": [
"jquery-plugin",
@@ -34,29 +40,27 @@
"HTML5",
"Accessibility"
],
- "licenses": [
- { "type": "MIT", "url": "http://www.opensource.org/licenses/MIT" }
- ],
+ "license": "MIT",
"dependencies": {
- "jquery": ">=1.9.0",
- "bootstrap": ">=3.1.1"
+ "bootstrap": "^3.3.7",
+ "jquery": "^3.2.1"
},
"devDependencies": {
- "grunt": "~0.4.5",
- "grunt-cli": "~0.1.13",
- "grunt-contrib-less": "~0.11.0",
- "grunt-contrib-csslint": "~0.3.1",
- "grunt-contrib-cssmin": "~0.10.0",
- "grunt-contrib-qunit": "~0.4.0",
- "grunt-contrib-jshint": "~0.10.0",
- "grunt-contrib-uglify": "~0.9.2",
- "grunt-contrib-concat": "~0.4.0",
- "grunt-contrib-yuidoc": "~0.5.2",
- "grunt-contrib-clean": "~0.5.0",
- "grunt-contrib-compress": "~0.8.0",
- "grunt-nuget": "~0.1.4",
- "grunt-regex-replace": "~0.2.6",
- "grunt-exec": "~0.4.5"
+ "grunt": "^1.0.3",
+ "grunt-cli": "~1.3.2",
+ "grunt-contrib-clean": "~2.0.0",
+ "grunt-contrib-compress": "~1.4.3",
+ "grunt-contrib-concat": "~1.0.1",
+ "grunt-contrib-csslint": "~2.0.0",
+ "grunt-contrib-cssmin": "~3.0.0",
+ "grunt-contrib-jshint": "~2.0.0",
+ "grunt-contrib-less": "~2.0.0",
+ "grunt-contrib-qunit": "~3.0.1",
+ "grunt-contrib-uglify": "~4.0.0",
+ "grunt-contrib-yuidoc": "~1.0.0",
+ "grunt-exec": "~3.0.0",
+ "grunt-nuget": "~0.3.1",
+ "grunt-regex-replace": "~0.4.0"
},
"readmeFilename": "README.md"
-}
\ No newline at end of file
+}
diff --git a/test/index.html b/test/index.html
index cdc0425..9808489 100644
--- a/test/index.html
+++ b/test/index.html
@@ -3,7 +3,7 @@
jQuery Bootgrid Test Suite
-
+
| Source: | " + escapeText( source ) + " |
|---|
";
- }
- }
- 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.' );
- }
- }
-};
-
-/**
- * @deprecate since 1.8.0
- * Kept assertion helpers in root for backwards compatibility.
- */
-extend( QUnit, assert );
-
-/**
- * @deprecated since 1.9.0
- * Kept root "raises()" for backwards compatibility.
- * (Note that we don't introduce assert.raises).
- */
-QUnit.raises = assert[ "throws" ];
-
-/**
- * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
- * Kept to avoid TypeErrors for undefined methods.
- */
-QUnit.equals = function() {
- QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
-};
-QUnit.same = function() {
- QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
-};
-
-// We want access to the constructor's prototype
-(function() {
- function F() {}
- F.prototype = QUnit;
- QUnit = new F();
- // Make F QUnit's constructor so that we can add to the prototype later
- QUnit.constructor = F;
-}());
-
-/**
- * Config object: Maintain internal state
- * Later exposed as QUnit.config
- * `config` initialized at top of scope
- */
-config = {
- // The queue of tests to run
- queue: [],
-
- // block until document ready
- blocking: true,
-
- // when enabled, show only failing tests
- // gets persisted through sessionStorage and can be changed in UI via checkbox
- hidepassed: false,
-
- // by default, run previously failed tests first
- // very useful in combination with "Hide passed tests" checked
- reorder: true,
-
- // by default, modify document.title when suite is done
- altertitle: true,
-
- // when enabled, all tests must call expect()
- requireExpects: false,
-
- // add checkboxes that are persisted in the query-string
- // when enabled, the id is set to `true` as a `QUnit.config` property
- urlConfig: [
- {
- id: "noglobals",
- label: "Check for Globals",
- tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
- },
- {
- id: "notrycatch",
- label: "No try-catch",
- tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
- }
- ],
-
- // Set of all modules.
- modules: {},
-
- // logging callback queues
- begin: [],
- done: [],
- log: [],
- testStart: [],
- testDone: [],
- moduleStart: [],
- moduleDone: []
-};
-
-// Export global variables, unless an 'exports' object exists,
-// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
-if ( typeof exports === "undefined" ) {
- extend( window, QUnit );
-
- // Expose QUnit object
- window.QUnit = QUnit;
-}
-
-// Initialize more QUnit.config and QUnit.urlParams
-(function() {
- var i,
- location = window.location || { search: "", protocol: "file:" },
- params = location.search.slice( 1 ).split( "&" ),
- length = params.length,
- urlParams = {},
- current;
-
- if ( params[ 0 ] ) {
- for ( i = 0; i < length; i++ ) {
- current = params[ i ].split( "=" );
- current[ 0 ] = decodeURIComponent( current[ 0 ] );
- // allow just a key to turn on a flag, e.g., test.html?noglobals
- current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
- urlParams[ current[ 0 ] ] = current[ 1 ];
- }
- }
-
- QUnit.urlParams = urlParams;
-
- // String search anywhere in moduleName+testName
- config.filter = urlParams.filter;
-
- // Exact match of the module name
- config.module = urlParams.module;
-
- config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
-
- // Figure out if we're running the tests from a server or not
- QUnit.isLocal = location.protocol === "file:";
-}());
-
-// Extend QUnit object,
-// these after set here because they should not be exposed as global functions
-extend( QUnit, {
- assert: assert,
-
- config: config,
-
- // Initialize the configuration options
- init: function() {
- extend( config, {
- stats: { all: 0, bad: 0 },
- moduleStats: { all: 0, bad: 0 },
- started: +new Date(),
- updateRate: 1000,
- blocking: false,
- autostart: true,
- autorun: false,
- filter: "",
- queue: [],
- semaphore: 1
- });
-
- var tests, banner, result,
- qunit = id( "qunit" );
-
- if ( qunit ) {
- qunit.innerHTML =
- "" +
- "" +
- "" +
- "" +
- "
";
- }
-
- tests = id( "qunit-tests" );
- banner = id( "qunit-banner" );
- result = id( "qunit-testresult" );
-
- if ( tests ) {
- tests.innerHTML = "";
- }
-
- if ( banner ) {
- banner.className = "";
- }
-
- if ( result ) {
- result.parentNode.removeChild( result );
- }
-
- if ( tests ) {
- result = document.createElement( "p" );
- result.id = "qunit-testresult";
- result.className = "result";
- tests.parentNode.insertBefore( result, tests );
- result.innerHTML = "Running...
";
- }
- },
-
- // Resets the test setup. Useful for tests that modify the DOM.
- reset: function() {
- var fixture = id( "qunit-fixture" );
- if ( fixture ) {
- fixture.innerHTML = config.fixture;
- }
- },
-
- // Trigger an event on an element.
- // @example triggerEvent( document.body, "click" );
- 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
- is: function( type, obj ) {
- return QUnit.objectType( obj ) === type;
- },
-
- objectType: function( obj ) {
- if ( typeof obj === "undefined" ) {
- return "undefined";
- // consider: typeof null === object
- }
- if ( obj === null ) {
- return "null";
- }
-
- 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;
- },
-
- push: function( result, actual, expected, message ) {
- if ( !config.current ) {
- throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
- }
-
- var output, source,
- details = {
- module: config.current.module,
- name: config.current.testName,
- result: result,
- message: message,
- actual: actual,
- expected: expected
- };
-
- message = escapeText( message ) || ( result ? "okay" : "failed" );
- message = "" + message + "";
- output = message;
-
- if ( !result ) {
- expected = escapeText( QUnit.jsDump.parse(expected) );
- actual = escapeText( QUnit.jsDump.parse(actual) );
- output += "| Expected: | " + expected + " |
";
-
- if ( actual !== expected ) {
- output += "| Result: | " + actual + " |
";
- output += "| Diff: | " + QUnit.diff( expected, actual ) + " |
";
- }
-
- source = sourceFromStacktrace();
-
- if ( source ) {
- details.source = source;
- output += "| Source: | " + escapeText( source ) + " |
";
- }
-
- output += "
";
- }
-
- runLoggingCallbacks( "log", QUnit, details );
-
- config.current.assertions.push({
- result: !!result,
- message: output
- });
- },
-
- pushFailure: function( message, source, actual ) {
- if ( !config.current ) {
- throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
- }
-
- var output,
- details = {
- module: config.current.module,
- name: config.current.testName,
- result: false,
- message: message
- };
-
- message = escapeText( message ) || "error";
- message = "" + message + "";
- output = message;
-
- output += "";
-
- if ( actual ) {
- output += "| Result: | " + escapeText( actual ) + " |
";
- }
-
- if ( source ) {
- details.source = source;
- output += "| Source: | " + escapeText( source ) + " |
";
- }
-
- output += "
";
-
- runLoggingCallbacks( "log", QUnit, details );
-
- config.current.assertions.push({
- result: false,
- message: output
- });
- },
-
- url: function( params ) {
- params = extend( extend( {}, QUnit.urlParams ), params );
- var key,
- querystring = "?";
-
- for ( key in params ) {
- if ( !hasOwn.call( params, key ) ) {
- continue;
- }
- querystring += encodeURIComponent( key ) + "=" +
- encodeURIComponent( params[ key ] ) + "&";
- }
- return window.location.protocol + "//" + window.location.host +
- window.location.pathname + querystring.slice( 0, -1 );
- },
-
- extend: extend,
- id: id,
- addEvent: addEvent
- // load, equiv, jsDump, diff: Attached later
-});
-
-/**
- * @deprecated: Created for backwards compatibility with test runner that set the hook function
- * into QUnit.{hook}, instead of invoking it and passing the hook function.
- * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
- * Doing this allows us to tell if the following methods have been overwritten on the actual
- * QUnit object.
- */
-extend( QUnit.constructor.prototype, {
-
- // Logging callbacks; all receive a single argument with the listed properties
- // run test/logs.html for any related changes
- begin: registerLoggingCallback( "begin" ),
-
- // done: { failed, passed, total, runtime }
- done: registerLoggingCallback( "done" ),
-
- // log: { result, actual, expected, message }
- log: registerLoggingCallback( "log" ),
-
- // testStart: { name }
- testStart: registerLoggingCallback( "testStart" ),
-
- // testDone: { name, failed, passed, total, duration }
- testDone: registerLoggingCallback( "testDone" ),
-
- // moduleStart: { name }
- moduleStart: registerLoggingCallback( "moduleStart" ),
-
- // moduleDone: { name, failed, passed, total }
- moduleDone: registerLoggingCallback( "moduleDone" )
-});
-
-if ( typeof document === "undefined" || document.readyState === "complete" ) {
- config.autorun = true;
-}
-
-QUnit.load = function() {
- runLoggingCallbacks( "begin", QUnit, {} );
-
- // Initialize the config, saving the execution queue
- var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
- urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
- numModules = 0,
- moduleFilterHtml = "",
- urlConfigHtml = "",
- oldconfig = extend( {}, config );
-
- QUnit.init();
- extend(config, oldconfig);
-
- config.blocking = false;
-
- len = config.urlConfig.length;
-
- for ( i = 0; i < len; i++ ) {
- val = config.urlConfig[i];
- if ( typeof val === "string" ) {
- val = {
- id: val,
- label: val,
- tooltip: "[no tooltip available]"
- };
- }
- config[ val.id ] = QUnit.urlParams[ val.id ];
- urlConfigHtml += "";
- }
-
- moduleFilterHtml += "";
-
- // `userAgent` initialized at top of scope
- userAgent = id( "qunit-userAgent" );
- if ( userAgent ) {
- userAgent.innerHTML = navigator.userAgent;
- }
-
- // `banner` initialized at top of scope
- banner = id( "qunit-header" );
- if ( banner ) {
- banner.innerHTML = "" + banner.innerHTML + " ";
- }
-
- // `toolbar` initialized at top of scope
- toolbar = id( "qunit-testrunner-toolbar" );
- if ( toolbar ) {
- // `filter` initialized at top of scope
- filter = document.createElement( "input" );
- filter.type = "checkbox";
- filter.id = "qunit-filter-pass";
-
- addEvent( filter, "click", function() {
- var tmp,
- ol = document.getElementById( "qunit-tests" );
-
- if ( filter.checked ) {
- ol.className = ol.className + " hidepass";
- } else {
- tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
- ol.className = tmp.replace( / hidepass /, " " );
- }
- if ( defined.sessionStorage ) {
- if (filter.checked) {
- sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
- } else {
- sessionStorage.removeItem( "qunit-filter-passed-tests" );
- }
- }
- });
-
- if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
- filter.checked = true;
- // `ol` initialized at top of scope
- ol = document.getElementById( "qunit-tests" );
- ol.className = ol.className + " hidepass";
- }
- toolbar.appendChild( filter );
-
- // `label` initialized at top of scope
- label = document.createElement( "label" );
- label.setAttribute( "for", "qunit-filter-pass" );
- label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
- label.innerHTML = "Hide passed tests";
- toolbar.appendChild( label );
-
- 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 } );
- });
- toolbar.appendChild(moduleFilter);
- }
- }
-
- // `main` initialized at top of scope
- main = id( "qunit-fixture" );
- if ( main ) {
- config.fixture = main.innerHTML;
- }
-
- if ( config.autostart ) {
- QUnit.start();
- }
-};
-
-addEvent( window, "load", QUnit.load );
-
-// `onErrorFnPrev` initialized at top of scope
-// Preserve other handlers
-onErrorFnPrev = window.onerror;
-
-// Cover uncaught exceptions
-// Returning true will surpress the default browser handler,
-// returning false will let it run.
-window.onerror = function ( error, filePath, linerNr ) {
- var ret = false;
- if ( onErrorFnPrev ) {
- ret = onErrorFnPrev( error, filePath, linerNr );
- }
-
- // Treat return value as window.onerror itself does,
- // Only do our handling if not surpressed.
- if ( ret !== true ) {
- if ( QUnit.config.current ) {
- if ( QUnit.config.current.ignoreGlobalErrors ) {
- return true;
- }
- QUnit.pushFailure( error, filePath + ":" + linerNr );
- } else {
- QUnit.test( "global failure", extend( function() {
- QUnit.pushFailure( error, filePath + ":" + linerNr );
- }, { validTest: validTest } ) );
- }
- return false;
- }
-
- return ret;
-};
-
-function done() {
- config.autorun = true;
-
- // Log the last module results
- if ( config.currentModule ) {
- runLoggingCallbacks( "moduleDone", QUnit, {
- name: config.currentModule,
- failed: config.moduleStats.bad,
- passed: config.moduleStats.all - config.moduleStats.bad,
- total: config.moduleStats.all
- });
- }
-
- var i, key,
- banner = id( "qunit-banner" ),
- tests = id( "qunit-tests" ),
- runtime = +new Date() - config.started,
- passed = config.stats.all - config.stats.bad,
- html = [
- "Tests completed in ",
- runtime,
- " milliseconds.
",
- "",
- passed,
- " assertions of ",
- config.stats.all,
- " passed, ",
- config.stats.bad,
- " failed."
- ].join( "" );
-
- if ( banner ) {
- banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
- }
-
- if ( tests ) {
- id( "qunit-testresult" ).innerHTML = html;
- }
-
- if ( config.altertitle && typeof document !== "undefined" && document.title ) {
- // show ✖ for good, ✔ for bad suite result in title
- // use escape sequences in case file gets loaded with non-utf-8-charset
- document.title = [
- ( config.stats.bad ? "\u2716" : "\u2714" ),
- document.title.replace( /^[\u2714\u2716] /i, "" )
- ].join( " " );
- }
-
- // 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,
- passed: passed,
- total: config.stats.all,
- runtime: runtime
- });
-}
-
-/** @return Boolean: true if this test should be ran */
-function validTest( test ) {
- var include,
- filter = config.filter && config.filter.toLowerCase(),
- module = config.module && config.module.toLowerCase(),
- fullName = (test.module + ": " + test.testName).toLowerCase();
-
- // Internally-generated tests are always valid
- if ( test.callback && test.callback.validTest === validTest ) {
- delete test.callback.validTest;
- return true;
- }
-
- if ( config.testNumber ) {
- return test.testNumber === config.testNumber;
- }
-
- if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
- return false;
- }
-
- if ( !filter ) {
- return true;
- }
-
- include = filter.charAt( 0 ) !== "!";
- if ( !include ) {
- filter = filter.slice( 1 );
- }
-
- // If the filter matches, we need to honour include
- if ( fullName.indexOf( filter ) !== -1 ) {
- return include;
- }
-
- // Otherwise, do the opposite
- return !include;
-}
-
-// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
-// Later Safari and IE10 are supposed to support error.stack as well
-// 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 {
- throw new Error();
- } catch ( e ) {
- return extractStacktrace( e, offset );
- }
-}
-
-/**
- * Escape text for attribute or text content.
- */
-function escapeText( s ) {
- if ( !s ) {
- return "";
- }
- s = s + "";
- // Both single quotes and double quotes (for attributes)
- return s.replace( /['"<>&]/g, function( s ) {
- switch( s ) {
- case '\'':
- return ''';
- case '"':
- return '"';
- case '<':
- return '<';
- case '>':
- return '>';
- case '&':
- return '&';
- }
- });
-}
-
-function synchronize( callback, last ) {
- config.queue.push( callback );
-
- if ( config.autorun && !config.blocking ) {
- process( last );
- }
-}
-
-function process( last ) {
- function next() {
- process( last );
- }
- var start = new Date().getTime();
- config.depth = config.depth ? config.depth + 1 : 1;
-
- while ( config.queue.length && !config.blocking ) {
- if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
- config.queue.shift()();
- } else {
- window.setTimeout( next, 13 );
- break;
- }
- }
- config.depth--;
- if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
- done();
- }
-}
-
-function saveGlobal() {
- config.pollution = [];
-
- if ( config.noglobals ) {
- for ( var key in window ) {
- // in Opera sometimes DOM element ids show up here, ignore them
- if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
- continue;
- }
- config.pollution.push( key );
- }
- }
-}
-
-function checkPollution() {
- var newGlobals,
- deletedGlobals,
- old = config.pollution;
-
- saveGlobal();
-
- newGlobals = diff( config.pollution, old );
- if ( newGlobals.length > 0 ) {
- QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
- }
-
- deletedGlobals = diff( old, config.pollution );
- if ( deletedGlobals.length > 0 ) {
- QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
- }
-}
-
-// returns a new Array with the elements that are in a but not in b
-function diff( a, b ) {
- var i, j,
- result = a.slice();
-
- for ( i = 0; i < result.length; i++ ) {
- for ( j = 0; j < b.length; j++ ) {
- if ( result[i] === b[j] ) {
- result.splice( i, 1 );
- i--;
- break;
- }
- }
- }
- return result;
-}
-
-function extend( a, b ) {
- for ( var prop in b ) {
- if ( b[ prop ] === undefined ) {
- delete a[ prop ];
-
- // Avoid "Member not found" error in IE8 caused by setting window.constructor
- } else if ( prop !== "constructor" || a !== window ) {
- a[ prop ] = b[ prop ];
- }
- }
-
- return a;
-}
-
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
- // Standards-based browsers
- if ( elem.addEventListener ) {
- elem.addEventListener( type, fn, false );
- // IE
- } else {
- elem.attachEvent( "on" + type, fn );
- }
-}
-
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
- var i = elems.length;
- while ( i-- ) {
- addEvent( elems[i], type, fn );
- }
-}
-
-function hasClass( elem, name ) {
- 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 neccecarily
- elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
-}
-
-function id( name ) {
- return !!( typeof document !== "undefined" && document && document.getElementById ) &&
- document.getElementById( name );
-}
-
-function registerLoggingCallback( key ) {
- return function( callback ) {
- config[key].push( callback );
- };
-}
-
-// Supports deprecated method of completely overwriting logging callbacks
-function runLoggingCallbacks( key, scope, args ) {
- var i, callbacks;
- if ( QUnit.hasOwnProperty( key ) ) {
- QUnit[ key ].call(scope, args );
- } else {
- callbacks = config[ key ];
- for ( i = 0; i < callbacks.length; i++ ) {
- callbacks[ i ].call( scope, args );
- }
- }
-}
-
-// Test for equality any JavaScript type.
-// Author: Philippe Rathé
-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 = [],
-
- getProto = Object.getPrototypeOf || function ( obj ) {
- 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 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" &&
- // the regex itself
- a.source === b.source &&
- // and its modifers
- 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;
-
- // 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,
- // 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 );
-
- 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] ) {
- // don't go down the same path twice
- loop = true;
- }
- }
- 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 = [].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 = 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 ? "
" : "\n" : this.HTML ? " " : " ";
- },
- // 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, " " );
- }
- 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 ) {
- 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 ? "<" : "<",
- close = QUnit.jsDump.HTML ? ">" : ">",
- 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;
-}());
-
-// from jquery.js
-function inArray( elem, array ) {
- if ( array.indexOf ) {
- return array.indexOf( elem );
- }
-
- for ( var i = 0, length = array.length; i < length; i++ ) {
- if ( array[ i ] === elem ) {
- return i;
- }
- }
-
- return -1;
-}
-
-/*
- * Javascript Diff Algorithm
- * By John Resig (http://ejohn.org/)
- * Modified by Chu Alan "sprite"
- *
- * Released under the MIT license.
- *
- * More Info:
- * http://ejohn.org/projects/javascript-diff-algorithm/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
- */
-QUnit.diff = (function() {
- /*jshint eqeqeq:false, eqnull:true */
- function diff( o, n ) {
- var i,
- ns = {},
- os = {};
-
- for ( i = 0; i < n.length; i++ ) {
- if ( !hasOwn.call( ns, n[i] ) ) {
- ns[ n[i] ] = {
- rows: [],
- o: null
- };
- }
- ns[ n[i] ].rows.push( i );
- }
-
- for ( i = 0; i < o.length; i++ ) {
- if ( !hasOwn.call( os, o[i] ) ) {
- os[ o[i] ] = {
- rows: [],
- n: null
- };
- }
- os[ o[i] ].rows.push( i );
- }
-
- for ( i in ns ) {
- if ( !hasOwn.call( ns, i ) ) {
- continue;
- }
- if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
- n[ ns[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] ],
- row: ns[i].rows[0]
- };
- }
- }
-
- 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 &&
- n[ i + 1 ] == o[ n[i].row + 1 ] ) {
-
- n[ i + 1 ] = {
- text: n[ i + 1 ],
- row: n[i].row + 1
- };
- o[ n[i].row + 1 ] = {
- text: o[ n[i].row + 1 ],
- row: i + 1
- };
- }
- }
-
- 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 &&
- n[ i - 1 ] == o[ n[i].row - 1 ]) {
-
- n[ i - 1 ] = {
- text: n[ i - 1 ],
- row: n[i].row - 1
- };
- o[ n[i].row - 1 ] = {
- text: o[ n[i].row - 1 ],
- row: i - 1
- };
- }
- }
-
- return {
- o: o,
- n: n
- };
- }
-
- return function( o, n ) {
- o = o.replace( /\s+$/, "" );
- n = n.replace( /\s+$/, "" );
-
- var i, pre,
- str = "",
- out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
- oSpace = o.match(/\s+/g),
- nSpace = n.match(/\s+/g);
-
- if ( oSpace == null ) {
- oSpace = [ " " ];
- }
- else {
- oSpace.push( " " );
- }
-
- if ( nSpace == null ) {
- nSpace = [ " " ];
- }
- else {
- nSpace.push( " " );
- }
-
- if ( out.n.length === 0 ) {
- for ( i = 0; i < out.o.length; i++ ) {
- str += "" + out.o[i] + oSpace[i] + "";
- }
- }
- else {
- if ( out.n[0].text == null ) {
- for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
- str += "" + out.o[n] + oSpace[n] + "";
- }
- }
-
- for ( i = 0; i < out.n.length; i++ ) {
- if (out.n[i].text == null) {
- str += "" + out.n[i] + nSpace[i] + "";
- }
- else {
- // `pre` initialized at top of scope
- pre = "";
-
- for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
- pre += "" + out.o[n] + oSpace[n] + "";
- }
- str += " " + out.n[i].text + nSpace[i] + pre;
- }
- }
- }
-
- return str;
- };
-}());
-
-// for CommonJS enviroments, export everything
-if ( typeof exports !== "undefined" ) {
- extend( exports, QUnit );
-}
-
-// get at whatever the global object is, like window in browsers
-}( (function() {return this;}.call()) ));
diff --git a/test/qunit/qunit-2.8.0.css b/test/qunit/qunit-2.8.0.css
new file mode 100644
index 0000000..9bf0381
--- /dev/null
+++ b/test/qunit/qunit-2.8.0.css
@@ -0,0 +1,436 @@
+/*!
+ * QUnit 2.8.0
+ * https://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2018-11-02T16:17Z
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header (excluding toolbar) */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699A4;
+ background-color: #0D3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: 400;
+
+ border-radius: 5px 5px 0 0;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #C2CCD1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #FFF;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-filteredTest {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #366097;
+ background-color: #F4FF77;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #FFF;
+ background-color: #2B81AF;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Toolbar */
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #5E740B;
+ background-color: #EEE;
+}
+
+#qunit-testrunner-toolbar .clearfix {
+ height: 0;
+ clear: both;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+}
+
+#qunit-testrunner-toolbar input[type=checkbox],
+#qunit-testrunner-toolbar input[type=radio] {
+ margin: 3px;
+ vertical-align: -2px;
+}
+
+#qunit-testrunner-toolbar input[type=text] {
+ box-sizing: border-box;
+ height: 1.6em;
+}
+
+.qunit-url-config,
+.qunit-filter,
+#qunit-modulefilter {
+ display: inline-block;
+ line-height: 2.1em;
+}
+
+.qunit-filter,
+#qunit-modulefilter {
+ float: right;
+ position: relative;
+ margin-left: 1em;
+}
+
+.qunit-url-config label {
+ margin-right: 0.5em;
+}
+
+#qunit-modulefilter-search {
+ box-sizing: border-box;
+ width: 400px;
+}
+
+#qunit-modulefilter-search-container:after {
+ position: absolute;
+ right: 0.3em;
+ content: "\25bc";
+ color: black;
+}
+
+#qunit-modulefilter-dropdown {
+ /* align with #qunit-modulefilter-search */
+ box-sizing: border-box;
+ width: 400px;
+ position: absolute;
+ right: 0;
+ top: 50%;
+ margin-top: 0.8em;
+
+ border: 1px solid #D3D3D3;
+ border-top: none;
+ border-radius: 0 0 .25em .25em;
+ color: #000;
+ background-color: #F5F5F5;
+ z-index: 99;
+}
+
+#qunit-modulefilter-dropdown a {
+ color: inherit;
+ text-decoration: none;
+}
+
+#qunit-modulefilter-dropdown .clickable.checked {
+ font-weight: bold;
+ color: #000;
+ background-color: #D2E0E6;
+}
+
+#qunit-modulefilter-dropdown .clickable:hover {
+ color: #FFF;
+ background-color: #0D3349;
+}
+
+#qunit-modulefilter-actions {
+ display: block;
+ overflow: auto;
+
+ /* align with #qunit-modulefilter-dropdown-list */
+ font: smaller/1.5em sans-serif;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
+ box-sizing: border-box;
+ max-height: 2.8em;
+ display: block;
+ padding: 0.4em;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
+ float: right;
+ font: inherit;
+}
+
+#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
+ /* insert padding to align with checkbox margins */
+ padding-left: 3px;
+}
+
+#qunit-modulefilter-dropdown-list {
+ max-height: 200px;
+ overflow-y: auto;
+ margin: 0;
+ border-top: 2px groove threedhighlight;
+ padding: 0.4em 0 0;
+ font: smaller/1.5em sans-serif;
+}
+
+#qunit-modulefilter-dropdown-list li {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#qunit-modulefilter-dropdown-list .clickable {
+ display: block;
+ padding-left: 0.15em;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 1em 0.4em 1em;
+ border-bottom: 1px solid #FFF;
+ list-style-position: inside;
+}
+
+#qunit-tests > li {
+ display: none;
+}
+
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped,
+#qunit-tests li.aborted {
+ display: list-item;
+}
+
+#qunit-tests.hidepass {
+ position: relative;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass:not(.todo) {
+ visibility: hidden;
+ position: absolute;
+ width: 0;
+ height: 0;
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li.skipped strong {
+ cursor: default;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #C2CCD1;
+ text-decoration: none;
+}
+
+#qunit-tests li p a {
+ padding: 0.25em;
+ color: #6B6464;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #FFF;
+
+ border-radius: 5px;
+}
+
+.qunit-source {
+ margin: 0.6em 0 0.3em;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: 0.2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 0.5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ color: #374E0C;
+ background-color: #E0F2BE;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ color: #500;
+ background-color: #FFCACA;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: #000; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #FFF;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3C510C;
+ background-color: #FFF;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #FFF;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+}
+
+#qunit-tests .fail { color: #000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: #008000; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/*** Aborted tests */
+#qunit-tests .aborted { color: #000; background-color: orange; }
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+ background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-todo-label,
+#qunit-tests .qunit-skipped-label {
+ background-color: #F4FF77;
+ display: inline-block;
+ font-style: normal;
+ color: #366097;
+ line-height: 1.8em;
+ padding: 0 0.5em;
+ margin: -0.4em 0.4em -0.4em 0;
+}
+
+#qunit-tests .qunit-todo-label {
+ background-color: #EEE;
+}
+
+/** Result */
+
+#qunit-testresult {
+ color: #2B81AF;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid #FFF;
+}
+#qunit-testresult .clearfix {
+ height: 0;
+ clear: both;
+}
+#qunit-testresult .module-name {
+ font-weight: 700;
+}
+#qunit-testresult-display {
+ padding: 0.5em 1em 0.5em 1em;
+ width: 85%;
+ float:left;
+}
+#qunit-testresult-controls {
+ padding: 0.5em 1em 0.5em 1em;
+ width: 10%;
+ float:left;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/test/qunit/qunit-2.8.0.js b/test/qunit/qunit-2.8.0.js
new file mode 100644
index 0000000..e30bb19
--- /dev/null
+++ b/test/qunit/qunit-2.8.0.js
@@ -0,0 +1,6566 @@
+/*!
+ * QUnit 2.8.0
+ * https://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * https://jquery.org/license
+ *
+ * Date: 2018-11-02T16:17Z
+ */
+(function (global$1) {
+ 'use strict';
+
+ global$1 = global$1 && global$1.hasOwnProperty('default') ? global$1['default'] : global$1;
+
+ var window$1 = global$1.window;
+ var self$1 = global$1.self;
+ var console = global$1.console;
+ var setTimeout$1 = global$1.setTimeout;
+ var clearTimeout = global$1.clearTimeout;
+
+ var document$1 = window$1 && window$1.document;
+ var navigator = window$1 && window$1.navigator;
+
+ var localSessionStorage = function () {
+ var x = "qunit-test-string";
+ try {
+ global$1.sessionStorage.setItem(x, x);
+ global$1.sessionStorage.removeItem(x);
+ return global$1.sessionStorage;
+ } catch (e) {
+ return undefined;
+ }
+ }();
+
+ /**
+ * Returns a function that proxies to the given method name on the globals
+ * console object. The proxy will also detect if the console doesn't exist and
+ * will appropriately no-op. This allows support for IE9, which doesn't have a
+ * console if the developer tools are not open.
+ */
+ function consoleProxy(method) {
+ return function () {
+ if (console) {
+ console[method].apply(console, arguments);
+ }
+ };
+ }
+
+ var Logger = {
+ warn: consoleProxy("warn")
+ };
+
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+ return typeof obj;
+ } : function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+
+
+
+
+
+
+
+
+
+
+
+ var classCallCheck = function (instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ };
+
+ var createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+ }();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ var toConsumableArray = function (arr) {
+ if (Array.isArray(arr)) {
+ for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
+
+ return arr2;
+ } else {
+ return Array.from(arr);
+ }
+ };
+
+ var toString = Object.prototype.toString;
+ var hasOwn = Object.prototype.hasOwnProperty;
+ var now = Date.now || function () {
+ return new Date().getTime();
+ };
+
+ var hasPerformanceApi = detectPerformanceApi();
+ var performance = hasPerformanceApi ? window$1.performance : undefined;
+ var performanceNow = hasPerformanceApi ? performance.now.bind(performance) : now;
+
+ function detectPerformanceApi() {
+ return window$1 && typeof window$1.performance !== "undefined" && typeof window$1.performance.mark === "function" && typeof window$1.performance.measure === "function";
+ }
+
+ function measure(comment, startMark, endMark) {
+
+ // `performance.measure` may fail if the mark could not be found.
+ // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()`
+ try {
+ performance.measure(comment, startMark, endMark);
+ } catch (ex) {
+ Logger.warn("performance.measure could not be executed because of ", ex.message);
+ }
+ }
+
+ var defined = {
+ document: window$1 && window$1.document !== undefined,
+ setTimeout: setTimeout$1 !== undefined
+ };
+
+ // Returns a new Array with the elements that are in a but not in b
+ function diff(a, b) {
+ var i,
+ j,
+ result = a.slice();
+
+ for (i = 0; i < result.length; i++) {
+ for (j = 0; j < b.length; j++) {
+ if (result[i] === b[j]) {
+ result.splice(i, 1);
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Determines whether an element exists in a given array or not.
+ *
+ * @method inArray
+ * @param {Any} elem
+ * @param {Array} array
+ * @return {Boolean}
+ */
+ function inArray(elem, array) {
+ return array.indexOf(elem) !== -1;
+ }
+
+ /**
+ * 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).
+ */
+ function objectValues(obj) {
+ var key,
+ val,
+ vals = 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;
+ }
+
+ function extend(a, b, undefOnly) {
+ for (var prop in b) {
+ if (hasOwn.call(b, prop)) {
+ if (b[prop] === undefined) {
+ delete a[prop];
+ } else if (!(undefOnly && typeof a[prop] !== "undefined")) {
+ a[prop] = b[prop];
+ }
+ }
+ }
+
+ return a;
+ }
+
+ function objectType(obj) {
+ if (typeof obj === "undefined") {
+ return "undefined";
+ }
+
+ // Consider: typeof null === object
+ if (obj === null) {
+ return "null";
+ }
+
+ 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 "Set":
+ case "Map":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ case "Symbol":
+ return type.toLowerCase();
+ default:
+ return typeof obj === "undefined" ? "undefined" : _typeof(obj);
+ }
+ }
+
+ // Safe object type checking
+ function is(type, obj) {
+ return objectType(obj) === type;
+ }
+
+ // Based on Java's String.hashCode, a simple but not
+ // rigorously collision resistant hashing function
+ function generateHash(module, testName) {
+ var str = module + "\x1C" + testName;
+ var hash = 0;
+
+ for (var i = 0; i < str.length; i++) {
+ hash = (hash << 5) - hash + str.charCodeAt(i);
+ hash |= 0;
+ }
+
+ // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+ // strictly necessary but increases user understanding that the id is a SHA-like hash
+ var hex = (0x100000000 + hash).toString(16);
+ if (hex.length < 8) {
+ hex = "0000000" + hex;
+ }
+
+ return hex.slice(-8);
+ }
+
+ // Test for equality any JavaScript type.
+ // Authors: Philippe Rathé , David Chan
+ var equiv = (function () {
+
+ // Value pairs queued for comparison. Used for breadth-first processing order, recursion
+ // detection and avoiding repeated comparison (see below for details).
+ // Elements are { a: val, b: val }.
+ var pairs = [];
+
+ var getProto = Object.getPrototypeOf || function (obj) {
+ return obj.__proto__;
+ };
+
+ function useStrictEquality(a, b) {
+
+ // This only gets called if a and b are not strict equal, and is used to compare on
+ // the primitive values inside object wrappers. For example:
+ // `var i = 1;`
+ // `var j = new Number(1);`
+ // Neither a nor b can be null, as a !== b and they have the same type.
+ if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") {
+ a = a.valueOf();
+ }
+ if ((typeof b === "undefined" ? "undefined" : _typeof(b)) === "object") {
+ b = b.valueOf();
+ }
+
+ return a === b;
+ }
+
+ function compareConstructors(a, b) {
+ var protoA = getProto(a);
+ var protoB = getProto(b);
+
+ // Comparing constructors is more strict than using `instanceof`
+ if (a.constructor === b.constructor) {
+ return true;
+ }
+
+ // Ref #851
+ // If the obj prototype descends from a null constructor, treat it
+ // as a null prototype.
+ if (protoA && protoA.constructor === null) {
+ protoA = null;
+ }
+ if (protoB && protoB.constructor === null) {
+ protoB = null;
+ }
+
+ // Allow objects with no prototype to be equivalent to
+ // objects with Object as their constructor.
+ if (protoA === null && protoB === Object.prototype || protoB === null && protoA === Object.prototype) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function getRegExpFlags(regexp) {
+ return "flags" in regexp ? regexp.flags : regexp.toString().match(/[gimuy]*$/)[0];
+ }
+
+ function isContainer(val) {
+ return ["object", "array", "map", "set"].indexOf(objectType(val)) !== -1;
+ }
+
+ function breadthFirstCompareChild(a, b) {
+
+ // If a is a container not reference-equal to b, postpone the comparison to the
+ // end of the pairs queue -- unless (a, b) has been seen before, in which case skip
+ // over the pair.
+ if (a === b) {
+ return true;
+ }
+ if (!isContainer(a)) {
+ return typeEquiv(a, b);
+ }
+ if (pairs.every(function (pair) {
+ return pair.a !== a || pair.b !== b;
+ })) {
+
+ // Not yet started comparing this pair
+ pairs.push({ a: a, b: b });
+ }
+ return true;
+ }
+
+ var callbacks = {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+ "symbol": useStrictEquality,
+ "date": useStrictEquality,
+
+ "nan": function nan() {
+ return true;
+ },
+
+ "regexp": function regexp(a, b) {
+ return a.source === b.source &&
+
+ // Include flags in the comparison
+ getRegExpFlags(a) === getRegExpFlags(b);
+ },
+
+ // abort (identical references / instance methods were skipped earlier)
+ "function": function _function() {
+ return false;
+ },
+
+ "array": function array(a, b) {
+ var i, len;
+
+ len = a.length;
+ if (len !== b.length) {
+
+ // Safe and faster
+ return false;
+ }
+
+ for (i = 0; i < len; i++) {
+
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ // Define sets a and b to be equivalent if for each element aVal in a, there
+ // is some element bVal in b such that aVal and bVal are equivalent. Element
+ // repetitions are not counted, so these are equivalent:
+ // a = new Set( [ {}, [], [] ] );
+ // b = new Set( [ {}, {}, [] ] );
+ "set": function set$$1(a, b) {
+ var innerEq,
+ outerEq = true;
+
+ if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) element to two equivalent sets can
+ // make them non-equivalent.
+ return false;
+ }
+
+ a.forEach(function (aVal) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Set is unused)
+ if (!outerEq) {
+ return;
+ }
+
+ innerEq = false;
+
+ b.forEach(function (bVal) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
+ if (innerEquiv(bVal, aVal)) {
+ innerEq = true;
+ }
+
+ // Replace the global pairs list
+ pairs = parentPairs;
+ });
+
+ if (!innerEq) {
+ outerEq = false;
+ }
+ });
+
+ return outerEq;
+ },
+
+ // Define maps a and b to be equivalent if for each key-value pair (aKey, aVal)
+ // in a, there is some key-value pair (bKey, bVal) in b such that
+ // [ aKey, aVal ] and [ bKey, bVal ] are equivalent. Key repetitions are not
+ // counted, so these are equivalent:
+ // a = new Map( [ [ {}, 1 ], [ {}, 1 ], [ [], 1 ] ] );
+ // b = new Map( [ [ {}, 1 ], [ [], 1 ], [ [], 1 ] ] );
+ "map": function map(a, b) {
+ var innerEq,
+ outerEq = true;
+
+ if (a.size !== b.size) {
+
+ // This optimization has certain quirks because of the lack of
+ // repetition counting. For instance, adding the same
+ // (reference-identical) key-value pair to two equivalent maps
+ // can make them non-equivalent.
+ return false;
+ }
+
+ a.forEach(function (aVal, aKey) {
+
+ // Short-circuit if the result is already known. (Using for...of
+ // with a break clause would be cleaner here, but it would cause
+ // a syntax error on older Javascript implementations even if
+ // Map is unused)
+ if (!outerEq) {
+ return;
+ }
+
+ innerEq = false;
+
+ b.forEach(function (bVal, bKey) {
+ var parentPairs;
+
+ // Likewise, short-circuit if the result is already known
+ if (innerEq) {
+ return;
+ }
+
+ // Swap out the global pairs list, as the nested call to
+ // innerEquiv will clobber its contents
+ parentPairs = pairs;
+ if (innerEquiv([bVal, bKey], [aVal, aKey])) {
+ innerEq = true;
+ }
+
+ // Replace the global pairs list
+ pairs = parentPairs;
+ });
+
+ if (!innerEq) {
+ outerEq = false;
+ }
+ });
+
+ return outerEq;
+ },
+
+ "object": function object(a, b) {
+ var i,
+ aProperties = [],
+ bProperties = [];
+
+ if (compareConstructors(a, b) === false) {
+ return false;
+ }
+
+ // Be strict: don't ensure hasOwnProperty and go deep
+ for (i in a) {
+
+ // Collect a's properties
+ aProperties.push(i);
+
+ // Skip OOP methods that look the same
+ if (a.constructor !== Object && typeof a.constructor !== "undefined" && typeof a[i] === "function" && typeof b[i] === "function" && a[i].toString() === b[i].toString()) {
+ continue;
+ }
+
+ // Compare non-containers; queue non-reference-equal containers
+ if (!breadthFirstCompareChild(a[i], b[i])) {
+ return false;
+ }
+ }
+
+ for (i in b) {
+
+ // Collect b's properties
+ bProperties.push(i);
+ }
+
+ // Ensures identical properties name
+ return typeEquiv(aProperties.sort(), bProperties.sort());
+ }
+ };
+
+ function typeEquiv(a, b) {
+ var type = objectType(a);
+
+ // Callbacks for containers will append to the pairs queue to achieve breadth-first
+ // search order. The pairs queue is also used to avoid reprocessing any pair of
+ // containers that are reference-equal to a previously visited pair (a special case
+ // this being recursion detection).
+ //
+ // Because of this approach, once typeEquiv returns a false value, it should not be
+ // called again without clearing the pair queue else it may wrongly report a visited
+ // pair as being equivalent.
+ return objectType(b) === type && callbacks[type](a, b);
+ }
+
+ function innerEquiv(a, b) {
+ var i, pair;
+
+ // We're done when there's nothing more to compare
+ if (arguments.length < 2) {
+ return true;
+ }
+
+ // Clear the global pair queue and add the top-level values being compared
+ pairs = [{ a: a, b: b }];
+
+ for (i = 0; i < pairs.length; i++) {
+ pair = pairs[i];
+
+ // Perform type-specific comparison on any pairs that are not strictly
+ // equal. For container types, that comparison will postpone comparison
+ // of any sub-container pair to the end of the pair queue. This gives
+ // breadth-first search order. It also avoids the reprocessing of
+ // reference-equal siblings, cousins etc, which can have a significant speed
+ // impact when comparing a container of small objects each of which has a
+ // reference to the same (singleton) large object.
+ if (pair.a !== pair.b && !typeEquiv(pair.a, pair.b)) {
+ return false;
+ }
+ }
+
+ // ...across all consecutive argument pairs
+ return arguments.length === 2 || innerEquiv.apply(this, [].slice.call(arguments, 1));
+ }
+
+ return function () {
+ var result = innerEquiv.apply(undefined, arguments);
+
+ // Release any retained objects
+ pairs.length = 0;
+ return result;
+ };
+ })();
+
+ /**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+ var config = {
+
+ // The queue of tests to run
+ queue: [],
+
+ // Block until document ready
+ blocking: true,
+
+ // By default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // By default, modify document.title when suite is done
+ altertitle: true,
+
+ // HTML Reporter: collapse every test except the first failing test
+ // If false, all failing tests will be expanded
+ collapse: true,
+
+ // By default, scroll to top of the page when suite is done
+ scrolltop: true,
+
+ // Depth up-to which object will be dumped
+ maxDepth: 5,
+
+ // When enabled, all tests must call expect()
+ requireExpects: false,
+
+ // Placeholder for user-configurable form-exposed URL parameters
+ urlConfig: [],
+
+ // Set of all modules.
+ modules: [],
+
+ // The first unnamed module
+ currentModule: {
+ name: "",
+ tests: [],
+ childModules: [],
+ testsRun: 0,
+ unskippedTestsRun: 0,
+ hooks: {
+ before: [],
+ beforeEach: [],
+ afterEach: [],
+ after: []
+ }
+ },
+
+ callbacks: {},
+
+ // The storage module to use for reordering tests
+ storage: localSessionStorage
+ };
+
+ // take a predefined QUnit.config and extend the defaults
+ var globalConfig = window$1 && window$1.QUnit && window$1.QUnit.config;
+
+ // only extend the global config if there is no QUnit overload
+ if (window$1 && window$1.QUnit && !window$1.QUnit.version) {
+ extend(config, globalConfig);
+ }
+
+ // Push a loose unnamed module to the modules collection
+ config.modules.push(config.currentModule);
+
+ // Based on jsDump by Ariel Flesler
+ // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+ var dump = (function () {
+ function quote(str) {
+ return "\"" + str.toString().replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
+ }
+ function literal(o) {
+ return o + "";
+ }
+ function join(pre, arr, post) {
+ var s = dump.separator(),
+ base = dump.indent(),
+ inner = dump.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);
+
+ if (dump.maxDepth && dump.depth > dump.maxDepth) {
+ return "[object Array]";
+ }
+
+ this.up();
+ while (i--) {
+ ret[i] = this.parse(arr[i], undefined, stack);
+ }
+ this.down();
+ return join("[", ret, "]");
+ }
+
+ function isArray(obj) {
+ return (
+
+ //Native Arrays
+ toString.call(obj) === "[object Array]" ||
+
+ // NodeList objects
+ typeof obj.length === "number" && obj.item !== undefined && (obj.length ? obj.item(0) === obj[0] : obj.item(0) === null && obj[0] === undefined)
+ );
+ }
+
+ var reName = /^function (\w+)/,
+ dump = {
+
+ // The objType is used mostly internally, you can fix a (custom) type in advance
+ parse: function parse(obj, objType, stack) {
+ stack = stack || [];
+ var res,
+ parser,
+ parserType,
+ objIndex = stack.indexOf(obj);
+
+ if (objIndex !== -1) {
+ return "recursion(" + (objIndex - stack.length) + ")";
+ }
+
+ objType = objType || this.typeOf(obj);
+ parser = this.parsers[objType];
+ parserType = typeof parser === "undefined" ? "undefined" : _typeof(parser);
+
+ if (parserType === "function") {
+ stack.push(obj);
+ res = parser.call(this, obj, stack);
+ stack.pop();
+ return res;
+ }
+ return parserType === "string" ? parser : this.parsers.error;
+ },
+ typeOf: function typeOf(obj) {
+ var type;
+
+ if (obj === null) {
+ type = "null";
+ } else if (typeof obj === "undefined") {
+ type = "undefined";
+ } else if (is("regexp", obj)) {
+ type = "regexp";
+ } else if (is("date", obj)) {
+ type = "date";
+ } else if (is("function", obj)) {
+ type = "function";
+ } else if (obj.setInterval !== undefined && obj.document !== undefined && obj.nodeType === undefined) {
+ type = "window";
+ } else if (obj.nodeType === 9) {
+ type = "document";
+ } else if (obj.nodeType) {
+ type = "node";
+ } else if (isArray(obj)) {
+ type = "array";
+ } else if (obj.constructor === Error.prototype.constructor) {
+ type = "error";
+ } else {
+ type = typeof obj === "undefined" ? "undefined" : _typeof(obj);
+ }
+ return type;
+ },
+
+ separator: function separator() {
+ if (this.multiline) {
+ return this.HTML ? "
" : "\n";
+ } else {
+ return this.HTML ? " " : " ";
+ }
+ },
+
+ // Extra can be a number, shortcut for increasing-calling-decreasing
+ indent: function indent(extra) {
+ if (!this.multiline) {
+ return "";
+ }
+ var chr = this.indentChar;
+ if (this.HTML) {
+ chr = chr.replace(/\t/g, " ").replace(/ /g, " ");
+ }
+ return new Array(this.depth + (extra || 0)).join(chr);
+ },
+ up: function up(a) {
+ this.depth += a || 1;
+ },
+ down: function down(a) {
+ this.depth -= a || 1;
+ },
+ setParser: function setParser(name, parser) {
+ this.parsers[name] = parser;
+ },
+
+ // The next 3 are exposed so you can use them
+ quote: quote,
+ literal: literal,
+ join: join,
+ depth: 1,
+ maxDepth: config.maxDepth,
+
+ // This is the list of parsers, to modify them, use dump.setParser
+ parsers: {
+ window: "[Window]",
+ document: "[Document]",
+ error: function error(_error) {
+ return "Error(\"" + _error.message + "\")";
+ },
+ unknown: "[Unknown]",
+ "null": "null",
+ "undefined": "undefined",
+ "function": 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, dump.parse(fn, "functionArgs"), "){"].join("");
+ return join(ret, dump.parse(fn, "functionCode"), "}");
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function object(map, stack) {
+ var keys,
+ key,
+ val,
+ i,
+ nonEnumerableProperties,
+ ret = [];
+
+ if (dump.maxDepth && dump.depth > dump.maxDepth) {
+ return "[object Object]";
+ }
+
+ dump.up();
+ keys = [];
+ for (key in map) {
+ keys.push(key);
+ }
+
+ // Some properties are not always enumerable on Error objects.
+ nonEnumerableProperties = ["message", "name"];
+ for (i in nonEnumerableProperties) {
+ key = nonEnumerableProperties[i];
+ if (key in map && !inArray(key, keys)) {
+ keys.push(key);
+ }
+ }
+ keys.sort();
+ for (i = 0; i < keys.length; i++) {
+ key = keys[i];
+ val = map[key];
+ ret.push(dump.parse(key, "key") + ": " + dump.parse(val, undefined, stack));
+ }
+ dump.down();
+ return join("{", ret, "}");
+ },
+ node: function node(_node) {
+ var len,
+ i,
+ val,
+ open = dump.HTML ? "<" : "<",
+ close = dump.HTML ? ">" : ">",
+ 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 + "=" + dump.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 functionArgs(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 a html attribute value
+ attribute: quote,
+ string: quote,
+ date: quote,
+ regexp: literal,
+ number: literal,
+ "boolean": literal,
+ symbol: function symbol(sym) {
+ return sym.toString();
+ }
+ },
+
+ // 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 dump;
+ })();
+
+ var SuiteReport = function () {
+ function SuiteReport(name, parentSuite) {
+ classCallCheck(this, SuiteReport);
+
+ this.name = name;
+ this.fullName = parentSuite ? parentSuite.fullName.concat(name) : [];
+
+ this.tests = [];
+ this.childSuites = [];
+
+ if (parentSuite) {
+ parentSuite.pushChildSuite(this);
+ }
+ }
+
+ createClass(SuiteReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = performanceNow();
+
+ if (performance) {
+ var suiteLevel = this.fullName.length;
+ performance.mark("qunit_suite_" + suiteLevel + "_start");
+ }
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.start();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.start();
+ }),
+ testCounts: {
+ total: this.getTestCounts().total
+ }
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = performanceNow();
+
+ if (performance) {
+ var suiteLevel = this.fullName.length;
+ performance.mark("qunit_suite_" + suiteLevel + "_end");
+
+ var suiteName = this.fullName.join(" – ");
+
+ measure(suiteLevel === 0 ? "QUnit Test Run" : "QUnit Test Suite: " + suiteName, "qunit_suite_" + suiteLevel + "_start", "qunit_suite_" + suiteLevel + "_end");
+ }
+ }
+
+ return {
+ name: this.name,
+ fullName: this.fullName.slice(),
+ tests: this.tests.map(function (test) {
+ return test.end();
+ }),
+ childSuites: this.childSuites.map(function (suite) {
+ return suite.end();
+ }),
+ testCounts: this.getTestCounts(),
+ runtime: this.getRuntime(),
+ status: this.getStatus()
+ };
+ }
+ }, {
+ key: "pushChildSuite",
+ value: function pushChildSuite(suite) {
+ this.childSuites.push(suite);
+ }
+ }, {
+ key: "pushTest",
+ value: function pushTest(test) {
+ this.tests.push(test);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getTestCounts",
+ value: function getTestCounts() {
+ var counts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 };
+
+ counts = this.tests.reduce(function (counts, test) {
+ if (test.valid) {
+ counts[test.getStatus()]++;
+ counts.total++;
+ }
+
+ return counts;
+ }, counts);
+
+ return this.childSuites.reduce(function (counts, suite) {
+ return suite.getTestCounts(counts);
+ }, counts);
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ var _getTestCounts = this.getTestCounts(),
+ total = _getTestCounts.total,
+ failed = _getTestCounts.failed,
+ skipped = _getTestCounts.skipped,
+ todo = _getTestCounts.todo;
+
+ if (failed) {
+ return "failed";
+ } else {
+ if (skipped === total) {
+ return "skipped";
+ } else if (todo === total) {
+ return "todo";
+ } else {
+ return "passed";
+ }
+ }
+ }
+ }]);
+ return SuiteReport;
+ }();
+
+ var focused = false;
+
+ var moduleStack = [];
+
+ function createModule(name, testEnvironment, modifiers) {
+ var parentModule = moduleStack.length ? moduleStack.slice(-1)[0] : null;
+ var moduleName = parentModule !== null ? [parentModule.name, name].join(" > ") : name;
+ var parentSuite = parentModule ? parentModule.suiteReport : globalSuite;
+
+ var skip = parentModule !== null && parentModule.skip || modifiers.skip;
+ var todo = parentModule !== null && parentModule.todo || modifiers.todo;
+
+ var module = {
+ name: moduleName,
+ parentModule: parentModule,
+ tests: [],
+ moduleId: generateHash(moduleName),
+ testsRun: 0,
+ unskippedTestsRun: 0,
+ childModules: [],
+ suiteReport: new SuiteReport(name, parentSuite),
+
+ // Pass along `skip` and `todo` properties from parent module, in case
+ // there is one, to childs. And use own otherwise.
+ // This property will be used to mark own tests and tests of child suites
+ // as either `skipped` or `todo`.
+ skip: skip,
+ todo: skip ? false : todo
+ };
+
+ var env = {};
+ if (parentModule) {
+ parentModule.childModules.push(module);
+ extend(env, parentModule.testEnvironment);
+ }
+ extend(env, testEnvironment);
+ module.testEnvironment = env;
+
+ config.modules.push(module);
+ return module;
+ }
+
+ function processModule(name, options, executeNow) {
+ var modifiers = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
+
+ if (objectType(options) === "function") {
+ executeNow = options;
+ options = undefined;
+ }
+
+ var module = createModule(name, options, modifiers);
+
+ // Move any hooks to a 'hooks' object
+ var testEnvironment = module.testEnvironment;
+ var hooks = module.hooks = {};
+
+ setHookFromEnvironment(hooks, testEnvironment, "before");
+ setHookFromEnvironment(hooks, testEnvironment, "beforeEach");
+ setHookFromEnvironment(hooks, testEnvironment, "afterEach");
+ setHookFromEnvironment(hooks, testEnvironment, "after");
+
+ var moduleFns = {
+ before: setHookFunction(module, "before"),
+ beforeEach: setHookFunction(module, "beforeEach"),
+ afterEach: setHookFunction(module, "afterEach"),
+ after: setHookFunction(module, "after")
+ };
+
+ var currentModule = config.currentModule;
+ if (objectType(executeNow) === "function") {
+ moduleStack.push(module);
+ config.currentModule = module;
+ executeNow.call(module.testEnvironment, moduleFns);
+ moduleStack.pop();
+ module = module.parentModule || currentModule;
+ }
+
+ config.currentModule = module;
+
+ function setHookFromEnvironment(hooks, environment, name) {
+ var potentialHook = environment[name];
+ hooks[name] = typeof potentialHook === "function" ? [potentialHook] : [];
+ delete environment[name];
+ }
+
+ function setHookFunction(module, hookName) {
+ return function setHook(callback) {
+ module.hooks[hookName].push(callback);
+ };
+ }
+ }
+
+ function module$1(name, options, executeNow) {
+ if (focused) {
+ return;
+ }
+
+ processModule(name, options, executeNow);
+ }
+
+ module$1.only = function () {
+ if (focused) {
+ return;
+ }
+
+ config.modules.length = 0;
+ config.queue.length = 0;
+
+ module$1.apply(undefined, arguments);
+
+ focused = true;
+ };
+
+ module$1.skip = function (name, options, executeNow) {
+ if (focused) {
+ return;
+ }
+
+ processModule(name, options, executeNow, { skip: true });
+ };
+
+ module$1.todo = function (name, options, executeNow) {
+ if (focused) {
+ return;
+ }
+
+ processModule(name, options, executeNow, { todo: true });
+ };
+
+ var LISTENERS = Object.create(null);
+ var SUPPORTED_EVENTS = ["runStart", "suiteStart", "testStart", "assertion", "testEnd", "suiteEnd", "runEnd"];
+
+ /**
+ * Emits an event with the specified data to all currently registered listeners.
+ * Callbacks will fire in the order in which they are registered (FIFO). This
+ * function is not exposed publicly; it is used by QUnit internals to emit
+ * logging events.
+ *
+ * @private
+ * @method emit
+ * @param {String} eventName
+ * @param {Object} data
+ * @return {Void}
+ */
+ function emit(eventName, data) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when emitting an event");
+ }
+
+ // Clone the callbacks in case one of them registers a new callback
+ var originalCallbacks = LISTENERS[eventName];
+ var callbacks = originalCallbacks ? [].concat(toConsumableArray(originalCallbacks)) : [];
+
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i](data);
+ }
+ }
+
+ /**
+ * Registers a callback as a listener to the specified event.
+ *
+ * @public
+ * @method on
+ * @param {String} eventName
+ * @param {Function} callback
+ * @return {Void}
+ */
+ function on(eventName, callback) {
+ if (objectType(eventName) !== "string") {
+ throw new TypeError("eventName must be a string when registering a listener");
+ } else if (!inArray(eventName, SUPPORTED_EVENTS)) {
+ var events = SUPPORTED_EVENTS.join(", ");
+ throw new Error("\"" + eventName + "\" is not a valid event; must be one of: " + events + ".");
+ } else if (objectType(callback) !== "function") {
+ throw new TypeError("callback must be a function when registering a listener");
+ }
+
+ if (!LISTENERS[eventName]) {
+ LISTENERS[eventName] = [];
+ }
+
+ // Don't register the same callback more than once
+ if (!inArray(callback, LISTENERS[eventName])) {
+ LISTENERS[eventName].push(callback);
+ }
+ }
+
+ function objectOrFunction(x) {
+ var type = typeof x === 'undefined' ? 'undefined' : _typeof(x);
+ return x !== null && (type === 'object' || type === 'function');
+ }
+
+ function isFunction(x) {
+ return typeof x === 'function';
+ }
+
+
+
+ var _isArray = void 0;
+ if (Array.isArray) {
+ _isArray = Array.isArray;
+ } else {
+ _isArray = function _isArray(x) {
+ return Object.prototype.toString.call(x) === '[object Array]';
+ };
+ }
+
+ var isArray = _isArray;
+
+ var len = 0;
+ var vertxNext = void 0;
+ var customSchedulerFn = void 0;
+
+ var asap = function asap(callback, arg) {
+ queue[len] = callback;
+ queue[len + 1] = arg;
+ len += 2;
+ if (len === 2) {
+ // If len is 2, that means that we need to schedule an async flush.
+ // If additional callbacks are queued before the queue is flushed, they
+ // will be processed by this flush that we are scheduling.
+ if (customSchedulerFn) {
+ customSchedulerFn(flush);
+ } else {
+ scheduleFlush();
+ }
+ }
+ };
+
+ function setScheduler(scheduleFn) {
+ customSchedulerFn = scheduleFn;
+ }
+
+ function setAsap(asapFn) {
+ asap = asapFn;
+ }
+
+ var browserWindow = typeof window !== 'undefined' ? window : undefined;
+ var browserGlobal = browserWindow || {};
+ var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
+
+ // test for web worker but not in IE10
+ var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
+
+ // node
+ function useNextTick() {
+ // node version 0.10.x displays a deprecation warning when nextTick is used recursively
+ // see https://github.com/cujojs/when/issues/410 for details
+ return function () {
+ return process.nextTick(flush);
+ };
+ }
+
+ // vertx
+ function useVertxTimer() {
+ if (typeof vertxNext !== 'undefined') {
+ return function () {
+ vertxNext(flush);
+ };
+ }
+
+ return useSetTimeout();
+ }
+
+ function useMutationObserver() {
+ var iterations = 0;
+ var observer = new BrowserMutationObserver(flush);
+ var node = document.createTextNode('');
+ observer.observe(node, { characterData: true });
+
+ return function () {
+ node.data = iterations = ++iterations % 2;
+ };
+ }
+
+ // web worker
+ function useMessageChannel() {
+ var channel = new MessageChannel();
+ channel.port1.onmessage = flush;
+ return function () {
+ return channel.port2.postMessage(0);
+ };
+ }
+
+ function useSetTimeout() {
+ // Store setTimeout reference so es6-promise will be unaffected by
+ // other code modifying setTimeout (like sinon.useFakeTimers())
+ var globalSetTimeout = setTimeout;
+ return function () {
+ return globalSetTimeout(flush, 1);
+ };
+ }
+
+ var queue = new Array(1000);
+ function flush() {
+ for (var i = 0; i < len; i += 2) {
+ var callback = queue[i];
+ var arg = queue[i + 1];
+
+ callback(arg);
+
+ queue[i] = undefined;
+ queue[i + 1] = undefined;
+ }
+
+ len = 0;
+ }
+
+ function attemptVertx() {
+ try {
+ var vertx = Function('return this')().require('vertx');
+ vertxNext = vertx.runOnLoop || vertx.runOnContext;
+ return useVertxTimer();
+ } catch (e) {
+ return useSetTimeout();
+ }
+ }
+
+ var scheduleFlush = void 0;
+ // Decide what async method to use to triggering processing of queued callbacks:
+ if (isNode) {
+ scheduleFlush = useNextTick();
+ } else if (BrowserMutationObserver) {
+ scheduleFlush = useMutationObserver();
+ } else if (isWorker) {
+ scheduleFlush = useMessageChannel();
+ } else if (browserWindow === undefined && typeof require === 'function') {
+ scheduleFlush = attemptVertx();
+ } else {
+ scheduleFlush = useSetTimeout();
+ }
+
+ function then(onFulfillment, onRejection) {
+ var parent = this;
+
+ var child = new this.constructor(noop);
+
+ if (child[PROMISE_ID] === undefined) {
+ makePromise(child);
+ }
+
+ var _state = parent._state;
+
+
+ if (_state) {
+ var callback = arguments[_state - 1];
+ asap(function () {
+ return invokeCallback(_state, child, callback, parent._result);
+ });
+ } else {
+ subscribe(parent, child, onFulfillment, onRejection);
+ }
+
+ return child;
+ }
+
+ /**
+ `Promise.resolve` returns a promise that will become resolved with the
+ passed `value`. It is shorthand for the following:
+
+ ```javascript
+ let promise = new Promise(function(resolve, reject){
+ resolve(1);
+ });
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ let promise = Promise.resolve(1);
+
+ promise.then(function(value){
+ // value === 1
+ });
+ ```
+
+ @method resolve
+ @static
+ @param {Any} value value that the returned promise will be resolved with
+ Useful for tooling.
+ @return {Promise} a promise that will become fulfilled with the given
+ `value`
+ */
+ function resolve$1(object) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ if (object && (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && object.constructor === Constructor) {
+ return object;
+ }
+
+ var promise = new Constructor(noop);
+ resolve(promise, object);
+ return promise;
+ }
+
+ var PROMISE_ID = Math.random().toString(36).substring(2);
+
+ function noop() {}
+
+ var PENDING = void 0;
+ var FULFILLED = 1;
+ var REJECTED = 2;
+
+ var TRY_CATCH_ERROR = { error: null };
+
+ function selfFulfillment() {
+ return new TypeError("You cannot resolve a promise with itself");
+ }
+
+ function cannotReturnOwn() {
+ return new TypeError('A promises callback cannot return that same promise.');
+ }
+
+ function getThen(promise) {
+ try {
+ return promise.then;
+ } catch (error) {
+ TRY_CATCH_ERROR.error = error;
+ return TRY_CATCH_ERROR;
+ }
+ }
+
+ function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
+ try {
+ then$$1.call(value, fulfillmentHandler, rejectionHandler);
+ } catch (e) {
+ return e;
+ }
+ }
+
+ function handleForeignThenable(promise, thenable, then$$1) {
+ asap(function (promise) {
+ var sealed = false;
+ var error = tryThen(then$$1, thenable, function (value) {
+ if (sealed) {
+ return;
+ }
+ sealed = true;
+ if (thenable !== value) {
+ resolve(promise, value);
+ } else {
+ fulfill(promise, value);
+ }
+ }, function (reason) {
+ if (sealed) {
+ return;
+ }
+ sealed = true;
+
+ reject(promise, reason);
+ }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+ if (!sealed && error) {
+ sealed = true;
+ reject(promise, error);
+ }
+ }, promise);
+ }
+
+ function handleOwnThenable(promise, thenable) {
+ if (thenable._state === FULFILLED) {
+ fulfill(promise, thenable._result);
+ } else if (thenable._state === REJECTED) {
+ reject(promise, thenable._result);
+ } else {
+ subscribe(thenable, undefined, function (value) {
+ return resolve(promise, value);
+ }, function (reason) {
+ return reject(promise, reason);
+ });
+ }
+ }
+
+ function handleMaybeThenable(promise, maybeThenable, then$$1) {
+ if (maybeThenable.constructor === promise.constructor && then$$1 === then && maybeThenable.constructor.resolve === resolve$1) {
+ handleOwnThenable(promise, maybeThenable);
+ } else {
+ if (then$$1 === TRY_CATCH_ERROR) {
+ reject(promise, TRY_CATCH_ERROR.error);
+ TRY_CATCH_ERROR.error = null;
+ } else if (then$$1 === undefined) {
+ fulfill(promise, maybeThenable);
+ } else if (isFunction(then$$1)) {
+ handleForeignThenable(promise, maybeThenable, then$$1);
+ } else {
+ fulfill(promise, maybeThenable);
+ }
+ }
+ }
+
+ function resolve(promise, value) {
+ if (promise === value) {
+ reject(promise, selfFulfillment());
+ } else if (objectOrFunction(value)) {
+ handleMaybeThenable(promise, value, getThen(value));
+ } else {
+ fulfill(promise, value);
+ }
+ }
+
+ function publishRejection(promise) {
+ if (promise._onerror) {
+ promise._onerror(promise._result);
+ }
+
+ publish(promise);
+ }
+
+ function fulfill(promise, value) {
+ if (promise._state !== PENDING) {
+ return;
+ }
+
+ promise._result = value;
+ promise._state = FULFILLED;
+
+ if (promise._subscribers.length !== 0) {
+ asap(publish, promise);
+ }
+ }
+
+ function reject(promise, reason) {
+ if (promise._state !== PENDING) {
+ return;
+ }
+ promise._state = REJECTED;
+ promise._result = reason;
+
+ asap(publishRejection, promise);
+ }
+
+ function subscribe(parent, child, onFulfillment, onRejection) {
+ var _subscribers = parent._subscribers;
+ var length = _subscribers.length;
+
+
+ parent._onerror = null;
+
+ _subscribers[length] = child;
+ _subscribers[length + FULFILLED] = onFulfillment;
+ _subscribers[length + REJECTED] = onRejection;
+
+ if (length === 0 && parent._state) {
+ asap(publish, parent);
+ }
+ }
+
+ function publish(promise) {
+ var subscribers = promise._subscribers;
+ var settled = promise._state;
+
+ if (subscribers.length === 0) {
+ return;
+ }
+
+ var child = void 0,
+ callback = void 0,
+ detail = promise._result;
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ if (child) {
+ invokeCallback(settled, child, callback, detail);
+ } else {
+ callback(detail);
+ }
+ }
+
+ promise._subscribers.length = 0;
+ }
+
+ function tryCatch(callback, detail) {
+ try {
+ return callback(detail);
+ } catch (e) {
+ TRY_CATCH_ERROR.error = e;
+ return TRY_CATCH_ERROR;
+ }
+ }
+
+ function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
+ value = void 0,
+ error = void 0,
+ succeeded = void 0,
+ failed = void 0;
+
+ if (hasCallback) {
+ value = tryCatch(callback, detail);
+
+ if (value === TRY_CATCH_ERROR) {
+ failed = true;
+ error = value.error;
+ value.error = null;
+ } else {
+ succeeded = true;
+ }
+
+ if (promise === value) {
+ reject(promise, cannotReturnOwn());
+ return;
+ }
+ } else {
+ value = detail;
+ succeeded = true;
+ }
+
+ if (promise._state !== PENDING) {
+ // noop
+ } else if (hasCallback && succeeded) {
+ resolve(promise, value);
+ } else if (failed) {
+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ fulfill(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
+ }
+ }
+
+ function initializePromise(promise, resolver) {
+ try {
+ resolver(function resolvePromise(value) {
+ resolve(promise, value);
+ }, function rejectPromise(reason) {
+ reject(promise, reason);
+ });
+ } catch (e) {
+ reject(promise, e);
+ }
+ }
+
+ var id = 0;
+ function nextId() {
+ return id++;
+ }
+
+ function makePromise(promise) {
+ promise[PROMISE_ID] = id++;
+ promise._state = undefined;
+ promise._result = undefined;
+ promise._subscribers = [];
+ }
+
+ function validationError() {
+ return new Error('Array Methods must be provided an Array');
+ }
+
+ var Enumerator = function () {
+ function Enumerator(Constructor, input) {
+ classCallCheck(this, Enumerator);
+
+ this._instanceConstructor = Constructor;
+ this.promise = new Constructor(noop);
+
+ if (!this.promise[PROMISE_ID]) {
+ makePromise(this.promise);
+ }
+
+ if (isArray(input)) {
+ this.length = input.length;
+ this._remaining = input.length;
+
+ this._result = new Array(this.length);
+
+ if (this.length === 0) {
+ fulfill(this.promise, this._result);
+ } else {
+ this.length = this.length || 0;
+ this._enumerate(input);
+ if (this._remaining === 0) {
+ fulfill(this.promise, this._result);
+ }
+ }
+ } else {
+ reject(this.promise, validationError());
+ }
+ }
+
+ createClass(Enumerator, [{
+ key: '_enumerate',
+ value: function _enumerate(input) {
+ for (var i = 0; this._state === PENDING && i < input.length; i++) {
+ this._eachEntry(input[i], i);
+ }
+ }
+ }, {
+ key: '_eachEntry',
+ value: function _eachEntry(entry, i) {
+ var c = this._instanceConstructor;
+ var resolve$$1 = c.resolve;
+
+
+ if (resolve$$1 === resolve$1) {
+ var _then = getThen(entry);
+
+ if (_then === then && entry._state !== PENDING) {
+ this._settledAt(entry._state, i, entry._result);
+ } else if (typeof _then !== 'function') {
+ this._remaining--;
+ this._result[i] = entry;
+ } else if (c === Promise$2) {
+ var promise = new c(noop);
+ handleMaybeThenable(promise, entry, _then);
+ this._willSettleAt(promise, i);
+ } else {
+ this._willSettleAt(new c(function (resolve$$1) {
+ return resolve$$1(entry);
+ }), i);
+ }
+ } else {
+ this._willSettleAt(resolve$$1(entry), i);
+ }
+ }
+ }, {
+ key: '_settledAt',
+ value: function _settledAt(state, i, value) {
+ var promise = this.promise;
+
+
+ if (promise._state === PENDING) {
+ this._remaining--;
+
+ if (state === REJECTED) {
+ reject(promise, value);
+ } else {
+ this._result[i] = value;
+ }
+ }
+
+ if (this._remaining === 0) {
+ fulfill(promise, this._result);
+ }
+ }
+ }, {
+ key: '_willSettleAt',
+ value: function _willSettleAt(promise, i) {
+ var enumerator = this;
+
+ subscribe(promise, undefined, function (value) {
+ return enumerator._settledAt(FULFILLED, i, value);
+ }, function (reason) {
+ return enumerator._settledAt(REJECTED, i, reason);
+ });
+ }
+ }]);
+ return Enumerator;
+ }();
+
+ /**
+ `Promise.all` accepts an array of promises, and returns a new promise which
+ is fulfilled with an array of fulfillment values for the passed promises, or
+ rejected with the reason of the first passed promise to be rejected. It casts all
+ elements of the passed iterable to promises as it runs this algorithm.
+
+ Example:
+
+ ```javascript
+ let promise1 = resolve(1);
+ let promise2 = resolve(2);
+ let promise3 = resolve(3);
+ let promises = [ promise1, promise2, promise3 ];
+
+ Promise.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ let promise1 = resolve(1);
+ let promise2 = reject(new Error("2"));
+ let promise3 = reject(new Error("3"));
+ let promises = [ promise1, promise2, promise3 ];
+
+ Promise.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @static
+ @param {Array} entries array of promises
+ @param {String} label optional string for labeling the promise.
+ Useful for tooling.
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ @static
+ */
+ function all(entries) {
+ return new Enumerator(this, entries).promise;
+ }
+
+ /**
+ `Promise.race` returns a new promise which is settled in the same way as the
+ first passed promise to settle.
+
+ Example:
+
+ ```javascript
+ let promise1 = new Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve('promise 1');
+ }, 200);
+ });
+
+ let promise2 = new Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve('promise 2');
+ }, 100);
+ });
+
+ Promise.race([promise1, promise2]).then(function(result){
+ // result === 'promise 2' because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `Promise.race` is deterministic in that only the state of the first
+ settled promise matters. For example, even if other promises given to the
+ `promises` array argument are resolved, but the first settled promise has
+ become rejected before the other promises became fulfilled, the returned
+ promise will become rejected:
+
+ ```javascript
+ let promise1 = new Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve('promise 1');
+ }, 200);
+ });
+
+ let promise2 = new Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error('promise 2'));
+ }, 100);
+ });
+
+ Promise.race([promise1, promise2]).then(function(result){
+ // Code here never runs
+ }, function(reason){
+ // reason.message === 'promise 2' because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ An example real-world use case is implementing timeouts:
+
+ ```javascript
+ Promise.race([ajax('foo.json'), timeout(5000)])
+ ```
+
+ @method race
+ @static
+ @param {Array} promises array of promises to observe
+ Useful for tooling.
+ @return {Promise} a promise which settles in the same way as the first passed
+ promise to settle.
+ */
+ function race(entries) {
+ /*jshint validthis:true */
+ var Constructor = this;
+
+ if (!isArray(entries)) {
+ return new Constructor(function (_, reject) {
+ return reject(new TypeError('You must pass an array to race.'));
+ });
+ } else {
+ return new Constructor(function (resolve, reject) {
+ var length = entries.length;
+ for (var i = 0; i < length; i++) {
+ Constructor.resolve(entries[i]).then(resolve, reject);
+ }
+ });
+ }
+ }
+
+ /**
+ `Promise.reject` returns a promise rejected with the passed `reason`.
+ It is shorthand for the following:
+
+ ```javascript
+ let promise = new Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ let promise = Promise.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @static
+ @param {Any} reason value that the returned promise will be rejected with.
+ Useful for tooling.
+ @return {Promise} a promise rejected with the given `reason`.
+ */
+ function reject$1(reason) {
+ /*jshint validthis:true */
+ var Constructor = this;
+ var promise = new Constructor(noop);
+ reject(promise, reason);
+ return promise;
+ }
+
+ function needsResolver() {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+ }
+
+ function needsNew() {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+ }
+
+ /**
+ Promise objects represent the eventual result of an asynchronous operation. The
+ primary way of interacting with a promise is through its `then` method, which
+ registers callbacks to receive either a promise's eventual value or the reason
+ why the promise cannot be fulfilled.
+
+ Terminology
+ -----------
+
+ - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+ - `thenable` is an object or function that defines a `then` method.
+ - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+ - `exception` is a value that is thrown using the throw statement.
+ - `reason` is a value that indicates why a promise was rejected.
+ - `settled` the final resting state of a promise, fulfilled or rejected.
+
+ A promise can be in one of three states: pending, fulfilled, or rejected.
+
+ Promises that are fulfilled have a fulfillment value and are in the fulfilled
+ state. Promises that are rejected have a rejection reason and are in the
+ rejected state. A fulfillment value is never a thenable.
+
+ Promises can also be said to *resolve* a value. If this value is also a
+ promise, then the original promise's settled state will match the value's
+ settled state. So a promise that *resolves* a promise that rejects will
+ itself reject, and a promise that *resolves* a promise that fulfills will
+ itself fulfill.
+
+
+ Basic Usage:
+ ------------
+
+ ```js
+ let promise = new Promise(function(resolve, reject) {
+ // on success
+ resolve(value);
+
+ // on failure
+ reject(reason);
+ });
+
+ promise.then(function(value) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Advanced Usage:
+ ---------------
+
+ Promises shine when abstracting away asynchronous interactions such as
+ `XMLHttpRequest`s.
+
+ ```js
+ function getJSON(url) {
+ return new Promise(function(resolve, reject){
+ let xhr = new XMLHttpRequest();
+
+ xhr.open('GET', url);
+ xhr.onreadystatechange = handler;
+ xhr.responseType = 'json';
+ xhr.setRequestHeader('Accept', 'application/json');
+ xhr.send();
+
+ function handler() {
+ if (this.readyState === this.DONE) {
+ if (this.status === 200) {
+ resolve(this.response);
+ } else {
+ reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+ }
+ }
+ };
+ });
+ }
+
+ getJSON('/posts.json').then(function(json) {
+ // on fulfillment
+ }, function(reason) {
+ // on rejection
+ });
+ ```
+
+ Unlike callbacks, promises are great composable primitives.
+
+ ```js
+ Promise.all([
+ getJSON('/posts'),
+ getJSON('/comments')
+ ]).then(function(values){
+ values[0] // => postsJSON
+ values[1] // => commentsJSON
+
+ return values;
+ });
+ ```
+
+ @class Promise
+ @param {Function} resolver
+ Useful for tooling.
+ @constructor
+ */
+
+ var Promise$2 = function () {
+ function Promise(resolver) {
+ classCallCheck(this, Promise);
+
+ this[PROMISE_ID] = nextId();
+ this._result = this._state = undefined;
+ this._subscribers = [];
+
+ if (noop !== resolver) {
+ typeof resolver !== 'function' && needsResolver();
+ this instanceof Promise ? initializePromise(this, resolver) : needsNew();
+ }
+ }
+
+ /**
+ The primary way of interacting with a promise is through its `then` method,
+ which registers callbacks to receive either a promise's eventual value or the
+ reason why the promise cannot be fulfilled.
+ ```js
+ findUser().then(function(user){
+ // user is available
+ }, function(reason){
+ // user is unavailable, and you are given the reason why
+ });
+ ```
+ Chaining
+ --------
+ The return value of `then` is itself a promise. This second, 'downstream'
+ promise is resolved with the return value of the first promise's fulfillment
+ or rejection handler, or rejected if the handler throws an exception.
+ ```js
+ findUser().then(function (user) {
+ return user.name;
+ }, function (reason) {
+ return 'default name';
+ }).then(function (userName) {
+ // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+ // will be `'default name'`
+ });
+ findUser().then(function (user) {
+ throw new Error('Found user, but still unhappy');
+ }, function (reason) {
+ throw new Error('`findUser` rejected and we're unhappy');
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+ // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+ });
+ ```
+ If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+ ```js
+ findUser().then(function (user) {
+ throw new PedagogicalException('Upstream error');
+ }).then(function (value) {
+ // never reached
+ }).then(function (value) {
+ // never reached
+ }, function (reason) {
+ // The `PedgagocialException` is propagated all the way down to here
+ });
+ ```
+ Assimilation
+ ------------
+ Sometimes the value you want to propagate to a downstream promise can only be
+ retrieved asynchronously. This can be achieved by returning a promise in the
+ fulfillment or rejection handler. The downstream promise will then be pending
+ until the returned promise is settled. This is called *assimilation*.
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // The user's comments are now available
+ });
+ ```
+ If the assimliated promise rejects, then the downstream promise will also reject.
+ ```js
+ findUser().then(function (user) {
+ return findCommentsByAuthor(user);
+ }).then(function (comments) {
+ // If `findCommentsByAuthor` fulfills, we'll have the value here
+ }, function (reason) {
+ // If `findCommentsByAuthor` rejects, we'll have the reason here
+ });
+ ```
+ Simple Example
+ --------------
+ Synchronous Example
+ ```javascript
+ let result;
+ try {
+ result = findResult();
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+ Errback Example
+ ```js
+ findResult(function(result, err){
+ if (err) {
+ // failure
+ } else {
+ // success
+ }
+ });
+ ```
+ Promise Example;
+ ```javascript
+ findResult().then(function(result){
+ // success
+ }, function(reason){
+ // failure
+ });
+ ```
+ Advanced Example
+ --------------
+ Synchronous Example
+ ```javascript
+ let author, books;
+ try {
+ author = findAuthor();
+ books = findBooksByAuthor(author);
+ // success
+ } catch(reason) {
+ // failure
+ }
+ ```
+ Errback Example
+ ```js
+ function foundBooks(books) {
+ }
+ function failure(reason) {
+ }
+ findAuthor(function(author, err){
+ if (err) {
+ failure(err);
+ // failure
+ } else {
+ try {
+ findBoooksByAuthor(author, function(books, err) {
+ if (err) {
+ failure(err);
+ } else {
+ try {
+ foundBooks(books);
+ } catch(reason) {
+ failure(reason);
+ }
+ }
+ });
+ } catch(error) {
+ failure(err);
+ }
+ // success
+ }
+ });
+ ```
+ Promise Example;
+ ```javascript
+ findAuthor().
+ then(findBooksByAuthor).
+ then(function(books){
+ // found books
+ }).catch(function(reason){
+ // something went wrong
+ });
+ ```
+ @method then
+ @param {Function} onFulfilled
+ @param {Function} onRejected
+ Useful for tooling.
+ @return {Promise}
+ */
+
+ /**
+ `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+ as the catch block of a try/catch statement.
+ ```js
+ function findAuthor(){
+ throw new Error('couldn't find that author');
+ }
+ // synchronous
+ try {
+ findAuthor();
+ } catch(reason) {
+ // something went wrong
+ }
+ // async with promises
+ findAuthor().catch(function(reason){
+ // something went wrong
+ });
+ ```
+ @method catch
+ @param {Function} onRejection
+ Useful for tooling.
+ @return {Promise}
+ */
+
+
+ createClass(Promise, [{
+ key: 'catch',
+ value: function _catch(onRejection) {
+ return this.then(null, onRejection);
+ }
+
+ /**
+ `finally` will be invoked regardless of the promise's fate just as native
+ try/catch/finally behaves
+
+ Synchronous example:
+
+ ```js
+ findAuthor() {
+ if (Math.random() > 0.5) {
+ throw new Error();
+ }
+ return new Author();
+ }
+
+ try {
+ return findAuthor(); // succeed or fail
+ } catch(error) {
+ return findOtherAuther();
+ } finally {
+ // always runs
+ // doesn't affect the return value
+ }
+ ```
+
+ Asynchronous example:
+
+ ```js
+ findAuthor().catch(function(reason){
+ return findOtherAuther();
+ }).finally(function(){
+ // author was either found, or not
+ });
+ ```
+
+ @method finally
+ @param {Function} callback
+ @return {Promise}
+ */
+
+ }, {
+ key: 'finally',
+ value: function _finally(callback) {
+ var promise = this;
+ var constructor = promise.constructor;
+
+ if (isFunction(callback)) {
+ return promise.then(function (value) {
+ return constructor.resolve(callback()).then(function () {
+ return value;
+ });
+ }, function (reason) {
+ return constructor.resolve(callback()).then(function () {
+ throw reason;
+ });
+ });
+ }
+
+ return promise.then(callback, callback);
+ }
+ }]);
+ return Promise;
+ }();
+
+ Promise$2.prototype.then = then;
+ Promise$2.all = all;
+ Promise$2.race = race;
+ Promise$2.resolve = resolve$1;
+ Promise$2.reject = reject$1;
+ Promise$2._setScheduler = setScheduler;
+ Promise$2._setAsap = setAsap;
+ Promise$2._asap = asap;
+
+ /*global self*/
+ function polyfill() {
+ var local = void 0;
+
+ if (typeof global !== 'undefined') {
+ local = global;
+ } else if (typeof self !== 'undefined') {
+ local = self;
+ } else {
+ try {
+ local = Function('return this')();
+ } catch (e) {
+ throw new Error('polyfill failed because global object is unavailable in this environment');
+ }
+ }
+
+ var P = local.Promise;
+
+ if (P) {
+ var promiseToString = null;
+ try {
+ promiseToString = Object.prototype.toString.call(P.resolve());
+ } catch (e) {
+ // silently ignored
+ }
+
+ if (promiseToString === '[object Promise]' && !P.cast) {
+ return;
+ }
+ }
+
+ local.Promise = Promise$2;
+ }
+
+ // Strange compat..
+ Promise$2.polyfill = polyfill;
+ Promise$2.Promise = Promise$2;
+
+ var Promise$1 = typeof Promise !== "undefined" ? Promise : Promise$2;
+
+ // Register logging callbacks
+ function registerLoggingCallbacks(obj) {
+ var i,
+ l,
+ key,
+ callbackNames = ["begin", "done", "log", "testStart", "testDone", "moduleStart", "moduleDone"];
+
+ function registerLoggingCallback(key) {
+ var loggingCallback = function loggingCallback(callback) {
+ if (objectType(callback) !== "function") {
+ throw new Error("QUnit logging methods require a callback function as their first parameters.");
+ }
+
+ config.callbacks[key].push(callback);
+ };
+
+ return loggingCallback;
+ }
+
+ for (i = 0, l = callbackNames.length; i < l; i++) {
+ key = callbackNames[i];
+
+ // Initialize key collection of logging callback
+ if (objectType(config.callbacks[key]) === "undefined") {
+ config.callbacks[key] = [];
+ }
+
+ obj[key] = registerLoggingCallback(key);
+ }
+ }
+
+ function runLoggingCallbacks(key, args) {
+ var callbacks = config.callbacks[key];
+
+ // Handling 'log' callbacks separately. Unlike the other callbacks,
+ // the log callback is not controlled by the processing queue,
+ // but rather used by asserts. Hence to promisfy the 'log' callback
+ // would mean promisfying each step of a test
+ if (key === "log") {
+ callbacks.map(function (callback) {
+ return callback(args);
+ });
+ return;
+ }
+
+ // ensure that each callback is executed serially
+ return callbacks.reduce(function (promiseChain, callback) {
+ return promiseChain.then(function () {
+ return Promise$1.resolve(callback(args));
+ });
+ }, Promise$1.resolve([]));
+ }
+
+ // Doesn't support IE9, it will return undefined on these browsers
+ // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+ var fileName = (sourceFromStacktrace(0) || "").replace(/(:\d+)+\)?/, "").replace(/.+\//, "");
+
+ function extractStacktrace(e, offset) {
+ offset = offset === undefined ? 4 : offset;
+
+ var stack, include, i;
+
+ if (e && e.stack) {
+ 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];
+ }
+ }
+
+ function sourceFromStacktrace(offset) {
+ var error = new Error();
+
+ // Support: Safari <=7 only, IE <=10 - 11 only
+ // Not all browsers generate the `stack` property for `new Error()`, see also #636
+ if (!error.stack) {
+ try {
+ throw error;
+ } catch (err) {
+ error = err;
+ }
+ }
+
+ return extractStacktrace(error, offset);
+ }
+
+ var priorityCount = 0;
+ var unitSampler = void 0;
+
+ // This is a queue of functions that are tasks within a single test.
+ // After tests are dequeued from config.queue they are expanded into
+ // a set of tasks in this queue.
+ var taskQueue = [];
+
+ /**
+ * Advances the taskQueue to the next task. If the taskQueue is empty,
+ * process the testQueue
+ */
+ function advance() {
+ advanceTaskQueue();
+
+ if (!taskQueue.length && !config.blocking && !config.current) {
+ advanceTestQueue();
+ }
+ }
+
+ /**
+ * Advances the taskQueue with an increased depth
+ */
+ function advanceTaskQueue() {
+ var start = now();
+ config.depth = (config.depth || 0) + 1;
+
+ processTaskQueue(start);
+
+ config.depth--;
+ }
+
+ /**
+ * Process the first task on the taskQueue as a promise.
+ * Each task is a function returned by https://github.com/qunitjs/qunit/blob/master/src/test.js#L381
+ */
+ function processTaskQueue(start) {
+ if (taskQueue.length && !config.blocking) {
+ var elapsedTime = now() - start;
+
+ if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
+ var task = taskQueue.shift();
+ Promise$1.resolve(task()).then(function () {
+ if (!taskQueue.length) {
+ advance();
+ } else {
+ processTaskQueue(start);
+ }
+ });
+ } else {
+ setTimeout$1(advance);
+ }
+ }
+ }
+
+ /**
+ * Advance the testQueue to the next test to process. Call done() if testQueue completes.
+ */
+ function advanceTestQueue() {
+ if (!config.blocking && !config.queue.length && config.depth === 0) {
+ done();
+ return;
+ }
+
+ var testTasks = config.queue.shift();
+ addToTaskQueue(testTasks());
+
+ if (priorityCount > 0) {
+ priorityCount--;
+ }
+
+ advance();
+ }
+
+ /**
+ * Enqueue the tasks for a test into the task queue.
+ * @param {Array} tasksArray
+ */
+ function addToTaskQueue(tasksArray) {
+ taskQueue.push.apply(taskQueue, toConsumableArray(tasksArray));
+ }
+
+ /**
+ * Return the number of tasks remaining in the task queue to be processed.
+ * @return {Number}
+ */
+ function taskQueueLength() {
+ return taskQueue.length;
+ }
+
+ /**
+ * Adds a test to the TestQueue for execution.
+ * @param {Function} testTasksFunc
+ * @param {Boolean} prioritize
+ * @param {String} seed
+ */
+ function addToTestQueue(testTasksFunc, prioritize, seed) {
+ if (prioritize) {
+ config.queue.splice(priorityCount++, 0, testTasksFunc);
+ } else if (seed) {
+ if (!unitSampler) {
+ unitSampler = unitSamplerGenerator(seed);
+ }
+
+ // Insert into a random position after all prioritized items
+ var index = Math.floor(unitSampler() * (config.queue.length - priorityCount + 1));
+ config.queue.splice(priorityCount + index, 0, testTasksFunc);
+ } else {
+ config.queue.push(testTasksFunc);
+ }
+ }
+
+ /**
+ * Creates a seeded "sample" generator which is used for randomizing tests.
+ */
+ function unitSamplerGenerator(seed) {
+
+ // 32-bit xorshift, requires only a nonzero seed
+ // http://excamera.com/sphinx/article-xorshift.html
+ var sample = parseInt(generateHash(seed), 16) || -1;
+ return function () {
+ sample ^= sample << 13;
+ sample ^= sample >>> 17;
+ sample ^= sample << 5;
+
+ // ECMAScript has no unsigned number type
+ if (sample < 0) {
+ sample += 0x100000000;
+ }
+
+ return sample / 0x100000000;
+ };
+ }
+
+ /**
+ * This function is called when the ProcessingQueue is done processing all
+ * items. It handles emitting the final run events.
+ */
+ function done() {
+ var storage = config.storage;
+
+ ProcessingQueue.finished = true;
+
+ var runtime = now() - config.started;
+ var passed = config.stats.all - config.stats.bad;
+
+ if (config.stats.all === 0) {
+
+ if (config.filter && config.filter.length) {
+ throw new Error("No tests matched the filter \"" + config.filter + "\".");
+ }
+
+ if (config.module && config.module.length) {
+ throw new Error("No tests matched the module \"" + config.module + "\".");
+ }
+
+ if (config.moduleId && config.moduleId.length) {
+ throw new Error("No tests matched the moduleId \"" + config.moduleId + "\".");
+ }
+
+ if (config.testId && config.testId.length) {
+ throw new Error("No tests matched the testId \"" + config.testId + "\".");
+ }
+
+ throw new Error("No tests were run.");
+ }
+
+ emit("runEnd", globalSuite.end(true));
+ runLoggingCallbacks("done", {
+ passed: passed,
+ failed: config.stats.bad,
+ total: config.stats.all,
+ runtime: runtime
+ }).then(function () {
+
+ // Clear own storage items if all tests passed
+ if (storage && config.stats.bad === 0) {
+ for (var i = storage.length - 1; i >= 0; i--) {
+ var key = storage.key(i);
+
+ if (key.indexOf("qunit-test-") === 0) {
+ storage.removeItem(key);
+ }
+ }
+ }
+ });
+ }
+
+ var ProcessingQueue = {
+ finished: false,
+ add: addToTestQueue,
+ advance: advance,
+ taskCount: taskQueueLength
+ };
+
+ var TestReport = function () {
+ function TestReport(name, suite, options) {
+ classCallCheck(this, TestReport);
+
+ this.name = name;
+ this.suiteName = suite.name;
+ this.fullName = suite.fullName.concat(name);
+ this.runtime = 0;
+ this.assertions = [];
+
+ this.skipped = !!options.skip;
+ this.todo = !!options.todo;
+
+ this.valid = options.valid;
+
+ this._startTime = 0;
+ this._endTime = 0;
+
+ suite.pushTest(this);
+ }
+
+ createClass(TestReport, [{
+ key: "start",
+ value: function start(recordTime) {
+ if (recordTime) {
+ this._startTime = performanceNow();
+ if (performance) {
+ performance.mark("qunit_test_start");
+ }
+ }
+
+ return {
+ name: this.name,
+ suiteName: this.suiteName,
+ fullName: this.fullName.slice()
+ };
+ }
+ }, {
+ key: "end",
+ value: function end(recordTime) {
+ if (recordTime) {
+ this._endTime = performanceNow();
+ if (performance) {
+ performance.mark("qunit_test_end");
+
+ var testName = this.fullName.join(" – ");
+
+ measure("QUnit Test: " + testName, "qunit_test_start", "qunit_test_end");
+ }
+ }
+
+ return extend(this.start(), {
+ runtime: this.getRuntime(),
+ status: this.getStatus(),
+ errors: this.getFailedAssertions(),
+ assertions: this.getAssertions()
+ });
+ }
+ }, {
+ key: "pushAssertion",
+ value: function pushAssertion(assertion) {
+ this.assertions.push(assertion);
+ }
+ }, {
+ key: "getRuntime",
+ value: function getRuntime() {
+ return this._endTime - this._startTime;
+ }
+ }, {
+ key: "getStatus",
+ value: function getStatus() {
+ if (this.skipped) {
+ return "skipped";
+ }
+
+ var testPassed = this.getFailedAssertions().length > 0 ? this.todo : !this.todo;
+
+ if (!testPassed) {
+ return "failed";
+ } else if (this.todo) {
+ return "todo";
+ } else {
+ return "passed";
+ }
+ }
+ }, {
+ key: "getFailedAssertions",
+ value: function getFailedAssertions() {
+ return this.assertions.filter(function (assertion) {
+ return !assertion.passed;
+ });
+ }
+ }, {
+ key: "getAssertions",
+ value: function getAssertions() {
+ return this.assertions.slice();
+ }
+
+ // Remove actual and expected values from assertions. This is to prevent
+ // leaking memory throughout a test suite.
+
+ }, {
+ key: "slimAssertions",
+ value: function slimAssertions() {
+ this.assertions = this.assertions.map(function (assertion) {
+ delete assertion.actual;
+ delete assertion.expected;
+ return assertion;
+ });
+ }
+ }]);
+ return TestReport;
+ }();
+
+ var focused$1 = false;
+
+ function Test(settings) {
+ var i, l;
+
+ ++Test.count;
+
+ this.expected = null;
+ this.assertions = [];
+ this.semaphore = 0;
+ this.module = config.currentModule;
+ this.stack = sourceFromStacktrace(3);
+ this.steps = [];
+ this.timeout = undefined;
+
+ // If a module is skipped, all its tests and the tests of the child suites
+ // should be treated as skipped even if they are defined as `only` or `todo`.
+ // As for `todo` module, all its tests will be treated as `todo` except for
+ // tests defined as `skip` which will be left intact.
+ //
+ // So, if a test is defined as `todo` and is inside a skipped module, we should
+ // then treat that test as if was defined as `skip`.
+ if (this.module.skip) {
+ settings.skip = true;
+ settings.todo = false;
+
+ // Skipped tests should be left intact
+ } else if (this.module.todo && !settings.skip) {
+ settings.todo = true;
+ }
+
+ extend(this, settings);
+
+ this.testReport = new TestReport(settings.testName, this.module.suiteReport, {
+ todo: settings.todo,
+ skip: settings.skip,
+ valid: this.valid()
+ });
+
+ // Register unique strings
+ for (i = 0, l = this.module.tests; i < l.length; i++) {
+ if (this.module.tests[i].name === this.testName) {
+ this.testName += " ";
+ }
+ }
+
+ this.testId = generateHash(this.module.name, this.testName);
+
+ this.module.tests.push({
+ name: this.testName,
+ testId: this.testId,
+ skip: !!settings.skip
+ });
+
+ if (settings.skip) {
+
+ // Skipped tests will fully ignore any sent callback
+ this.callback = function () {};
+ this.async = false;
+ this.expected = 0;
+ } else {
+ if (typeof this.callback !== "function") {
+ var method = this.todo ? "todo" : "test";
+
+ // eslint-disable-next-line max-len
+ throw new TypeError("You must provide a function as a test callback to QUnit." + method + "(\"" + settings.testName + "\")");
+ }
+
+ this.assert = new Assert(this);
+ }
+ }
+
+ Test.count = 0;
+
+ function getNotStartedModules(startModule) {
+ var module = startModule,
+ modules = [];
+
+ while (module && module.testsRun === 0) {
+ modules.push(module);
+ module = module.parentModule;
+ }
+
+ // The above push modules from the child to the parent
+ // return a reversed order with the top being the top most parent module
+ return modules.reverse();
+ }
+
+ Test.prototype = {
+ before: function before() {
+ var _this = this;
+
+ var module = this.module,
+ notStartedModules = getNotStartedModules(module);
+
+ // ensure the callbacks are executed serially for each module
+ var callbackPromises = notStartedModules.reduce(function (promiseChain, startModule) {
+ return promiseChain.then(function () {
+ startModule.stats = { all: 0, bad: 0, started: now() };
+ emit("suiteStart", startModule.suiteReport.start(true));
+ return runLoggingCallbacks("moduleStart", {
+ name: startModule.name,
+ tests: startModule.tests
+ });
+ });
+ }, Promise$1.resolve([]));
+
+ return callbackPromises.then(function () {
+ config.current = _this;
+
+ _this.testEnvironment = extend({}, module.testEnvironment);
+
+ _this.started = now();
+ emit("testStart", _this.testReport.start(true));
+ return runLoggingCallbacks("testStart", {
+ name: _this.testName,
+ module: module.name,
+ testId: _this.testId,
+ previousFailure: _this.previousFailure
+ }).then(function () {
+ if (!config.pollution) {
+ saveGlobal();
+ }
+ });
+ });
+ },
+
+ run: function run() {
+ var promise;
+
+ config.current = this;
+
+ this.callbackStarted = now();
+
+ if (config.notrycatch) {
+ runTest(this);
+ return;
+ }
+
+ try {
+ runTest(this);
+ } catch (e) {
+ this.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + (e.message || e), extractStacktrace(e, 0));
+
+ // Else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if (config.blocking) {
+ internalRecover(this);
+ }
+ }
+
+ function runTest(test) {
+ promise = test.callback.call(test.testEnvironment, test.assert);
+ test.resolvePromise(promise);
+
+ // If the test has a "lock" on it, but the timeout is 0, then we push a
+ // failure as the test should be synchronous.
+ if (test.timeout === 0 && test.semaphore !== 0) {
+ pushFailure("Test did not finish synchronously even though assert.timeout( 0 ) was used.", sourceFromStacktrace(2));
+ }
+ }
+ },
+
+ after: function after() {
+ checkPollution();
+ },
+
+ queueHook: function queueHook(hook, hookName, hookOwner) {
+ var _this2 = this;
+
+ var callHook = function callHook() {
+ var promise = hook.call(_this2.testEnvironment, _this2.assert);
+ _this2.resolvePromise(promise, hookName);
+ };
+
+ var runHook = function runHook() {
+ if (hookName === "before") {
+ if (hookOwner.unskippedTestsRun !== 0) {
+ return;
+ }
+
+ _this2.preserveEnvironment = true;
+ }
+
+ // The 'after' hook should only execute when there are not tests left and
+ // when the 'after' and 'finish' tasks are the only tasks left to process
+ if (hookName === "after" && hookOwner.unskippedTestsRun !== numberOfUnskippedTests(hookOwner) - 1 && (config.queue.length > 0 || ProcessingQueue.taskCount() > 2)) {
+ return;
+ }
+
+ config.current = _this2;
+ if (config.notrycatch) {
+ callHook();
+ return;
+ }
+ try {
+ callHook();
+ } catch (error) {
+ _this2.pushFailure(hookName + " failed on " + _this2.testName + ": " + (error.message || error), extractStacktrace(error, 0));
+ }
+ };
+
+ return runHook;
+ },
+
+
+ // Currently only used for module level hooks, can be used to add global level ones
+ hooks: function hooks(handler) {
+ var hooks = [];
+
+ function processHooks(test, module) {
+ if (module.parentModule) {
+ processHooks(test, module.parentModule);
+ }
+
+ if (module.hooks[handler].length) {
+ for (var i = 0; i < module.hooks[handler].length; i++) {
+ hooks.push(test.queueHook(module.hooks[handler][i], handler, module));
+ }
+ }
+ }
+
+ // Hooks are ignored on skipped tests
+ if (!this.skip) {
+ processHooks(this, this.module);
+ }
+
+ return hooks;
+ },
+
+
+ finish: function finish() {
+ config.current = this;
+
+ // Release the test callback to ensure that anything referenced has been
+ // released to be garbage collected.
+ this.callback = undefined;
+
+ if (this.steps.length) {
+ var stepsList = this.steps.join(", ");
+ this.pushFailure("Expected assert.verifySteps() to be called before end of test " + ("after using assert.step(). Unverified steps: " + stepsList), this.stack);
+ }
+
+ if (config.requireExpects && this.expected === null) {
+ this.pushFailure("Expected number of assertions to be defined, but expect() was " + "not called.", this.stack);
+ } else if (this.expected !== null && this.expected !== this.assertions.length) {
+ this.pushFailure("Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack);
+ } else if (this.expected === null && !this.assertions.length) {
+ this.pushFailure("Expected at least one assertion, but none were run - call " + "expect(0) to accept zero assertions.", this.stack);
+ }
+
+ var i,
+ module = this.module,
+ moduleName = module.name,
+ testName = this.testName,
+ skipped = !!this.skip,
+ todo = !!this.todo,
+ bad = 0,
+ storage = config.storage;
+
+ this.runtime = now() - this.started;
+
+ config.stats.all += this.assertions.length;
+ module.stats.all += this.assertions.length;
+
+ for (i = 0; i < this.assertions.length; i++) {
+ if (!this.assertions[i].result) {
+ bad++;
+ config.stats.bad++;
+ module.stats.bad++;
+ }
+ }
+
+ notifyTestsRan(module, skipped);
+
+ // Store result when possible
+ if (storage) {
+ if (bad) {
+ storage.setItem("qunit-test-" + moduleName + "-" + testName, bad);
+ } else {
+ storage.removeItem("qunit-test-" + moduleName + "-" + testName);
+ }
+ }
+
+ // After emitting the js-reporters event we cleanup the assertion data to
+ // avoid leaking it. It is not used by the legacy testDone callbacks.
+ emit("testEnd", this.testReport.end(true));
+ this.testReport.slimAssertions();
+
+ return runLoggingCallbacks("testDone", {
+ name: testName,
+ module: moduleName,
+ skipped: skipped,
+ todo: todo,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ runtime: skipped ? 0 : this.runtime,
+
+ // HTML Reporter use
+ assertions: this.assertions,
+ testId: this.testId,
+
+ // Source of Test
+ source: this.stack
+ }).then(function () {
+ if (module.testsRun === numberOfTests(module)) {
+ var completedModules = [module];
+
+ // Check if the parent modules, iteratively, are done. If that the case,
+ // we emit the `suiteEnd` event and trigger `moduleDone` callback.
+ var parent = module.parentModule;
+ while (parent && parent.testsRun === numberOfTests(parent)) {
+ completedModules.push(parent);
+ parent = parent.parentModule;
+ }
+
+ return completedModules.reduce(function (promiseChain, completedModule) {
+ return promiseChain.then(function () {
+ return logSuiteEnd(completedModule);
+ });
+ }, Promise$1.resolve([]));
+ }
+ }).then(function () {
+ config.current = undefined;
+ });
+
+ function logSuiteEnd(module) {
+
+ // Reset `module.hooks` to ensure that anything referenced in these hooks
+ // has been released to be garbage collected.
+ module.hooks = {};
+
+ emit("suiteEnd", module.suiteReport.end(true));
+ return runLoggingCallbacks("moduleDone", {
+ name: module.name,
+ tests: module.tests,
+ failed: module.stats.bad,
+ passed: module.stats.all - module.stats.bad,
+ total: module.stats.all,
+ runtime: now() - module.stats.started
+ });
+ }
+ },
+
+ preserveTestEnvironment: function preserveTestEnvironment() {
+ if (this.preserveEnvironment) {
+ this.module.testEnvironment = this.testEnvironment;
+ this.testEnvironment = extend({}, this.module.testEnvironment);
+ }
+ },
+
+ queue: function queue() {
+ var test = this;
+
+ if (!this.valid()) {
+ return;
+ }
+
+ function runTest() {
+ return [function () {
+ return test.before();
+ }].concat(toConsumableArray(test.hooks("before")), [function () {
+ test.preserveTestEnvironment();
+ }], toConsumableArray(test.hooks("beforeEach")), [function () {
+ test.run();
+ }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () {
+ test.after();
+ }, function () {
+ return test.finish();
+ }]);
+ }
+
+ var previousFailCount = config.storage && +config.storage.getItem("qunit-test-" + this.module.name + "-" + this.testName);
+
+ // Prioritize previously failed tests, detected from storage
+ var prioritize = config.reorder && !!previousFailCount;
+
+ this.previousFailure = !!previousFailCount;
+
+ ProcessingQueue.add(runTest, prioritize, config.seed);
+
+ // If the queue has already finished, we manually process the new test
+ if (ProcessingQueue.finished) {
+ ProcessingQueue.advance();
+ }
+ },
+
+
+ pushResult: function pushResult(resultInfo) {
+ if (this !== config.current) {
+ throw new Error("Assertion occurred after test had finished.");
+ }
+
+ // Destructure of resultInfo = { result, actual, expected, message, negative }
+ var source,
+ details = {
+ module: this.module.name,
+ name: this.testName,
+ result: resultInfo.result,
+ message: resultInfo.message,
+ actual: resultInfo.actual,
+ testId: this.testId,
+ negative: resultInfo.negative || false,
+ runtime: now() - this.started,
+ todo: !!this.todo
+ };
+
+ if (hasOwn.call(resultInfo, "expected")) {
+ details.expected = resultInfo.expected;
+ }
+
+ if (!resultInfo.result) {
+ source = resultInfo.source || sourceFromStacktrace();
+
+ if (source) {
+ details.source = source;
+ }
+ }
+
+ this.logAssertion(details);
+
+ this.assertions.push({
+ result: !!resultInfo.result,
+ message: resultInfo.message
+ });
+ },
+
+ pushFailure: function pushFailure(message, source, actual) {
+ if (!(this instanceof Test)) {
+ throw new Error("pushFailure() assertion outside test context, was " + sourceFromStacktrace(2));
+ }
+
+ this.pushResult({
+ result: false,
+ message: message || "error",
+ actual: actual || null,
+ source: source
+ });
+ },
+
+ /**
+ * Log assertion details using both the old QUnit.log interface and
+ * QUnit.on( "assertion" ) interface.
+ *
+ * @private
+ */
+ logAssertion: function logAssertion(details) {
+ runLoggingCallbacks("log", details);
+
+ var assertion = {
+ passed: details.result,
+ actual: details.actual,
+ expected: details.expected,
+ message: details.message,
+ stack: details.source,
+ todo: details.todo
+ };
+ this.testReport.pushAssertion(assertion);
+ emit("assertion", assertion);
+ },
+
+
+ resolvePromise: function resolvePromise(promise, phase) {
+ var then,
+ resume,
+ message,
+ test = this;
+ if (promise != null) {
+ then = promise.then;
+ if (objectType(then) === "function") {
+ resume = internalStop(test);
+ if (config.notrycatch) {
+ then.call(promise, function () {
+ resume();
+ });
+ } else {
+ then.call(promise, function () {
+ resume();
+ }, function (error) {
+ message = "Promise rejected " + (!phase ? "during" : phase.replace(/Each$/, "")) + " \"" + test.testName + "\": " + (error && error.message || error);
+ test.pushFailure(message, extractStacktrace(error, 0));
+
+ // Else next test will carry the responsibility
+ saveGlobal();
+
+ // Unblock
+ internalRecover(test);
+ });
+ }
+ }
+ }
+ },
+
+ valid: function valid() {
+ var filter = config.filter,
+ regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec(filter),
+ module = config.module && config.module.toLowerCase(),
+ fullName = this.module.name + ": " + this.testName;
+
+ function moduleChainNameMatch(testModule) {
+ var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
+ if (testModuleName === module) {
+ return true;
+ } else if (testModule.parentModule) {
+ return moduleChainNameMatch(testModule.parentModule);
+ } else {
+ return false;
+ }
+ }
+
+ function moduleChainIdMatch(testModule) {
+ return inArray(testModule.moduleId, config.moduleId) || testModule.parentModule && moduleChainIdMatch(testModule.parentModule);
+ }
+
+ // Internally-generated tests are always valid
+ if (this.callback && this.callback.validTest) {
+ return true;
+ }
+
+ if (config.moduleId && config.moduleId.length > 0 && !moduleChainIdMatch(this.module)) {
+
+ return false;
+ }
+
+ if (config.testId && config.testId.length > 0 && !inArray(this.testId, config.testId)) {
+
+ return false;
+ }
+
+ if (module && !moduleChainNameMatch(this.module)) {
+ return false;
+ }
+
+ if (!filter) {
+ return true;
+ }
+
+ return regexFilter ? this.regexFilter(!!regexFilter[1], regexFilter[2], regexFilter[3], fullName) : this.stringFilter(filter, fullName);
+ },
+
+ regexFilter: function regexFilter(exclude, pattern, flags, fullName) {
+ var regex = new RegExp(pattern, flags);
+ var match = regex.test(fullName);
+
+ return match !== exclude;
+ },
+
+ stringFilter: function stringFilter(filter, fullName) {
+ filter = filter.toLowerCase();
+ fullName = fullName.toLowerCase();
+
+ var include = filter.charAt(0) !== "!";
+ if (!include) {
+ filter = filter.slice(1);
+ }
+
+ // If the filter matches, we need to honour include
+ if (fullName.indexOf(filter) !== -1) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+ }
+ };
+
+ function pushFailure() {
+ if (!config.current) {
+ throw new Error("pushFailure() assertion outside test context, in " + sourceFromStacktrace(2));
+ }
+
+ // Gets current test obj
+ var currentTest = config.current;
+
+ return currentTest.pushFailure.apply(currentTest, arguments);
+ }
+
+ function saveGlobal() {
+ config.pollution = [];
+
+ if (config.noglobals) {
+ for (var key in global$1) {
+ if (hasOwn.call(global$1, 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() {
+ var newGlobals,
+ deletedGlobals,
+ old = config.pollution;
+
+ saveGlobal();
+
+ newGlobals = diff(config.pollution, old);
+ if (newGlobals.length > 0) {
+ pushFailure("Introduced global variable(s): " + newGlobals.join(", "));
+ }
+
+ deletedGlobals = diff(old, config.pollution);
+ if (deletedGlobals.length > 0) {
+ pushFailure("Deleted global variable(s): " + deletedGlobals.join(", "));
+ }
+ }
+
+ // Will be exposed as QUnit.test
+ function test(testName, callback) {
+ if (focused$1) {
+ return;
+ }
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback
+ });
+
+ newTest.queue();
+ }
+
+ function todo(testName, callback) {
+ if (focused$1) {
+ return;
+ }
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback,
+ todo: true
+ });
+
+ newTest.queue();
+ }
+
+ // Will be exposed as QUnit.skip
+ function skip(testName) {
+ if (focused$1) {
+ return;
+ }
+
+ var test = new Test({
+ testName: testName,
+ skip: true
+ });
+
+ test.queue();
+ }
+
+ // Will be exposed as QUnit.only
+ function only(testName, callback) {
+ if (focused$1) {
+ return;
+ }
+
+ config.queue.length = 0;
+ focused$1 = true;
+
+ var newTest = new Test({
+ testName: testName,
+ callback: callback
+ });
+
+ newTest.queue();
+ }
+
+ // Put a hold on processing and return a function that will release it.
+ function internalStop(test) {
+ test.semaphore += 1;
+ config.blocking = true;
+
+ // Set a recovery timeout, if so configured.
+ if (defined.setTimeout) {
+ var timeoutDuration = void 0;
+
+ if (typeof test.timeout === "number") {
+ timeoutDuration = test.timeout;
+ } else if (typeof config.testTimeout === "number") {
+ timeoutDuration = config.testTimeout;
+ }
+
+ if (typeof timeoutDuration === "number" && timeoutDuration > 0) {
+ clearTimeout(config.timeout);
+ config.timeout = setTimeout$1(function () {
+ pushFailure("Test took longer than " + timeoutDuration + "ms; test timed out.", sourceFromStacktrace(2));
+ internalRecover(test);
+ }, timeoutDuration);
+ }
+ }
+
+ var released = false;
+ return function resume() {
+ if (released) {
+ return;
+ }
+
+ released = true;
+ test.semaphore -= 1;
+ internalStart(test);
+ };
+ }
+
+ // Forcefully release all processing holds.
+ function internalRecover(test) {
+ test.semaphore = 0;
+ internalStart(test);
+ }
+
+ // Release a processing hold, scheduling a resumption attempt if no holds remain.
+ function internalStart(test) {
+
+ // If semaphore is non-numeric, throw error
+ if (isNaN(test.semaphore)) {
+ test.semaphore = 0;
+
+ pushFailure("Invalid value on test.semaphore", sourceFromStacktrace(2));
+ return;
+ }
+
+ // Don't start until equal number of stop-calls
+ if (test.semaphore > 0) {
+ return;
+ }
+
+ // Throw an Error if start is called more often than stop
+ if (test.semaphore < 0) {
+ test.semaphore = 0;
+
+ pushFailure("Tried to restart test while already started (test's semaphore was 0 already)", sourceFromStacktrace(2));
+ return;
+ }
+
+ // Add a slight delay to allow more assertions etc.
+ if (defined.setTimeout) {
+ if (config.timeout) {
+ clearTimeout(config.timeout);
+ }
+ config.timeout = setTimeout$1(function () {
+ if (test.semaphore > 0) {
+ return;
+ }
+
+ if (config.timeout) {
+ clearTimeout(config.timeout);
+ }
+
+ begin();
+ });
+ } else {
+ begin();
+ }
+ }
+
+ function collectTests(module) {
+ var tests = [].concat(module.tests);
+ var modules = [].concat(toConsumableArray(module.childModules));
+
+ // Do a breadth-first traversal of the child modules
+ while (modules.length) {
+ var nextModule = modules.shift();
+ tests.push.apply(tests, nextModule.tests);
+ modules.push.apply(modules, toConsumableArray(nextModule.childModules));
+ }
+
+ return tests;
+ }
+
+ function numberOfTests(module) {
+ return collectTests(module).length;
+ }
+
+ function numberOfUnskippedTests(module) {
+ return collectTests(module).filter(function (test) {
+ return !test.skip;
+ }).length;
+ }
+
+ function notifyTestsRan(module, skipped) {
+ module.testsRun++;
+ if (!skipped) {
+ module.unskippedTestsRun++;
+ }
+ while (module = module.parentModule) {
+ module.testsRun++;
+ if (!skipped) {
+ module.unskippedTestsRun++;
+ }
+ }
+ }
+
+ var Assert = function () {
+ function Assert(testContext) {
+ classCallCheck(this, Assert);
+
+ this.test = testContext;
+ }
+
+ // Assert helpers
+
+ createClass(Assert, [{
+ key: "timeout",
+ value: function timeout(duration) {
+ if (typeof duration !== "number") {
+ throw new Error("You must pass a number as the duration to assert.timeout");
+ }
+
+ this.test.timeout = duration;
+ }
+
+ // Documents a "step", which is a string value, in a test as a passing assertion
+
+ }, {
+ key: "step",
+ value: function step(message) {
+ var assertionMessage = message;
+ var result = !!message;
+
+ this.test.steps.push(message);
+
+ if (objectType(message) === "undefined" || message === "") {
+ assertionMessage = "You must provide a message to assert.step";
+ } else if (objectType(message) !== "string") {
+ assertionMessage = "You must provide a string value to assert.step";
+ result = false;
+ }
+
+ this.pushResult({
+ result: result,
+ message: assertionMessage
+ });
+ }
+
+ // Verifies the steps in a test match a given array of string values
+
+ }, {
+ key: "verifySteps",
+ value: function verifySteps(steps, message) {
+
+ // Since the steps array is just string values, we can clone with slice
+ var actualStepsClone = this.test.steps.slice();
+ this.deepEqual(actualStepsClone, steps, message);
+ this.test.steps.length = 0;
+ }
+
+ // Specify the number of expected assertions to guarantee that failed test
+ // (no assertions are run at all) don't slip through.
+
+ }, {
+ key: "expect",
+ value: function expect(asserts) {
+ if (arguments.length === 1) {
+ this.test.expected = asserts;
+ } else {
+ return this.test.expected;
+ }
+ }
+
+ // Put a hold on processing and return a function that will release it a maximum of once.
+
+ }, {
+ key: "async",
+ value: function async(count) {
+ var test$$1 = this.test;
+
+ var popped = false,
+ acceptCallCount = count;
+
+ if (typeof acceptCallCount === "undefined") {
+ acceptCallCount = 1;
+ }
+
+ var resume = internalStop(test$$1);
+
+ return function done() {
+ if (config.current !== test$$1) {
+ throw Error("assert.async callback called after test finished.");
+ }
+
+ if (popped) {
+ test$$1.pushFailure("Too many calls to the `assert.async` callback", sourceFromStacktrace(2));
+ return;
+ }
+
+ acceptCallCount -= 1;
+ if (acceptCallCount > 0) {
+ return;
+ }
+
+ popped = true;
+ resume();
+ };
+ }
+
+ // Exports test.push() to the user API
+ // Alias of pushResult.
+
+ }, {
+ key: "push",
+ value: function push(result, actual, expected, message, negative) {
+ Logger.warn("assert.push is deprecated and will be removed in QUnit 3.0." + " Please use assert.pushResult instead (https://api.qunitjs.com/assert/pushResult).");
+
+ var currentAssert = this instanceof Assert ? this : config.current.assert;
+ return currentAssert.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: negative
+ });
+ }
+ }, {
+ key: "pushResult",
+ value: function pushResult(resultInfo) {
+
+ // Destructure of resultInfo = { result, actual, expected, message, negative }
+ var assert = this;
+ var currentTest = assert instanceof Assert && assert.test || config.current;
+
+ // Backwards compatibility fix.
+ // Allows the direct use of global exported assertions and QUnit.assert.*
+ // Although, it's use is not recommended as it can leak assertions
+ // to other tests from async tests, because we only get a reference to the current test,
+ // not exactly the test where assertion were intended to be called.
+ if (!currentTest) {
+ throw new Error("assertion outside test context, in " + sourceFromStacktrace(2));
+ }
+
+ if (!(assert instanceof Assert)) {
+ assert = currentTest.assert;
+ }
+
+ return assert.test.pushResult(resultInfo);
+ }
+ }, {
+ key: "ok",
+ value: function ok(result, message) {
+ if (!message) {
+ message = result ? "okay" : "failed, expected argument to be truthy, was: " + dump.parse(result);
+ }
+
+ this.pushResult({
+ result: !!result,
+ actual: result,
+ expected: true,
+ message: message
+ });
+ }
+ }, {
+ key: "notOk",
+ value: function notOk(result, message) {
+ if (!message) {
+ message = !result ? "okay" : "failed, expected argument to be falsy, was: " + dump.parse(result);
+ }
+
+ this.pushResult({
+ result: !result,
+ actual: result,
+ expected: false,
+ message: message
+ });
+ }
+ }, {
+ key: "equal",
+ value: function equal(actual, expected, message) {
+
+ // eslint-disable-next-line eqeqeq
+ var result = expected == actual;
+
+ this.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notEqual",
+ value: function notEqual(actual, expected, message) {
+
+ // eslint-disable-next-line eqeqeq
+ var result = expected != actual;
+
+ this.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "propEqual",
+ value: function propEqual(actual, expected, message) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+
+ this.pushResult({
+ result: equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notPropEqual",
+ value: function notPropEqual(actual, expected, message) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+
+ this.pushResult({
+ result: !equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "deepEqual",
+ value: function deepEqual(actual, expected, message) {
+ this.pushResult({
+ result: equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notDeepEqual",
+ value: function notDeepEqual(actual, expected, message) {
+ this.pushResult({
+ result: !equiv(actual, expected),
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "strictEqual",
+ value: function strictEqual(actual, expected, message) {
+ this.pushResult({
+ result: expected === actual,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "notStrictEqual",
+ value: function notStrictEqual(actual, expected, message) {
+ this.pushResult({
+ result: expected !== actual,
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ });
+ }
+ }, {
+ key: "throws",
+ value: function throws(block, expected, message) {
+ var actual = void 0,
+ result = false;
+
+ var currentTest = this instanceof Assert && this.test || config.current;
+
+ // 'expected' is optional unless doing string comparison
+ if (objectType(expected) === "string") {
+ if (message == null) {
+ message = expected;
+ expected = null;
+ } else {
+ throw new Error("throws/raises does not accept a string value for the expected argument.\n" + "Use a non-string object value (e.g. regExp) instead if it's necessary.");
+ }
+ }
+
+ currentTest.ignoreGlobalErrors = true;
+ try {
+ block.call(currentTest.testEnvironment);
+ } catch (e) {
+ actual = e;
+ }
+ currentTest.ignoreGlobalErrors = false;
+
+ if (actual) {
+ var expectedType = objectType(expected);
+
+ // We don't want to validate thrown error
+ if (!expected) {
+ result = true;
+ expected = null;
+
+ // Expected is a regexp
+ } else if (expectedType === "regexp") {
+ result = expected.test(errorString(actual));
+
+ // Expected is a constructor, maybe an Error constructor
+ } else if (expectedType === "function" && actual instanceof expected) {
+ result = true;
+
+ // Expected is an Error object
+ } else if (expectedType === "object") {
+ result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
+
+ // Expected is a validation function which returns true if validation passed
+ } else if (expectedType === "function" && expected.call({}, actual) === true) {
+ expected = null;
+ result = true;
+ }
+ }
+
+ currentTest.assert.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+ }
+ }, {
+ key: "rejects",
+ value: function rejects(promise, expected, message) {
+ var result = false;
+
+ var currentTest = this instanceof Assert && this.test || config.current;
+
+ // 'expected' is optional unless doing string comparison
+ if (objectType(expected) === "string") {
+ if (message === undefined) {
+ message = expected;
+ expected = undefined;
+ } else {
+ message = "assert.rejects does not accept a string value for the expected " + "argument.\nUse a non-string object value (e.g. validator function) instead " + "if necessary.";
+
+ currentTest.assert.pushResult({
+ result: false,
+ message: message
+ });
+
+ return;
+ }
+ }
+
+ var then = promise && promise.then;
+ if (objectType(then) !== "function") {
+ var _message = "The value provided to `assert.rejects` in " + "\"" + currentTest.testName + "\" was not a promise.";
+
+ currentTest.assert.pushResult({
+ result: false,
+ message: _message,
+ actual: promise
+ });
+
+ return;
+ }
+
+ var done = this.async();
+
+ return then.call(promise, function handleFulfillment() {
+ var message = "The promise returned by the `assert.rejects` callback in " + "\"" + currentTest.testName + "\" did not reject.";
+
+ currentTest.assert.pushResult({
+ result: false,
+ message: message,
+ actual: promise
+ });
+
+ done();
+ }, function handleRejection(actual) {
+ var expectedType = objectType(expected);
+
+ // We don't want to validate
+ if (expected === undefined) {
+ result = true;
+ expected = actual;
+
+ // Expected is a regexp
+ } else if (expectedType === "regexp") {
+ result = expected.test(errorString(actual));
+
+ // Expected is a constructor, maybe an Error constructor
+ } else if (expectedType === "function" && actual instanceof expected) {
+ result = true;
+
+ // Expected is an Error object
+ } else if (expectedType === "object") {
+ result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
+
+ // Expected is a validation function which returns true if validation passed
+ } else {
+ if (expectedType === "function") {
+ result = expected.call({}, actual) === true;
+ expected = null;
+
+ // Expected is some other invalid type
+ } else {
+ result = false;
+ message = "invalid expected value provided to `assert.rejects` " + "callback in \"" + currentTest.testName + "\": " + expectedType + ".";
+ }
+ }
+
+ currentTest.assert.pushResult({
+ result: result,
+ actual: actual,
+ expected: expected,
+ message: message
+ });
+
+ done();
+ });
+ }
+ }]);
+ return Assert;
+ }();
+
+ // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
+ // Known to us are: Closure Compiler, Narwhal
+ // eslint-disable-next-line dot-notation
+
+
+ Assert.prototype.raises = Assert.prototype["throws"];
+
+ /**
+ * Converts an error into a simple string for comparisons.
+ *
+ * @param {Error} error
+ * @return {String}
+ */
+ function errorString(error) {
+ var resultErrorString = error.toString();
+
+ if (resultErrorString.substring(0, 7) === "[object") {
+ var name = error.name ? error.name.toString() : "Error";
+ var 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";
+ }
+ } else {
+ return resultErrorString;
+ }
+ }
+
+ /* global module, exports, define */
+ function exportQUnit(QUnit) {
+
+ if (defined.document) {
+
+ // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
+ if (window$1.QUnit && window$1.QUnit.version) {
+ throw new Error("QUnit has already been defined.");
+ }
+
+ window$1.QUnit = QUnit;
+ }
+
+ // For nodejs
+ if (typeof module !== "undefined" && module && module.exports) {
+ module.exports = QUnit;
+
+ // For consistency with CommonJS environments' exports
+ module.exports.QUnit = QUnit;
+ }
+
+ // For CommonJS with exports, but without module.exports, like Rhino
+ if (typeof exports !== "undefined" && exports) {
+ exports.QUnit = QUnit;
+ }
+
+ if (typeof define === "function" && define.amd) {
+ define(function () {
+ return QUnit;
+ });
+ QUnit.config.autostart = false;
+ }
+
+ // For Web/Service Workers
+ if (self$1 && self$1.WorkerGlobalScope && self$1 instanceof self$1.WorkerGlobalScope) {
+ self$1.QUnit = QUnit;
+ }
+ }
+
+ // Handle an unhandled exception. By convention, returns true if further
+ // error handling should be suppressed and false otherwise.
+ // In this case, we will only suppress further error handling if the
+ // "ignoreGlobalErrors" configuration option is enabled.
+ function onError(error) {
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ if (config.current) {
+ if (config.current.ignoreGlobalErrors) {
+ return true;
+ }
+ pushFailure.apply(undefined, [error.message, error.stacktrace || error.fileName + ":" + error.lineNumber].concat(args));
+ } else {
+ test("global failure", extend(function () {
+ pushFailure.apply(undefined, [error.message, error.stacktrace || error.fileName + ":" + error.lineNumber].concat(args));
+ }, { validTest: true }));
+ }
+
+ return false;
+ }
+
+ // Handle an unhandled rejection
+ function onUnhandledRejection(reason) {
+ var resultInfo = {
+ result: false,
+ message: reason.message || "error",
+ actual: reason,
+ source: reason.stack || sourceFromStacktrace(3)
+ };
+
+ var currentTest = config.current;
+ if (currentTest) {
+ currentTest.assert.pushResult(resultInfo);
+ } else {
+ test("global failure", extend(function (assert) {
+ assert.pushResult(resultInfo);
+ }, { validTest: true }));
+ }
+ }
+
+ var QUnit = {};
+ var globalSuite = new SuiteReport();
+
+ // The initial "currentModule" represents the global (or top-level) module that
+ // is not explicitly defined by the user, therefore we add the "globalSuite" to
+ // it since each module has a suiteReport associated with it.
+ config.currentModule.suiteReport = globalSuite;
+
+ var globalStartCalled = false;
+ var runStarted = false;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = !(defined.document && window$1.location.protocol !== "file:");
+
+ // Expose the current QUnit version
+ QUnit.version = "2.8.0";
+
+ extend(QUnit, {
+ on: on,
+
+ module: module$1,
+
+ test: test,
+
+ todo: todo,
+
+ skip: skip,
+
+ only: only,
+
+ start: function start(count) {
+ var globalStartAlreadyCalled = globalStartCalled;
+
+ if (!config.current) {
+ globalStartCalled = true;
+
+ if (runStarted) {
+ throw new Error("Called start() while test already started running");
+ } else if (globalStartAlreadyCalled || count > 1) {
+ throw new Error("Called start() outside of a test context too many times");
+ } else if (config.autostart) {
+ throw new Error("Called start() outside of a test context when " + "QUnit.config.autostart was true");
+ } else if (!config.pageLoaded) {
+
+ // The page isn't completely loaded yet, so we set autostart and then
+ // load if we're in Node or wait for the browser's load event.
+ config.autostart = true;
+
+ // Starts from Node even if .load was not previously called. We still return
+ // early otherwise we'll wind up "beginning" twice.
+ if (!defined.document) {
+ QUnit.load();
+ }
+
+ return;
+ }
+ } else {
+ throw new Error("QUnit.start cannot be called inside a test context.");
+ }
+
+ scheduleBegin();
+ },
+
+ config: config,
+
+ is: is,
+
+ objectType: objectType,
+
+ extend: extend,
+
+ load: function load() {
+ config.pageLoaded = true;
+
+ // Initialize the configuration options
+ extend(config, {
+ stats: { all: 0, bad: 0 },
+ started: 0,
+ updateRate: 1000,
+ autostart: true,
+ filter: ""
+ }, true);
+
+ if (!runStarted) {
+ config.blocking = false;
+
+ if (config.autostart) {
+ scheduleBegin();
+ }
+ }
+ },
+
+ stack: function stack(offset) {
+ offset = (offset || 0) + 2;
+ return sourceFromStacktrace(offset);
+ },
+
+ onError: onError,
+
+ onUnhandledRejection: onUnhandledRejection
+ });
+
+ QUnit.pushFailure = pushFailure;
+ QUnit.assert = Assert.prototype;
+ QUnit.equiv = equiv;
+ QUnit.dump = dump;
+
+ registerLoggingCallbacks(QUnit);
+
+ function scheduleBegin() {
+
+ runStarted = true;
+
+ // Add a slight delay to allow definition of more modules and tests.
+ if (defined.setTimeout) {
+ setTimeout$1(function () {
+ begin();
+ });
+ } else {
+ begin();
+ }
+ }
+
+ function unblockAndAdvanceQueue() {
+ config.blocking = false;
+ ProcessingQueue.advance();
+ }
+
+ function begin() {
+ var i,
+ l,
+ modulesLog = [];
+
+ // If the test run hasn't officially begun yet
+ if (!config.started) {
+
+ // Record the time of the test run's beginning
+ config.started = now();
+
+ // Delete the loose unnamed module if unused.
+ if (config.modules[0].name === "" && config.modules[0].tests.length === 0) {
+ config.modules.shift();
+ }
+
+ // Avoid unnecessary information by not logging modules' test environments
+ for (i = 0, l = config.modules.length; i < l; i++) {
+ modulesLog.push({
+ name: config.modules[i].name,
+ tests: config.modules[i].tests
+ });
+ }
+
+ // The test run is officially beginning now
+ emit("runStart", globalSuite.start(true));
+ runLoggingCallbacks("begin", {
+ totalTests: Test.count,
+ modules: modulesLog
+ }).then(unblockAndAdvanceQueue);
+ } else {
+ unblockAndAdvanceQueue();
+ }
+ }
+
+ exportQUnit(QUnit);
+
+ (function () {
+
+ if (typeof window$1 === "undefined" || typeof document$1 === "undefined") {
+ return;
+ }
+
+ var config = QUnit.config,
+ hasOwn = Object.prototype.hasOwnProperty;
+
+ // Stores fixture HTML for resetting later
+ function storeFixture() {
+
+ // Avoid overwriting user-defined values
+ if (hasOwn.call(config, "fixture")) {
+ return;
+ }
+
+ var fixture = document$1.getElementById("qunit-fixture");
+ if (fixture) {
+ config.fixture = fixture.cloneNode(true);
+ }
+ }
+
+ QUnit.begin(storeFixture);
+
+ // Resets the fixture DOM element if available.
+ function resetFixture() {
+ if (config.fixture == null) {
+ return;
+ }
+
+ var fixture = document$1.getElementById("qunit-fixture");
+ var resetFixtureType = _typeof(config.fixture);
+ if (resetFixtureType === "string") {
+
+ // support user defined values for `config.fixture`
+ var newFixture = document$1.createElement("div");
+ newFixture.setAttribute("id", "qunit-fixture");
+ newFixture.innerHTML = config.fixture;
+ fixture.parentNode.replaceChild(newFixture, fixture);
+ } else {
+ var clonedFixture = config.fixture.cloneNode(true);
+ fixture.parentNode.replaceChild(clonedFixture, fixture);
+ }
+ }
+
+ QUnit.testStart(resetFixture);
+ })();
+
+ (function () {
+
+ // Only interact with URLs via window.location
+ var location = typeof window$1 !== "undefined" && window$1.location;
+ if (!location) {
+ return;
+ }
+
+ var urlParams = getUrlParams();
+
+ QUnit.urlParams = urlParams;
+
+ // Match module/test by inclusion in an array
+ QUnit.config.moduleId = [].concat(urlParams.moduleId || []);
+ QUnit.config.testId = [].concat(urlParams.testId || []);
+
+ // Exact case-insensitive match of the module name
+ QUnit.config.module = urlParams.module;
+
+ // Regular expression or case-insenstive substring match against "moduleName: testName"
+ QUnit.config.filter = urlParams.filter;
+
+ // Test order randomization
+ if (urlParams.seed === true) {
+
+ // Generate a random seed if the option is specified without a value
+ QUnit.config.seed = Math.random().toString(36).slice(2);
+ } else if (urlParams.seed) {
+ QUnit.config.seed = urlParams.seed;
+ }
+
+ // Add URL-parameter-mapped config values with UI form rendering data
+ QUnit.config.urlConfig.push({
+ id: "hidepassed",
+ label: "Hide passed tests",
+ tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+ }, {
+ id: "noglobals",
+ label: "Check for Globals",
+ tooltip: "Enabling this will test if any test introduces new properties on the " + "global object (`window` in Browsers). 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."
+ });
+
+ QUnit.begin(function () {
+ var i,
+ option,
+ urlConfig = QUnit.config.urlConfig;
+
+ for (i = 0; i < urlConfig.length; i++) {
+
+ // Options can be either strings or objects with nonempty "id" properties
+ option = QUnit.config.urlConfig[i];
+ if (typeof option !== "string") {
+ option = option.id;
+ }
+
+ if (QUnit.config[option] === undefined) {
+ QUnit.config[option] = urlParams[option];
+ }
+ }
+ });
+
+ function getUrlParams() {
+ var i, param, name, value;
+ var urlParams = Object.create(null);
+ var params = location.search.slice(1).split("&");
+ var length = params.length;
+
+ for (i = 0; i < length; i++) {
+ if (params[i]) {
+ param = params[i].split("=");
+ name = decodeQueryParam(param[0]);
+
+ // Allow just a key to turn on a flag, e.g., test.html?noglobals
+ value = param.length === 1 || decodeQueryParam(param.slice(1).join("="));
+ if (name in urlParams) {
+ urlParams[name] = [].concat(urlParams[name], value);
+ } else {
+ urlParams[name] = value;
+ }
+ }
+ }
+
+ return urlParams;
+ }
+
+ function decodeQueryParam(param) {
+ return decodeURIComponent(param.replace(/\+/g, "%20"));
+ }
+ })();
+
+ var stats = {
+ passedTests: 0,
+ failedTests: 0,
+ skippedTests: 0,
+ todoTests: 0
+ };
+
+ // Escape text for attribute or text content.
+ function escapeText(s) {
+ if (!s) {
+ return "";
+ }
+ s = s + "";
+
+ // Both single quotes and double quotes (for attributes)
+ return s.replace(/['"<>&]/g, function (s) {
+ switch (s) {
+ case "'":
+ return "'";
+ case "\"":
+ return """;
+ case "<":
+ return "<";
+ case ">":
+ return ">";
+ case "&":
+ return "&";
+ }
+ });
+ }
+
+ (function () {
+
+ // Don't load the HTML Reporter on non-browser environments
+ if (typeof window$1 === "undefined" || !window$1.document) {
+ return;
+ }
+
+ var config = QUnit.config,
+ hiddenTests = [],
+ document = window$1.document,
+ collapseNext = false,
+ hasOwn = Object.prototype.hasOwnProperty,
+ unfilteredUrl = setUrl({ filter: undefined, module: undefined,
+ moduleId: undefined, testId: undefined }),
+ modulesList = [];
+
+ function addEvent(elem, type, fn) {
+ elem.addEventListener(type, fn, false);
+ }
+
+ function removeEvent(elem, type, fn) {
+ elem.removeEventListener(type, fn, false);
+ }
+
+ function addEvents(elems, type, fn) {
+ var i = elems.length;
+ while (i--) {
+ addEvent(elems[i], type, fn);
+ }
+ }
+
+ function hasClass(elem, name) {
+ return (" " + elem.className + " ").indexOf(" " + name + " ") >= 0;
+ }
+
+ function addClass(elem, name) {
+ if (!hasClass(elem, name)) {
+ elem.className += (elem.className ? " " : "") + name;
+ }
+ }
+
+ function toggleClass(elem, name, force) {
+ if (force || typeof force === "undefined" && !hasClass(elem, name)) {
+ addClass(elem, name);
+ } else {
+ removeClass(elem, name);
+ }
+ }
+
+ function removeClass(elem, name) {
+ var set = " " + elem.className + " ";
+
+ // Class name may appear multiple times
+ while (set.indexOf(" " + name + " ") >= 0) {
+ set = set.replace(" " + name + " ", " ");
+ }
+
+ // Trim for prettiness
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
+ }
+
+ function id(name) {
+ return document.getElementById && document.getElementById(name);
+ }
+
+ function abortTests() {
+ var abortButton = id("qunit-abort-tests-button");
+ if (abortButton) {
+ abortButton.disabled = true;
+ abortButton.innerHTML = "Aborting...";
+ }
+ QUnit.config.queue.length = 0;
+ return false;
+ }
+
+ function interceptNavigation(ev) {
+ applyUrlParams();
+
+ if (ev && ev.preventDefault) {
+ ev.preventDefault();
+ }
+
+ return false;
+ }
+
+ function getUrlConfigHtml() {
+ var i,
+ j,
+ val,
+ escaped,
+ escapedTooltip,
+ selection = false,
+ urlConfig = config.urlConfig,
+ urlConfigHtml = "";
+
+ for (i = 0; i < urlConfig.length; i++) {
+
+ // Options can be either strings or objects with nonempty "id" properties
+ val = config.urlConfig[i];
+ if (typeof val === "string") {
+ val = {
+ id: val,
+ label: val
+ };
+ }
+
+ escaped = escapeText(val.id);
+ escapedTooltip = escapeText(val.tooltip);
+
+ if (!val.value || typeof val.value === "string") {
+ urlConfigHtml += "";
+ } else {
+ urlConfigHtml += "";
+ }
+ }
+
+ return urlConfigHtml;
+ }
+
+ // Handle "click" events on toolbar checkboxes and "change" for select menus.
+ // Updates the URL with the new state of `config.urlConfig` values.
+ function toolbarChanged() {
+ var updatedUrl,
+ value,
+ tests,
+ field = this,
+ params = {};
+
+ // Detect if field is a select menu or a checkbox
+ if ("selectedIndex" in field) {
+ value = field.options[field.selectedIndex].value || undefined;
+ } else {
+ value = field.checked ? field.defaultValue || true : undefined;
+ }
+
+ params[field.name] = value;
+ updatedUrl = setUrl(params);
+
+ // Check if we can apply the change without a page refresh
+ if ("hidepassed" === field.name && "replaceState" in window$1.history) {
+ QUnit.urlParams[field.name] = value;
+ config[field.name] = value || false;
+ tests = id("qunit-tests");
+ if (tests) {
+ var length = tests.children.length;
+ var children = tests.children;
+
+ if (field.checked) {
+ for (var i = 0; i < length; i++) {
+ var test = children[i];
+
+ if (test && test.className.indexOf("pass") > -1) {
+ hiddenTests.push(test);
+ }
+ }
+
+ var _iteratorNormalCompletion = true;
+ var _didIteratorError = false;
+ var _iteratorError = undefined;
+
+ try {
+ for (var _iterator = hiddenTests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+ var hiddenTest = _step.value;
+
+ tests.removeChild(hiddenTest);
+ }
+ } catch (err) {
+ _didIteratorError = true;
+ _iteratorError = err;
+ } finally {
+ try {
+ if (!_iteratorNormalCompletion && _iterator.return) {
+ _iterator.return();
+ }
+ } finally {
+ if (_didIteratorError) {
+ throw _iteratorError;
+ }
+ }
+ }
+ } else {
+ while ((test = hiddenTests.pop()) != null) {
+ tests.appendChild(test);
+ }
+ }
+ }
+ window$1.history.replaceState(null, "", updatedUrl);
+ } else {
+ window$1.location = updatedUrl;
+ }
+ }
+
+ function setUrl(params) {
+ var key,
+ arrValue,
+ i,
+ querystring = "?",
+ location = window$1.location;
+
+ params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
+
+ for (key in params) {
+
+ // Skip inherited or undefined properties
+ if (hasOwn.call(params, key) && params[key] !== undefined) {
+
+ // Output a parameter for each value of this key
+ // (but usually just one)
+ arrValue = [].concat(params[key]);
+ for (i = 0; i < arrValue.length; i++) {
+ querystring += encodeURIComponent(key);
+ if (arrValue[i] !== true) {
+ querystring += "=" + encodeURIComponent(arrValue[i]);
+ }
+ querystring += "&";
+ }
+ }
+ }
+ return location.protocol + "//" + location.host + location.pathname + querystring.slice(0, -1);
+ }
+
+ function applyUrlParams() {
+ var i,
+ selectedModules = [],
+ modulesList = id("qunit-modulefilter-dropdown-list").getElementsByTagName("input"),
+ filter = id("qunit-filter-input").value;
+
+ for (i = 0; i < modulesList.length; i++) {
+ if (modulesList[i].checked) {
+ selectedModules.push(modulesList[i].value);
+ }
+ }
+
+ window$1.location = setUrl({
+ filter: filter === "" ? undefined : filter,
+ moduleId: selectedModules.length === 0 ? undefined : selectedModules,
+
+ // Remove module and testId filter
+ module: undefined,
+ testId: undefined
+ });
+ }
+
+ function toolbarUrlConfigContainer() {
+ var urlConfigContainer = document.createElement("span");
+
+ urlConfigContainer.innerHTML = getUrlConfigHtml();
+ addClass(urlConfigContainer, "qunit-url-config");
+
+ addEvents(urlConfigContainer.getElementsByTagName("input"), "change", toolbarChanged);
+ addEvents(urlConfigContainer.getElementsByTagName("select"), "change", toolbarChanged);
+
+ return urlConfigContainer;
+ }
+
+ function abortTestsButton() {
+ var button = document.createElement("button");
+ button.id = "qunit-abort-tests-button";
+ button.innerHTML = "Abort";
+ addEvent(button, "click", abortTests);
+ return button;
+ }
+
+ function toolbarLooseFilter() {
+ var filter = document.createElement("form"),
+ label = document.createElement("label"),
+ input = document.createElement("input"),
+ button = document.createElement("button");
+
+ addClass(filter, "qunit-filter");
+
+ label.innerHTML = "Filter: ";
+
+ input.type = "text";
+ input.value = config.filter || "";
+ input.name = "filter";
+ input.id = "qunit-filter-input";
+
+ button.innerHTML = "Go";
+
+ label.appendChild(input);
+
+ filter.appendChild(label);
+ filter.appendChild(document.createTextNode(" "));
+ filter.appendChild(button);
+ addEvent(filter, "submit", interceptNavigation);
+
+ return filter;
+ }
+
+ function moduleListHtml() {
+ var i,
+ checked,
+ html = "";
+
+ for (i = 0; i < config.modules.length; i++) {
+ if (config.modules[i].name !== "") {
+ checked = config.moduleId.indexOf(config.modules[i].moduleId) > -1;
+ html += "";
+ }
+ }
+
+ return html;
+ }
+
+ function toolbarModuleFilter() {
+ var allCheckbox,
+ commit,
+ reset,
+ moduleFilter = document.createElement("form"),
+ label = document.createElement("label"),
+ moduleSearch = document.createElement("input"),
+ dropDown = document.createElement("div"),
+ actions = document.createElement("span"),
+ dropDownList = document.createElement("ul"),
+ dirty = false;
+
+ moduleSearch.id = "qunit-modulefilter-search";
+ moduleSearch.autocomplete = "off";
+ addEvent(moduleSearch, "input", searchInput);
+ addEvent(moduleSearch, "input", searchFocus);
+ addEvent(moduleSearch, "focus", searchFocus);
+ addEvent(moduleSearch, "click", searchFocus);
+
+ label.id = "qunit-modulefilter-search-container";
+ label.innerHTML = "Module: ";
+ label.appendChild(moduleSearch);
+
+ actions.id = "qunit-modulefilter-actions";
+ actions.innerHTML = "" + "" + "";
+ allCheckbox = actions.lastChild.firstChild;
+ commit = actions.firstChild;
+ reset = commit.nextSibling;
+ addEvent(commit, "click", applyUrlParams);
+
+ dropDownList.id = "qunit-modulefilter-dropdown-list";
+ dropDownList.innerHTML = moduleListHtml();
+
+ dropDown.id = "qunit-modulefilter-dropdown";
+ dropDown.style.display = "none";
+ dropDown.appendChild(actions);
+ dropDown.appendChild(dropDownList);
+ addEvent(dropDown, "change", selectionChange);
+ selectionChange();
+
+ moduleFilter.id = "qunit-modulefilter";
+ moduleFilter.appendChild(label);
+ moduleFilter.appendChild(dropDown);
+ addEvent(moduleFilter, "submit", interceptNavigation);
+ addEvent(moduleFilter, "reset", function () {
+
+ // Let the reset happen, then update styles
+ window$1.setTimeout(selectionChange);
+ });
+
+ // Enables show/hide for the dropdown
+ function searchFocus() {
+ if (dropDown.style.display !== "none") {
+ return;
+ }
+
+ dropDown.style.display = "block";
+ addEvent(document, "click", hideHandler);
+ addEvent(document, "keydown", hideHandler);
+
+ // Hide on Escape keydown or outside-container click
+ function hideHandler(e) {
+ var inContainer = moduleFilter.contains(e.target);
+
+ if (e.keyCode === 27 || !inContainer) {
+ if (e.keyCode === 27 && inContainer) {
+ moduleSearch.focus();
+ }
+ dropDown.style.display = "none";
+ removeEvent(document, "click", hideHandler);
+ removeEvent(document, "keydown", hideHandler);
+ moduleSearch.value = "";
+ searchInput();
+ }
+ }
+ }
+
+ // Processes module search box input
+ function searchInput() {
+ var i,
+ item,
+ searchText = moduleSearch.value.toLowerCase(),
+ listItems = dropDownList.children;
+
+ for (i = 0; i < listItems.length; i++) {
+ item = listItems[i];
+ if (!searchText || item.textContent.toLowerCase().indexOf(searchText) > -1) {
+ item.style.display = "";
+ } else {
+ item.style.display = "none";
+ }
+ }
+ }
+
+ // Processes selection changes
+ function selectionChange(evt) {
+ var i,
+ item,
+ checkbox = evt && evt.target || allCheckbox,
+ modulesList = dropDownList.getElementsByTagName("input"),
+ selectedNames = [];
+
+ toggleClass(checkbox.parentNode, "checked", checkbox.checked);
+
+ dirty = false;
+ if (checkbox.checked && checkbox !== allCheckbox) {
+ allCheckbox.checked = false;
+ removeClass(allCheckbox.parentNode, "checked");
+ }
+ for (i = 0; i < modulesList.length; i++) {
+ item = modulesList[i];
+ if (!evt) {
+ toggleClass(item.parentNode, "checked", item.checked);
+ } else if (checkbox === allCheckbox && checkbox.checked) {
+ item.checked = false;
+ removeClass(item.parentNode, "checked");
+ }
+ dirty = dirty || item.checked !== item.defaultChecked;
+ if (item.checked) {
+ selectedNames.push(item.parentNode.textContent);
+ }
+ }
+
+ commit.style.display = reset.style.display = dirty ? "" : "none";
+ moduleSearch.placeholder = selectedNames.join(", ") || allCheckbox.parentNode.textContent;
+ moduleSearch.title = "Type to filter list. Current selection:\n" + (selectedNames.join("\n") || allCheckbox.parentNode.textContent);
+ }
+
+ return moduleFilter;
+ }
+
+ function appendToolbar() {
+ var toolbar = id("qunit-testrunner-toolbar");
+
+ if (toolbar) {
+ toolbar.appendChild(toolbarUrlConfigContainer());
+ toolbar.appendChild(toolbarModuleFilter());
+ toolbar.appendChild(toolbarLooseFilter());
+ toolbar.appendChild(document.createElement("div")).className = "clearfix";
+ }
+ }
+
+ function appendHeader() {
+ var header = id("qunit-header");
+
+ if (header) {
+ header.innerHTML = "" + header.innerHTML + " ";
+ }
+ }
+
+ function appendBanner() {
+ var banner = id("qunit-banner");
+
+ if (banner) {
+ banner.className = "";
+ }
+ }
+
+ function appendTestResults() {
+ var tests = id("qunit-tests"),
+ result = id("qunit-testresult"),
+ controls;
+
+ if (result) {
+ result.parentNode.removeChild(result);
+ }
+
+ if (tests) {
+ tests.innerHTML = "";
+ result = document.createElement("p");
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore(result, tests);
+ result.innerHTML = "Running...
" + "" + "";
+ controls = id("qunit-testresult-controls");
+ }
+
+ if (controls) {
+ controls.appendChild(abortTestsButton());
+ }
+ }
+
+ function appendFilteredTest() {
+ var testId = QUnit.config.testId;
+ if (!testId || testId.length <= 0) {
+ return "";
+ }
+ return "Rerunning selected tests: " + escapeText(testId.join(", ")) + "
Run all tests ";
+ }
+
+ function appendUserAgent() {
+ var userAgent = id("qunit-userAgent");
+
+ if (userAgent) {
+ userAgent.innerHTML = "";
+ userAgent.appendChild(document.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
+ }
+ }
+
+ function appendInterface() {
+ var qunit = id("qunit");
+
+ if (qunit) {
+ qunit.innerHTML = "" + "" + "" + appendFilteredTest() + "" + "
";
+ }
+
+ appendHeader();
+ appendBanner();
+ appendTestResults();
+ appendUserAgent();
+ appendToolbar();
+ }
+
+ function appendTest(name, testId, moduleName) {
+ var title,
+ rerunTrigger,
+ testBlock,
+ assertList,
+ tests = id("qunit-tests");
+
+ if (!tests) {
+ return;
+ }
+
+ title = document.createElement("strong");
+ title.innerHTML = getNameHtml(name, moduleName);
+
+ rerunTrigger = document.createElement("a");
+ rerunTrigger.innerHTML = "Rerun";
+ rerunTrigger.href = setUrl({ testId: testId });
+
+ testBlock = document.createElement("li");
+ testBlock.appendChild(title);
+ testBlock.appendChild(rerunTrigger);
+ testBlock.id = "qunit-test-output-" + testId;
+
+ assertList = document.createElement("ol");
+ assertList.className = "qunit-assert-list";
+
+ testBlock.appendChild(assertList);
+
+ tests.appendChild(testBlock);
+ }
+
+ // HTML Reporter initialization and load
+ QUnit.begin(function (details) {
+ var i, moduleObj;
+
+ // Sort modules by name for the picker
+ for (i = 0; i < details.modules.length; i++) {
+ moduleObj = details.modules[i];
+ if (moduleObj.name) {
+ modulesList.push(moduleObj.name);
+ }
+ }
+ modulesList.sort(function (a, b) {
+ return a.localeCompare(b);
+ });
+
+ // Initialize QUnit elements
+ appendInterface();
+ });
+
+ QUnit.done(function (details) {
+ var banner = id("qunit-banner"),
+ tests = id("qunit-tests"),
+ abortButton = id("qunit-abort-tests-button"),
+ totalTests = stats.passedTests + stats.skippedTests + stats.todoTests + stats.failedTests,
+ html = [totalTests, " tests completed in ", details.runtime, " milliseconds, with ", stats.failedTests, " failed, ", stats.skippedTests, " skipped, and ", stats.todoTests, " todo.
", "", details.passed, " assertions of ", details.total, " passed, ", details.failed, " failed."].join(""),
+ test,
+ assertLi,
+ assertList;
+
+ // Update remaing tests to aborted
+ if (abortButton && abortButton.disabled) {
+ html = "Tests aborted after " + details.runtime + " milliseconds.";
+
+ for (var i = 0; i < tests.children.length; i++) {
+ test = tests.children[i];
+ if (test.className === "" || test.className === "running") {
+ test.className = "aborted";
+ assertList = test.getElementsByTagName("ol")[0];
+ assertLi = document.createElement("li");
+ assertLi.className = "fail";
+ assertLi.innerHTML = "Test aborted.";
+ assertList.appendChild(assertLi);
+ }
+ }
+ }
+
+ if (banner && (!abortButton || abortButton.disabled === false)) {
+ banner.className = stats.failedTests ? "qunit-fail" : "qunit-pass";
+ }
+
+ if (abortButton) {
+ abortButton.parentNode.removeChild(abortButton);
+ }
+
+ if (tests) {
+ id("qunit-testresult-display").innerHTML = html;
+ }
+
+ if (config.altertitle && document.title) {
+
+ // Show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8
+ // charset
+ document.title = [stats.failedTests ? "\u2716" : "\u2714", document.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
+ }
+
+ // Scroll back to top to show results
+ if (config.scrolltop && window$1.scrollTo) {
+ window$1.scrollTo(0, 0);
+ }
+ });
+
+ function getNameHtml(name, module) {
+ var nameHtml = "";
+
+ if (module) {
+ nameHtml = "" + escapeText(module) + ": ";
+ }
+
+ nameHtml += "" + escapeText(name) + "";
+
+ return nameHtml;
+ }
+
+ QUnit.testStart(function (details) {
+ var running, bad;
+
+ appendTest(details.name, details.testId, details.module);
+
+ running = id("qunit-testresult-display");
+
+ if (running) {
+ addClass(running, "running");
+
+ bad = QUnit.config.reorder && details.previousFailure;
+
+ running.innerHTML = [bad ? "Rerunning previously failed test:
" : "Running:
", getNameHtml(details.name, details.module)].join("");
+ }
+ });
+
+ function stripHtml(string) {
+
+ // Strip tags, html entity and whitespaces
+ return string.replace(/<\/?[^>]+(>|$)/g, "").replace(/"/g, "").replace(/\s+/g, "");
+ }
+
+ QUnit.log(function (details) {
+ var assertList,
+ assertLi,
+ message,
+ expected,
+ actual,
+ diff,
+ showDiff = false,
+ testItem = id("qunit-test-output-" + details.testId);
+
+ if (!testItem) {
+ return;
+ }
+
+ message = escapeText(details.message) || (details.result ? "okay" : "failed");
+ message = "" + message + "";
+ message += "@ " + details.runtime + " ms";
+
+ // The pushFailure doesn't provide details.expected
+ // when it calls, it's implicit to also not show expected and diff stuff
+ // Also, we need to check details.expected existence, as it can exist and be undefined
+ if (!details.result && hasOwn.call(details, "expected")) {
+ if (details.negative) {
+ expected = "NOT " + QUnit.dump.parse(details.expected);
+ } else {
+ expected = QUnit.dump.parse(details.expected);
+ }
+
+ actual = QUnit.dump.parse(details.actual);
+ message += "| Expected: | " + escapeText(expected) + " |
";
+
+ if (actual !== expected) {
+
+ message += "| Result: | " + escapeText(actual) + " |
";
+
+ if (typeof details.actual === "number" && typeof details.expected === "number") {
+ if (!isNaN(details.actual) && !isNaN(details.expected)) {
+ showDiff = true;
+ diff = details.actual - details.expected;
+ diff = (diff > 0 ? "+" : "") + diff;
+ }
+ } else if (typeof details.actual !== "boolean" && typeof details.expected !== "boolean") {
+ diff = QUnit.diff(expected, actual);
+
+ // don't show diff if there is zero overlap
+ showDiff = stripHtml(diff).length !== stripHtml(expected).length + stripHtml(actual).length;
+ }
+
+ if (showDiff) {
+ message += "| Diff: | " + diff + " |
";
+ }
+ } else if (expected.indexOf("[object Array]") !== -1 || expected.indexOf("[object Object]") !== -1) {
+ message += "| Message: | " + "Diff suppressed as the depth of object is more than current max depth (" + QUnit.config.maxDepth + "). Hint: Use QUnit.dump.maxDepth to " + " run with a higher max depth or " + "Rerun without max depth. |
";
+ } else {
+ message += "| Message: | " + "Diff suppressed as the expected and actual results have an equivalent" + " serialization |
";
+ }
+
+ if (details.source) {
+ message += "| Source: | " + escapeText(details.source) + " |
";
+ }
+
+ message += "
";
+
+ // This occurs when pushFailure is set and we have an extracted stack trace
+ } else if (!details.result && details.source) {
+ message += "" + "| Source: | " + escapeText(details.source) + " |
" + "
";
+ }
+
+ assertList = testItem.getElementsByTagName("ol")[0];
+
+ assertLi = document.createElement("li");
+ assertLi.className = details.result ? "pass" : "fail";
+ assertLi.innerHTML = message;
+ assertList.appendChild(assertLi);
+ });
+
+ QUnit.testDone(function (details) {
+ var testTitle,
+ time,
+ testItem,
+ assertList,
+ status,
+ good,
+ bad,
+ testCounts,
+ skipped,
+ sourceName,
+ tests = id("qunit-tests");
+
+ if (!tests) {
+ return;
+ }
+
+ testItem = id("qunit-test-output-" + details.testId);
+
+ removeClass(testItem, "running");
+
+ if (details.failed > 0) {
+ status = "failed";
+ } else if (details.todo) {
+ status = "todo";
+ } else {
+ status = details.skipped ? "skipped" : "passed";
+ }
+
+ assertList = testItem.getElementsByTagName("ol")[0];
+
+ good = details.passed;
+ bad = details.failed;
+
+ // This test passed if it has no unexpected failed assertions
+ var testPassed = details.failed > 0 ? details.todo : !details.todo;
+
+ if (testPassed) {
+
+ // Collapse the passing tests
+ addClass(assertList, "qunit-collapsed");
+ } else if (config.collapse) {
+ if (!collapseNext) {
+
+ // Skip collapsing the first failing test
+ collapseNext = true;
+ } else {
+
+ // Collapse remaining tests
+ addClass(assertList, "qunit-collapsed");
+ }
+ }
+
+ // The testItem.firstChild is the test name
+ testTitle = testItem.firstChild;
+
+ testCounts = bad ? "" + bad + ", " + "" + good + ", " : "";
+
+ testTitle.innerHTML += " (" + testCounts + details.assertions.length + ")";
+
+ if (details.skipped) {
+ stats.skippedTests++;
+
+ testItem.className = "skipped";
+ skipped = document.createElement("em");
+ skipped.className = "qunit-skipped-label";
+ skipped.innerHTML = "skipped";
+ testItem.insertBefore(skipped, testTitle);
+ } else {
+ addEvent(testTitle, "click", function () {
+ toggleClass(assertList, "qunit-collapsed");
+ });
+
+ testItem.className = testPassed ? "pass" : "fail";
+
+ if (details.todo) {
+ var todoLabel = document.createElement("em");
+ todoLabel.className = "qunit-todo-label";
+ todoLabel.innerHTML = "todo";
+ testItem.className += " todo";
+ testItem.insertBefore(todoLabel, testTitle);
+ }
+
+ time = document.createElement("span");
+ time.className = "runtime";
+ time.innerHTML = details.runtime + " ms";
+ testItem.insertBefore(time, assertList);
+
+ if (!testPassed) {
+ stats.failedTests++;
+ } else if (details.todo) {
+ stats.todoTests++;
+ } else {
+ stats.passedTests++;
+ }
+ }
+
+ // Show the source of the test when showing assertions
+ if (details.source) {
+ sourceName = document.createElement("p");
+ sourceName.innerHTML = "Source: " + details.source;
+ addClass(sourceName, "qunit-source");
+ if (testPassed) {
+ addClass(sourceName, "qunit-collapsed");
+ }
+ addEvent(testTitle, "click", function () {
+ toggleClass(sourceName, "qunit-collapsed");
+ });
+ testItem.appendChild(sourceName);
+ }
+
+ if (config.hidepassed && status === "passed") {
+
+ // use removeChild instead of remove because of support
+ hiddenTests.push(testItem);
+
+ tests.removeChild(testItem);
+ }
+ });
+
+ // Avoid readyState issue with phantomjs
+ // Ref: #818
+ var notPhantom = function (p) {
+ return !(p && p.version && p.version.major > 0);
+ }(window$1.phantom);
+
+ if (notPhantom && document.readyState === "complete") {
+ QUnit.load();
+ } else {
+ addEvent(window$1, "load", QUnit.load);
+ }
+
+ // Wrap window.onerror. We will call the original window.onerror to see if
+ // the existing handler fully handles the error; if not, we will call the
+ // QUnit.onError function.
+ var originalWindowOnError = window$1.onerror;
+
+ // Cover uncaught exceptions
+ // Returning true will suppress the default browser handler,
+ // returning false will let it run.
+ window$1.onerror = function (message, fileName, lineNumber, columnNumber, errorObj) {
+ var ret = false;
+ if (originalWindowOnError) {
+ for (var _len = arguments.length, args = Array(_len > 5 ? _len - 5 : 0), _key = 5; _key < _len; _key++) {
+ args[_key - 5] = arguments[_key];
+ }
+
+ ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber, columnNumber, errorObj].concat(args));
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not suppressed.
+ if (ret !== true) {
+ var error = {
+ message: message,
+ fileName: fileName,
+ lineNumber: lineNumber
+ };
+
+ // According to
+ // https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror,
+ // most modern browsers support an errorObj argument; use that to
+ // get a full stack trace if it's available.
+ if (errorObj && errorObj.stack) {
+ error.stacktrace = extractStacktrace(errorObj, 0);
+ }
+
+ ret = QUnit.onError(error);
+ }
+
+ return ret;
+ };
+
+ // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
+ window$1.addEventListener("unhandledrejection", function (event) {
+ QUnit.onUnhandledRejection(event.reason);
+ });
+ })();
+
+ /*
+ * This file is a modified version of google-diff-match-patch's JavaScript implementation
+ * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+ * modifications are licensed as more fully set forth in LICENSE.txt.
+ *
+ * The original source of google-diff-match-patch is attributable and licensed as follows:
+ *
+ * Copyright 2006 Google Inc.
+ * https://code.google.com/p/google-diff-match-patch/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More Info:
+ * https://code.google.com/p/google-diff-match-patch/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ */
+ QUnit.diff = function () {
+ function DiffMatchPatch() {}
+
+ // DIFF FUNCTIONS
+
+ /**
+ * The data structure representing a diff is an array of tuples:
+ * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
+ * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
+ */
+ var DIFF_DELETE = -1,
+ DIFF_INSERT = 1,
+ DIFF_EQUAL = 0;
+
+ /**
+ * Find the differences between two texts. Simplifies the problem by stripping
+ * any common prefix or suffix off the texts before diffing.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+ * then don't run a line-level diff first to identify the changed areas.
+ * Defaults to true, which does a faster, slightly less optimal diff.
+ * @return {!Array.} Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.DiffMain = function (text1, text2, optChecklines) {
+ var deadline, checklines, commonlength, commonprefix, commonsuffix, diffs;
+
+ // The diff must be complete in up to 1 second.
+ deadline = new Date().getTime() + 1000;
+
+ // Check for null inputs.
+ if (text1 === null || text2 === null) {
+ throw new Error("Null input. (DiffMain)");
+ }
+
+ // Check for equality (speedup).
+ if (text1 === text2) {
+ if (text1) {
+ return [[DIFF_EQUAL, text1]];
+ }
+ return [];
+ }
+
+ if (typeof optChecklines === "undefined") {
+ optChecklines = true;
+ }
+
+ checklines = optChecklines;
+
+ // Trim off common prefix (speedup).
+ commonlength = this.diffCommonPrefix(text1, text2);
+ commonprefix = text1.substring(0, commonlength);
+ text1 = text1.substring(commonlength);
+ text2 = text2.substring(commonlength);
+
+ // Trim off common suffix (speedup).
+ commonlength = this.diffCommonSuffix(text1, text2);
+ commonsuffix = text1.substring(text1.length - commonlength);
+ text1 = text1.substring(0, text1.length - commonlength);
+ text2 = text2.substring(0, text2.length - commonlength);
+
+ // Compute the diff on the middle block.
+ diffs = this.diffCompute(text1, text2, checklines, deadline);
+
+ // Restore the prefix and suffix.
+ if (commonprefix) {
+ diffs.unshift([DIFF_EQUAL, commonprefix]);
+ }
+ if (commonsuffix) {
+ diffs.push([DIFF_EQUAL, commonsuffix]);
+ }
+ this.diffCleanupMerge(diffs);
+ return diffs;
+ };
+
+ /**
+ * Reduce the number of edits by eliminating operationally trivial equalities.
+ * @param {!Array.} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupEfficiency = function (diffs) {
+ var changes, equalities, equalitiesLength, lastequality, pointer, preIns, preDel, postIns, postDel;
+ changes = false;
+ equalities = []; // Stack of indices where equalities are found.
+ equalitiesLength = 0; // Keeping our own length var is faster in JS.
+ /** @type {?string} */
+ lastequality = null;
+
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+ pointer = 0; // Index of current position.
+
+ // Is there an insertion operation before the last equality.
+ preIns = false;
+
+ // Is there a deletion operation before the last equality.
+ preDel = false;
+
+ // Is there an insertion operation after the last equality.
+ postIns = false;
+
+ // Is there a deletion operation after the last equality.
+ postDel = false;
+ while (pointer < diffs.length) {
+
+ // Equality found.
+ if (diffs[pointer][0] === DIFF_EQUAL) {
+ if (diffs[pointer][1].length < 4 && (postIns || postDel)) {
+
+ // Candidate found.
+ equalities[equalitiesLength++] = pointer;
+ preIns = postIns;
+ preDel = postDel;
+ lastequality = diffs[pointer][1];
+ } else {
+
+ // Not a candidate, and can never become one.
+ equalitiesLength = 0;
+ lastequality = null;
+ }
+ postIns = postDel = false;
+
+ // An insertion or deletion.
+ } else {
+
+ if (diffs[pointer][0] === DIFF_DELETE) {
+ postDel = true;
+ } else {
+ postIns = true;
+ }
+
+ /*
+ * Five types to be split:
+ * ABXYCD
+ * AXCD
+ * ABXC
+ * AXCD
+ * ABXC
+ */
+ if (lastequality && (preIns && preDel && postIns && postDel || lastequality.length < 2 && preIns + preDel + postIns + postDel === 3)) {
+
+ // Duplicate record.
+ diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
+
+ // Change second copy to insert.
+ diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+ equalitiesLength--; // Throw away the equality we just deleted;
+ lastequality = null;
+ if (preIns && preDel) {
+
+ // No changes made which could affect previous entry, keep going.
+ postIns = postDel = true;
+ equalitiesLength = 0;
+ } else {
+ equalitiesLength--; // Throw away the previous equality.
+ pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+ postIns = postDel = false;
+ }
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+ };
+
+ /**
+ * Convert a diff array into a pretty HTML report.
+ * @param {!Array.} diffs Array of diff tuples.
+ * @param {integer} string to be beautified.
+ * @return {string} HTML representation.
+ */
+ DiffMatchPatch.prototype.diffPrettyHtml = function (diffs) {
+ var op,
+ data,
+ x,
+ html = [];
+ for (x = 0; x < diffs.length; x++) {
+ op = diffs[x][0]; // Operation (insert, delete, equal)
+ data = diffs[x][1]; // Text of change.
+ switch (op) {
+ case DIFF_INSERT:
+ html[x] = "" + escapeText(data) + "";
+ break;
+ case DIFF_DELETE:
+ html[x] = "" + escapeText(data) + "";
+ break;
+ case DIFF_EQUAL:
+ html[x] = "" + escapeText(data) + "";
+ break;
+ }
+ }
+ return html.join("");
+ };
+
+ /**
+ * Determine the common prefix of two strings.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the start of each
+ * string.
+ */
+ DiffMatchPatch.prototype.diffCommonPrefix = function (text1, text2) {
+ var pointermid, pointermax, pointermin, pointerstart;
+
+ // Quick check for common null cases.
+ if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
+ return 0;
+ }
+
+ // Binary search.
+ // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+ pointermin = 0;
+ pointermax = Math.min(text1.length, text2.length);
+ pointermid = pointermax;
+ pointerstart = 0;
+ while (pointermin < pointermid) {
+ if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) {
+ pointermin = pointermid;
+ pointerstart = pointermin;
+ } else {
+ pointermax = pointermid;
+ }
+ pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
+ }
+ return pointermid;
+ };
+
+ /**
+ * Determine the common suffix of two strings.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the end of each string.
+ */
+ DiffMatchPatch.prototype.diffCommonSuffix = function (text1, text2) {
+ var pointermid, pointermax, pointermin, pointerend;
+
+ // Quick check for common null cases.
+ if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
+ return 0;
+ }
+
+ // Binary search.
+ // Performance analysis: https://neil.fraser.name/news/2007/10/09/
+ pointermin = 0;
+ pointermax = Math.min(text1.length, text2.length);
+ pointermid = pointermax;
+ pointerend = 0;
+ while (pointermin < pointermid) {
+ if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) {
+ pointermin = pointermid;
+ pointerend = pointermin;
+ } else {
+ pointermax = pointermid;
+ }
+ pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
+ }
+ return pointermid;
+ };
+
+ /**
+ * Find the differences between two texts. Assumes that the texts do not
+ * have any common prefix or suffix.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {boolean} checklines Speedup flag. If false, then don't run a
+ * line-level diff first to identify the changed areas.
+ * If true, then run a faster, slightly less optimal diff.
+ * @param {number} deadline Time when the diff should be complete by.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCompute = function (text1, text2, checklines, deadline) {
+ var diffs, longtext, shorttext, i, hm, text1A, text2A, text1B, text2B, midCommon, diffsA, diffsB;
+
+ if (!text1) {
+
+ // Just add some text (speedup).
+ return [[DIFF_INSERT, text2]];
+ }
+
+ if (!text2) {
+
+ // Just delete some text (speedup).
+ return [[DIFF_DELETE, text1]];
+ }
+
+ longtext = text1.length > text2.length ? text1 : text2;
+ shorttext = text1.length > text2.length ? text2 : text1;
+ i = longtext.indexOf(shorttext);
+ if (i !== -1) {
+
+ // Shorter text is inside the longer text (speedup).
+ diffs = [[DIFF_INSERT, longtext.substring(0, i)], [DIFF_EQUAL, shorttext], [DIFF_INSERT, longtext.substring(i + shorttext.length)]];
+
+ // Swap insertions for deletions if diff is reversed.
+ if (text1.length > text2.length) {
+ diffs[0][0] = diffs[2][0] = DIFF_DELETE;
+ }
+ return diffs;
+ }
+
+ if (shorttext.length === 1) {
+
+ // Single character string.
+ // After the previous speedup, the character can't be an equality.
+ return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
+ }
+
+ // Check to see if the problem can be split in two.
+ hm = this.diffHalfMatch(text1, text2);
+ if (hm) {
+
+ // A half-match was found, sort out the return data.
+ text1A = hm[0];
+ text1B = hm[1];
+ text2A = hm[2];
+ text2B = hm[3];
+ midCommon = hm[4];
+
+ // Send both pairs off for separate processing.
+ diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
+ diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
+
+ // Merge the results.
+ return diffsA.concat([[DIFF_EQUAL, midCommon]], diffsB);
+ }
+
+ if (checklines && text1.length > 100 && text2.length > 100) {
+ return this.diffLineMode(text1, text2, deadline);
+ }
+
+ return this.diffBisect(text1, text2, deadline);
+ };
+
+ /**
+ * Do the two texts share a substring which is at least half the length of the
+ * longer text?
+ * This speedup can produce non-minimal diffs.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {Array.} Five element Array, containing the prefix of
+ * text1, the suffix of text1, the prefix of text2, the suffix of
+ * text2 and the common middle. Or null if there was no match.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffHalfMatch = function (text1, text2) {
+ var longtext, shorttext, dmp, text1A, text2B, text2A, text1B, midCommon, hm1, hm2, hm;
+
+ longtext = text1.length > text2.length ? text1 : text2;
+ shorttext = text1.length > text2.length ? text2 : text1;
+ if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
+ return null; // Pointless.
+ }
+ dmp = this; // 'this' becomes 'window' in a closure.
+
+ /**
+ * Does a substring of shorttext exist within longtext such that the substring
+ * is at least half the length of longtext?
+ * Closure, but does not reference any external variables.
+ * @param {string} longtext Longer string.
+ * @param {string} shorttext Shorter string.
+ * @param {number} i Start index of quarter length substring within longtext.
+ * @return {Array.} Five element Array, containing the prefix of
+ * longtext, the suffix of longtext, the prefix of shorttext, the suffix
+ * of shorttext and the common middle. Or null if there was no match.
+ * @private
+ */
+ function diffHalfMatchI(longtext, shorttext, i) {
+ var seed, j, bestCommon, prefixLength, suffixLength, bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
+
+ // Start with a 1/4 length substring at position i as a seed.
+ seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
+ j = -1;
+ bestCommon = "";
+ while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
+ prefixLength = dmp.diffCommonPrefix(longtext.substring(i), shorttext.substring(j));
+ suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
+ if (bestCommon.length < suffixLength + prefixLength) {
+ bestCommon = shorttext.substring(j - suffixLength, j) + shorttext.substring(j, j + prefixLength);
+ bestLongtextA = longtext.substring(0, i - suffixLength);
+ bestLongtextB = longtext.substring(i + prefixLength);
+ bestShorttextA = shorttext.substring(0, j - suffixLength);
+ bestShorttextB = shorttext.substring(j + prefixLength);
+ }
+ }
+ if (bestCommon.length * 2 >= longtext.length) {
+ return [bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB, bestCommon];
+ } else {
+ return null;
+ }
+ }
+
+ // First check if the second quarter is the seed for a half-match.
+ hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
+
+ // Check again based on the third quarter.
+ hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
+ if (!hm1 && !hm2) {
+ return null;
+ } else if (!hm2) {
+ hm = hm1;
+ } else if (!hm1) {
+ hm = hm2;
+ } else {
+
+ // Both matched. Select the longest.
+ hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
+ }
+
+ // A half-match was found, sort out the return data.
+ if (text1.length > text2.length) {
+ text1A = hm[0];
+ text1B = hm[1];
+ text2A = hm[2];
+ text2B = hm[3];
+ } else {
+ text2A = hm[0];
+ text2B = hm[1];
+ text1A = hm[2];
+ text1B = hm[3];
+ }
+ midCommon = hm[4];
+ return [text1A, text1B, text2A, text2B, midCommon];
+ };
+
+ /**
+ * Do a quick line-level diff on both strings, then rediff the parts for
+ * greater accuracy.
+ * This speedup can produce non-minimal diffs.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} deadline Time when the diff should be complete by.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffLineMode = function (text1, text2, deadline) {
+ var a, diffs, linearray, pointer, countInsert, countDelete, textInsert, textDelete, j;
+
+ // Scan the text on a line-by-line basis first.
+ a = this.diffLinesToChars(text1, text2);
+ text1 = a.chars1;
+ text2 = a.chars2;
+ linearray = a.lineArray;
+
+ diffs = this.DiffMain(text1, text2, false, deadline);
+
+ // Convert the diff back to original text.
+ this.diffCharsToLines(diffs, linearray);
+
+ // Eliminate freak matches (e.g. blank lines)
+ this.diffCleanupSemantic(diffs);
+
+ // Rediff any replacement blocks, this time character-by-character.
+ // Add a dummy entry at the end.
+ diffs.push([DIFF_EQUAL, ""]);
+ pointer = 0;
+ countDelete = 0;
+ countInsert = 0;
+ textDelete = "";
+ textInsert = "";
+ while (pointer < diffs.length) {
+ switch (diffs[pointer][0]) {
+ case DIFF_INSERT:
+ countInsert++;
+ textInsert += diffs[pointer][1];
+ break;
+ case DIFF_DELETE:
+ countDelete++;
+ textDelete += diffs[pointer][1];
+ break;
+ case DIFF_EQUAL:
+
+ // Upon reaching an equality, check for prior redundancies.
+ if (countDelete >= 1 && countInsert >= 1) {
+
+ // Delete the offending records and add the merged ones.
+ diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert);
+ pointer = pointer - countDelete - countInsert;
+ a = this.DiffMain(textDelete, textInsert, false, deadline);
+ for (j = a.length - 1; j >= 0; j--) {
+ diffs.splice(pointer, 0, a[j]);
+ }
+ pointer = pointer + a.length;
+ }
+ countInsert = 0;
+ countDelete = 0;
+ textDelete = "";
+ textInsert = "";
+ break;
+ }
+ pointer++;
+ }
+ diffs.pop(); // Remove the dummy entry at the end.
+
+ return diffs;
+ };
+
+ /**
+ * Find the 'middle snake' of a diff, split the problem in two
+ * and return the recursively constructed diff.
+ * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} deadline Time at which to bail if not yet complete.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffBisect = function (text1, text2, deadline) {
+ var text1Length, text2Length, maxD, vOffset, vLength, v1, v2, x, delta, front, k1start, k1end, k2start, k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
+
+ // Cache the text lengths to prevent multiple calls.
+ text1Length = text1.length;
+ text2Length = text2.length;
+ maxD = Math.ceil((text1Length + text2Length) / 2);
+ vOffset = maxD;
+ vLength = 2 * maxD;
+ v1 = new Array(vLength);
+ v2 = new Array(vLength);
+
+ // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+ // integers and undefined.
+ for (x = 0; x < vLength; x++) {
+ v1[x] = -1;
+ v2[x] = -1;
+ }
+ v1[vOffset + 1] = 0;
+ v2[vOffset + 1] = 0;
+ delta = text1Length - text2Length;
+
+ // If the total number of characters is odd, then the front path will collide
+ // with the reverse path.
+ front = delta % 2 !== 0;
+
+ // Offsets for start and end of k loop.
+ // Prevents mapping of space beyond the grid.
+ k1start = 0;
+ k1end = 0;
+ k2start = 0;
+ k2end = 0;
+ for (d = 0; d < maxD; d++) {
+
+ // Bail out if deadline is reached.
+ if (new Date().getTime() > deadline) {
+ break;
+ }
+
+ // Walk the front path one step.
+ for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
+ k1Offset = vOffset + k1;
+ if (k1 === -d || k1 !== d && v1[k1Offset - 1] < v1[k1Offset + 1]) {
+ x1 = v1[k1Offset + 1];
+ } else {
+ x1 = v1[k1Offset - 1] + 1;
+ }
+ y1 = x1 - k1;
+ while (x1 < text1Length && y1 < text2Length && text1.charAt(x1) === text2.charAt(y1)) {
+ x1++;
+ y1++;
+ }
+ v1[k1Offset] = x1;
+ if (x1 > text1Length) {
+
+ // Ran off the right of the graph.
+ k1end += 2;
+ } else if (y1 > text2Length) {
+
+ // Ran off the bottom of the graph.
+ k1start += 2;
+ } else if (front) {
+ k2Offset = vOffset + delta - k1;
+ if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
+
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1Length - v2[k2Offset];
+ if (x1 >= x2) {
+
+ // Overlap detected.
+ return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+
+ // Walk the reverse path one step.
+ for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
+ k2Offset = vOffset + k2;
+ if (k2 === -d || k2 !== d && v2[k2Offset - 1] < v2[k2Offset + 1]) {
+ x2 = v2[k2Offset + 1];
+ } else {
+ x2 = v2[k2Offset - 1] + 1;
+ }
+ y2 = x2 - k2;
+ while (x2 < text1Length && y2 < text2Length && text1.charAt(text1Length - x2 - 1) === text2.charAt(text2Length - y2 - 1)) {
+ x2++;
+ y2++;
+ }
+ v2[k2Offset] = x2;
+ if (x2 > text1Length) {
+
+ // Ran off the left of the graph.
+ k2end += 2;
+ } else if (y2 > text2Length) {
+
+ // Ran off the top of the graph.
+ k2start += 2;
+ } else if (!front) {
+ k1Offset = vOffset + delta - k2;
+ if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
+ x1 = v1[k1Offset];
+ y1 = vOffset + x1 - k1Offset;
+
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1Length - x2;
+ if (x1 >= x2) {
+
+ // Overlap detected.
+ return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+ }
+
+ // Diff took too long and hit the deadline or
+ // number of diffs equals number of characters, no commonality at all.
+ return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]];
+ };
+
+ /**
+ * Given the location of the 'middle snake', split the diff in two parts
+ * and recurse.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} x Index of split point in text1.
+ * @param {number} y Index of split point in text2.
+ * @param {number} deadline Time at which to bail if not yet complete.
+ * @return {!Array.} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffBisectSplit = function (text1, text2, x, y, deadline) {
+ var text1a, text1b, text2a, text2b, diffs, diffsb;
+ text1a = text1.substring(0, x);
+ text2a = text2.substring(0, y);
+ text1b = text1.substring(x);
+ text2b = text2.substring(y);
+
+ // Compute both diffs serially.
+ diffs = this.DiffMain(text1a, text2a, false, deadline);
+ diffsb = this.DiffMain(text1b, text2b, false, deadline);
+
+ return diffs.concat(diffsb);
+ };
+
+ /**
+ * Reduce the number of edits by eliminating semantically trivial equalities.
+ * @param {!Array.} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupSemantic = function (diffs) {
+ var changes, equalities, equalitiesLength, lastequality, pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1, lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+ changes = false;
+ equalities = []; // Stack of indices where equalities are found.
+ equalitiesLength = 0; // Keeping our own length var is faster in JS.
+ /** @type {?string} */
+ lastequality = null;
+
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+ pointer = 0; // Index of current position.
+
+ // Number of characters that changed prior to the equality.
+ lengthInsertions1 = 0;
+ lengthDeletions1 = 0;
+
+ // Number of characters that changed after the equality.
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ while (pointer < diffs.length) {
+ if (diffs[pointer][0] === DIFF_EQUAL) {
+ // Equality found.
+ equalities[equalitiesLength++] = pointer;
+ lengthInsertions1 = lengthInsertions2;
+ lengthDeletions1 = lengthDeletions2;
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ lastequality = diffs[pointer][1];
+ } else {
+ // An insertion or deletion.
+ if (diffs[pointer][0] === DIFF_INSERT) {
+ lengthInsertions2 += diffs[pointer][1].length;
+ } else {
+ lengthDeletions2 += diffs[pointer][1].length;
+ }
+
+ // Eliminate an equality that is smaller or equal to the edits on both
+ // sides of it.
+ if (lastequality && lastequality.length <= Math.max(lengthInsertions1, lengthDeletions1) && lastequality.length <= Math.max(lengthInsertions2, lengthDeletions2)) {
+
+ // Duplicate record.
+ diffs.splice(equalities[equalitiesLength - 1], 0, [DIFF_DELETE, lastequality]);
+
+ // Change second copy to insert.
+ diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+
+ // Throw away the equality we just deleted.
+ equalitiesLength--;
+
+ // Throw away the previous equality (it needs to be reevaluated).
+ equalitiesLength--;
+ pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+
+ // Reset the counters.
+ lengthInsertions1 = 0;
+ lengthDeletions1 = 0;
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ lastequality = null;
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ // Normalize the diff.
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+
+ // Find any overlaps between deletions and insertions.
+ // e.g: abcxxxxxxdef
+ // -> abcxxxdef
+ // e.g: xxxabcdefxxx
+ // -> defxxxabc
+ // Only extract an overlap if it is as big as the edit ahead or behind it.
+ pointer = 1;
+ while (pointer < diffs.length) {
+ if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
+ deletion = diffs[pointer - 1][1];
+ insertion = diffs[pointer][1];
+ overlapLength1 = this.diffCommonOverlap(deletion, insertion);
+ overlapLength2 = this.diffCommonOverlap(insertion, deletion);
+ if (overlapLength1 >= overlapLength2) {
+ if (overlapLength1 >= deletion.length / 2 || overlapLength1 >= insertion.length / 2) {
+
+ // Overlap found. Insert an equality and trim the surrounding edits.
+ diffs.splice(pointer, 0, [DIFF_EQUAL, insertion.substring(0, overlapLength1)]);
+ diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlapLength1);
+ diffs[pointer + 1][1] = insertion.substring(overlapLength1);
+ pointer++;
+ }
+ } else {
+ if (overlapLength2 >= deletion.length / 2 || overlapLength2 >= insertion.length / 2) {
+
+ // Reverse overlap found.
+ // Insert an equality and swap and trim the surrounding edits.
+ diffs.splice(pointer, 0, [DIFF_EQUAL, deletion.substring(0, overlapLength2)]);
+
+ diffs[pointer - 1][0] = DIFF_INSERT;
+ diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlapLength2);
+ diffs[pointer + 1][0] = DIFF_DELETE;
+ diffs[pointer + 1][1] = deletion.substring(overlapLength2);
+ pointer++;
+ }
+ }
+ pointer++;
+ }
+ pointer++;
+ }
+ };
+
+ /**
+ * Determine if the suffix of one string is the prefix of another.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the end of the first
+ * string and the start of the second string.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCommonOverlap = function (text1, text2) {
+ var text1Length, text2Length, textLength, best, length, pattern, found;
+
+ // Cache the text lengths to prevent multiple calls.
+ text1Length = text1.length;
+ text2Length = text2.length;
+
+ // Eliminate the null case.
+ if (text1Length === 0 || text2Length === 0) {
+ return 0;
+ }
+
+ // Truncate the longer string.
+ if (text1Length > text2Length) {
+ text1 = text1.substring(text1Length - text2Length);
+ } else if (text1Length < text2Length) {
+ text2 = text2.substring(0, text1Length);
+ }
+ textLength = Math.min(text1Length, text2Length);
+
+ // Quick check for the worst case.
+ if (text1 === text2) {
+ return textLength;
+ }
+
+ // Start by looking for a single character match
+ // and increase length until no match is found.
+ // Performance analysis: https://neil.fraser.name/news/2010/11/04/
+ best = 0;
+ length = 1;
+ while (true) {
+ pattern = text1.substring(textLength - length);
+ found = text2.indexOf(pattern);
+ if (found === -1) {
+ return best;
+ }
+ length += found;
+ if (found === 0 || text1.substring(textLength - length) === text2.substring(0, length)) {
+ best = length;
+ length++;
+ }
+ }
+ };
+
+ /**
+ * Split two texts into an array of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {{chars1: string, chars2: string, lineArray: !Array.}}
+ * An object containing the encoded text1, the encoded text2 and
+ * the array of unique strings.
+ * The zeroth element of the array of unique strings is intentionally blank.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffLinesToChars = function (text1, text2) {
+ var lineArray, lineHash, chars1, chars2;
+ lineArray = []; // E.g. lineArray[4] === 'Hello\n'
+ lineHash = {}; // E.g. lineHash['Hello\n'] === 4
+
+ // '\x00' is a valid character, but various debuggers don't like it.
+ // So we'll insert a junk entry to avoid generating a null character.
+ lineArray[0] = "";
+
+ /**
+ * Split a text into an array of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ * Modifies linearray and linehash through being a closure.
+ * @param {string} text String to encode.
+ * @return {string} Encoded string.
+ * @private
+ */
+ function diffLinesToCharsMunge(text) {
+ var chars, lineStart, lineEnd, lineArrayLength, line;
+ chars = "";
+
+ // Walk the text, pulling out a substring for each line.
+ // text.split('\n') would would temporarily double our memory footprint.
+ // Modifying text would create many large strings to garbage collect.
+ lineStart = 0;
+ lineEnd = -1;
+
+ // Keeping our own length variable is faster than looking it up.
+ lineArrayLength = lineArray.length;
+ while (lineEnd < text.length - 1) {
+ lineEnd = text.indexOf("\n", lineStart);
+ if (lineEnd === -1) {
+ lineEnd = text.length - 1;
+ }
+ line = text.substring(lineStart, lineEnd + 1);
+ lineStart = lineEnd + 1;
+
+ var lineHashExists = lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : lineHash[line] !== undefined;
+
+ if (lineHashExists) {
+ chars += String.fromCharCode(lineHash[line]);
+ } else {
+ chars += String.fromCharCode(lineArrayLength);
+ lineHash[line] = lineArrayLength;
+ lineArray[lineArrayLength++] = line;
+ }
+ }
+ return chars;
+ }
+
+ chars1 = diffLinesToCharsMunge(text1);
+ chars2 = diffLinesToCharsMunge(text2);
+ return {
+ chars1: chars1,
+ chars2: chars2,
+ lineArray: lineArray
+ };
+ };
+
+ /**
+ * Rehydrate the text in a diff from a string of line hashes to real lines of
+ * text.
+ * @param {!Array.} diffs Array of diff tuples.
+ * @param {!Array.} lineArray Array of unique strings.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCharsToLines = function (diffs, lineArray) {
+ var x, chars, text, y;
+ for (x = 0; x < diffs.length; x++) {
+ chars = diffs[x][1];
+ text = [];
+ for (y = 0; y < chars.length; y++) {
+ text[y] = lineArray[chars.charCodeAt(y)];
+ }
+ diffs[x][1] = text.join("");
+ }
+ };
+
+ /**
+ * Reorder and merge like edit sections. Merge equalities.
+ * Any edit section can move as long as it doesn't cross an equality.
+ * @param {!Array.} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupMerge = function (diffs) {
+ var pointer, countDelete, countInsert, textInsert, textDelete, commonlength, changes, diffPointer, position;
+ diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end.
+ pointer = 0;
+ countDelete = 0;
+ countInsert = 0;
+ textDelete = "";
+ textInsert = "";
+
+ while (pointer < diffs.length) {
+ switch (diffs[pointer][0]) {
+ case DIFF_INSERT:
+ countInsert++;
+ textInsert += diffs[pointer][1];
+ pointer++;
+ break;
+ case DIFF_DELETE:
+ countDelete++;
+ textDelete += diffs[pointer][1];
+ pointer++;
+ break;
+ case DIFF_EQUAL:
+
+ // Upon reaching an equality, check for prior redundancies.
+ if (countDelete + countInsert > 1) {
+ if (countDelete !== 0 && countInsert !== 0) {
+
+ // Factor out any common prefixes.
+ commonlength = this.diffCommonPrefix(textInsert, textDelete);
+ if (commonlength !== 0) {
+ if (pointer - countDelete - countInsert > 0 && diffs[pointer - countDelete - countInsert - 1][0] === DIFF_EQUAL) {
+ diffs[pointer - countDelete - countInsert - 1][1] += textInsert.substring(0, commonlength);
+ } else {
+ diffs.splice(0, 0, [DIFF_EQUAL, textInsert.substring(0, commonlength)]);
+ pointer++;
+ }
+ textInsert = textInsert.substring(commonlength);
+ textDelete = textDelete.substring(commonlength);
+ }
+
+ // Factor out any common suffixies.
+ commonlength = this.diffCommonSuffix(textInsert, textDelete);
+ if (commonlength !== 0) {
+ diffs[pointer][1] = textInsert.substring(textInsert.length - commonlength) + diffs[pointer][1];
+ textInsert = textInsert.substring(0, textInsert.length - commonlength);
+ textDelete = textDelete.substring(0, textDelete.length - commonlength);
+ }
+ }
+
+ // Delete the offending records and add the merged ones.
+ if (countDelete === 0) {
+ diffs.splice(pointer - countInsert, countDelete + countInsert, [DIFF_INSERT, textInsert]);
+ } else if (countInsert === 0) {
+ diffs.splice(pointer - countDelete, countDelete + countInsert, [DIFF_DELETE, textDelete]);
+ } else {
+ diffs.splice(pointer - countDelete - countInsert, countDelete + countInsert, [DIFF_DELETE, textDelete], [DIFF_INSERT, textInsert]);
+ }
+ pointer = pointer - countDelete - countInsert + (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
+ } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
+
+ // Merge this equality with the previous one.
+ diffs[pointer - 1][1] += diffs[pointer][1];
+ diffs.splice(pointer, 1);
+ } else {
+ pointer++;
+ }
+ countInsert = 0;
+ countDelete = 0;
+ textDelete = "";
+ textInsert = "";
+ break;
+ }
+ }
+ if (diffs[diffs.length - 1][1] === "") {
+ diffs.pop(); // Remove the dummy entry at the end.
+ }
+
+ // Second pass: look for single edits surrounded on both sides by equalities
+ // which can be shifted sideways to eliminate an equality.
+ // e.g: ABAC -> ABAC
+ changes = false;
+ pointer = 1;
+
+ // Intentionally ignore the first and last element (don't need checking).
+ while (pointer < diffs.length - 1) {
+ if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
+
+ diffPointer = diffs[pointer][1];
+ position = diffPointer.substring(diffPointer.length - diffs[pointer - 1][1].length);
+
+ // This is a single edit surrounded by equalities.
+ if (position === diffs[pointer - 1][1]) {
+
+ // Shift the edit over the previous equality.
+ diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
+ diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
+ diffs.splice(pointer - 1, 1);
+ changes = true;
+ } else if (diffPointer.substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
+
+ // Shift the edit over the next equality.
+ diffs[pointer - 1][1] += diffs[pointer + 1][1];
+ diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
+ diffs.splice(pointer + 1, 1);
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ // If shifts were made, the diff needs reordering and another shift sweep.
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+ };
+
+ return function (o, n) {
+ var diff, output, text;
+ diff = new DiffMatchPatch();
+ output = diff.DiffMain(o, n);
+ diff.diffCleanupEfficiency(output);
+ text = diff.diffPrettyHtml(output);
+
+ return text;
+ };
+ }();
+
+}((function() { return this; }())));
diff --git a/test/tests-extensions.js b/test/tests-extensions.js
index f0e5994..5a0b8ea 100644
--- a/test/tests-extensions.js
+++ b/test/tests-extensions.js
@@ -1,10 +1,12 @@
/*jshint -W024 */
/*jshint -W117 */
-module("extensions");
+QUnit.module("extensions");
-test("String.resolve basic (one dimension) test", 1, function ()
+QUnit.test("String.resolve basic (one dimension) test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var values = {
first: "test",
@@ -16,11 +18,13 @@ test("String.resolve basic (one dimension) test", 1, function ()
var result = stringToResolve.resolve(values);
// then
- equal(result, "test case", "Valid string");
+ assert.equal(result, "test case", "Valid string");
});
-test("String.resolve advanced (n dimension) test", 1, function ()
+QUnit.test("String.resolve advanced (n dimension) test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var values = {
first: {
@@ -41,5 +45,5 @@ test("String.resolve advanced (n dimension) test", 1, function ()
var result = stringToResolve.resolve(values);
// then
- equal(result, "this is a more advanced test case", "Valid string");
+ assert.equal(result, "this is a more advanced test case", "Valid string");
});
\ No newline at end of file
diff --git a/test/tests-internal.js b/test/tests-internal.js
index dea9eb6..cf46e5d 100644
--- a/test/tests-internal.js
+++ b/test/tests-internal.js
@@ -1,19 +1,21 @@
/*jshint -W024 */
/*jshint -W117 */
-module("internal functions", {
- setup: function ()
+QUnit.module("internal functions", {
+ beforeEach: function ()
{
$("#qunit-fixture").html("");
},
- teardown: function ()
+ afterEach: function ()
{
$("#qunit-fixture").empty();
}
});
-test("findFooterAndHeaderItems test", 1, function ()
+QUnit.test("findFooterAndHeaderItems test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var instance = {
footer: $("#test > tfoot"),
@@ -25,11 +27,13 @@ test("findFooterAndHeaderItems test", 1, function ()
var result = findFooterAndHeaderItems.call(instance, selector);
// then
- equal(result.length, 2, "Found two elements as expected");
+ assert.equal(result.length, 2, "Found two elements as expected");
});
-test("findFooterAndHeaderItems test (footer is null)", 1, function ()
+QUnit.test("findFooterAndHeaderItems test (footer is null)", function ( assert )
{
+ assert.expect( 1 );
+
// given
var instance = {
footer: null,
@@ -41,11 +45,13 @@ test("findFooterAndHeaderItems test (footer is null)", 1, function ()
var result = findFooterAndHeaderItems.call(instance, selector);
// then
- equal(result.length, 1, "Found one element as expected");
+ assert.equal(result.length, 1, "Found one element as expected");
});
-test("findFooterAndHeaderItems test (header is null)", 1, function ()
+QUnit.test("findFooterAndHeaderItems test (header is null)", function ( assert )
{
+ assert.expect( 1 );
+
// given
var instance = {
footer: $("#test > tfoot"),
@@ -57,11 +63,13 @@ test("findFooterAndHeaderItems test (header is null)", 1, function ()
var result = findFooterAndHeaderItems.call(instance, selector);
// then
- equal(result.length, 1, "Found one element as expected");
+ assert.equal(result.length, 1, "Found one element as expected");
});
-test("findFooterAndHeaderItems test (footer and header is string empty)", 2, function ()
+QUnit.test("findFooterAndHeaderItems test (footer and header is string empty)", function ( assert )
{
+ assert.expect( 2 );
+
// given
var instance = {
footer: "",
@@ -73,12 +81,14 @@ test("findFooterAndHeaderItems test (footer and header is string empty)", 2, fun
var result = findFooterAndHeaderItems.call(instance, selector);
// then
- equal(result.length, 0, "Foundd one element as expecte");
- ok(result.find, "Got an empty jQuery array as expected");
+ assert.equal(result.length, 0, "Found one element as expected");
+ assert.ok(result.find, "Got an empty jQuery array as expected");
});
-test("findFooterAndHeaderItems test (footer and header is null)", 2, function ()
+QUnit.test("findFooterAndHeaderItems test (footer and header is null)", function ( assert )
{
+ assert.expect( 2 );
+
// given
var instance = {
footer: null,
@@ -90,12 +100,14 @@ test("findFooterAndHeaderItems test (footer and header is null)", 2, function ()
var result = findFooterAndHeaderItems.call(instance, selector);
// then
- equal(result.length, 0, "Found no elements as expected");
- ok(result.find, "Got an empty jQuery array as expected");
+ assert.equal(result.length, 0, "Found no elements as expected");
+ assert.ok(result.find, "Got an empty jQuery array as expected");
});
-test("getRequest post function test", 1, function ()
+QUnit.test("getRequest post function test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var instance = {
options: {
@@ -124,10 +136,12 @@ test("getRequest post function test", 1, function ()
var result = getRequest.call(instance);
// then
- propEqual(result, expected, "Valid request object");
+ assert.propEqual(result, expected, "Valid request object");
});
-test("getRequest post object test", 1, function() {
+QUnit.test("getRequest post object test", function( assert ) {
+ assert.expect( 1 );
+
// given
var instance = {
options: {
@@ -153,11 +167,13 @@ test("getRequest post object test", 1, function() {
var result = getRequest.call(instance);
// then
- propEqual(result, expected, "Valid request object");
+ assert.propEqual(result, expected, "Valid request object");
});
-test("getCssSelector test", 1, function ()
+QUnit.test("getCssSelector test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var classNames = " itallic bold normal ";
@@ -165,11 +181,13 @@ test("getCssSelector test", 1, function ()
var result = getCssSelector(classNames);
// then
- equal(result, ".itallic.bold.normal", "Valid css selector");
+ assert.equal(result, ".itallic.bold.normal", "Valid css selector");
});
-test("getUrl function test", 1, function ()
+QUnit.test("getUrl function test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var instance = {
options: {
@@ -184,11 +202,13 @@ test("getUrl function test", 1, function ()
var result = getUrl.call(instance);
// then
- equal(result, "url/test/1", "Valid URL");
+ assert.equal(result, "url/test/1", "Valid URL");
});
-test("getUrl string test", 1, function ()
+QUnit.test("getUrl string test", function ( assert )
{
+ assert.expect( 1 );
+
// given
var instance = {
options: {
@@ -200,5 +220,5 @@ test("getUrl string test", 1, function ()
var result = getUrl.call(instance);
// then
- equal(result, "url/test/1", "Valid URL");
+ assert.equal(result, "url/test/1", "Valid URL");
});
diff --git a/test/tests-rendering.js b/test/tests-rendering.js
index 3fa6682..632e603 100644
--- a/test/tests-rendering.js
+++ b/test/tests-rendering.js
@@ -1,18 +1,18 @@
/*jshint -W024 */
/*jshint -W117 */
-module("render functions", {
- setup: function ()
+QUnit.module("render functions", {
+ beforeEach: function ()
{
$("#qunit-fixture").html("");
},
- teardown: function ()
+ afterEach: function ()
{
$("#qunit-fixture").empty();
}
});
-function renderInfosTest(expected, message, current, rowCount, total)
+function renderInfosTest(expected, message, current, rowCount, total, assert)
{
// given
var instance = {
@@ -41,15 +41,19 @@ function renderInfosTest(expected, message, current, rowCount, total)
// then
var infos = instance.header.find(".infos").text();
- equal(infos, expected, message);
+ assert.equal(infos, expected, message);
}
-test("renderInfos all test", 1, function ()
+QUnit.test("renderInfos all test", function ( assert )
{
- renderInfosTest("11010", "Valid infos", 1, -1, 10);
+ assert.expect( 1 );
+
+ renderInfosTest("11010", "Valid infos", 1, -1, 10, assert);
});
-test("renderInfos paged test", 1, function ()
+QUnit.test("renderInfos paged test", function ( assert )
{
- renderInfosTest("1510", "Valid infos", 1, 5, 10);
+ assert.expect( 1 );
+
+ renderInfosTest("1510", "Valid infos", 1, 5, 10, assert);
});
\ No newline at end of file