jsface
Advanced tools
Comparing version 2.0.0 to 2.0.1
@@ -0,9 +1,16 @@ | ||
Version 2.0.1 Jan 16, 2011 | ||
---------------------------------------------------------------- | ||
* Fix unit tests on IE (extra commas, iframe injection) | ||
* Fix jsface.isArray works incorrectly over String (IE6, IE5.5) | ||
* Fix extend(instance, Class) | ||
Version 2.0.0 Dec 10, 2011 | ||
---------------------------------------------------------------- | ||
* Get rid of $meta (cleaner class structure). | ||
* Introduce $super, extend (mixin, trait). | ||
* Overloading support as a standalone plugin (TBD). | ||
* AOP support as a standalone plugin (TBD). | ||
* Get rid of $meta (cleaner class structure) | ||
* Introduce $super, extend (mixin, trait) | ||
* Overloading support as a standalone plugin (TBD) | ||
* AOP support as a standalone plugin (TBD) | ||
* Anonymous class (without name) (i.e: var Animal = Class({});) | ||
* constructor instead of java style ClassName | ||
* noConflict() mode | ||
* support private method (class body is a function) | ||
@@ -15,3 +22,3 @@ * Single constructor inheritance, multiple trails (Scala style) | ||
* Remove unneccessary code | ||
* Optimization | ||
* Size optimization | ||
* More unit tests | ||
@@ -18,0 +25,0 @@ |
/* | ||
* JsFace Object Oriented Programming Library v2.0.0 | ||
* JSFace Object Oriented Programming Library | ||
* https://github.com/tannhu/jsface | ||
* | ||
* Copyright (c) 2011 Tan Nhu | ||
* Copyright (c) 2009-2012 Tan Nhu | ||
* Licensed under MIT license (https://github.com/tannhu/jsface/blob/master/MIT-LICENSE.txt). | ||
* | ||
* Date: Saturday, March 07 2009 | ||
* Version: 2.0.1 | ||
*/ | ||
@@ -19,37 +20,2 @@ (function(context, undefined) { | ||
/** | ||
* Loop over a collection (a string, an array, an object (a map with pairs of {key:value})), or a function (over all | ||
* static properties). | ||
* Over a String or Array, fn is executed as: fn(value, index, collection) Otherwise: fn(key, value, collection). | ||
* Return Infinity on fn will stop the iteration. each returns an array of results returned by fn. | ||
*/ | ||
function each(collection, fn) { | ||
var iArray, iMap, iFunction, item, i, r, len, result = []; | ||
if ( !collection || !fn) { return; } | ||
iArray = isArray(collection) || isString(collection); | ||
iMap = isMap(collection); | ||
iFunction = isFunction(collection); | ||
// convert to array if collection is not a collection itself | ||
if ( !iArray && !iMap && !iFunction) { | ||
collection = [ collection ]; | ||
iArray = 1; | ||
} | ||
if (iArray) { | ||
for (i = 0, len = collection.length; i < len; i++) { | ||
if ((r = fn(collection[i], i, collection)) === Infinity) { break; } | ||
result.push(r); | ||
} | ||
} else { | ||
for (item in collection) { | ||
if ((r = fn(item, collection[item], collection)) === Infinity) { break; } | ||
result.push(r); | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Check an object is a map or not. A map is something like { key1: value1, key2: value2 }. | ||
@@ -90,2 +56,39 @@ */ | ||
/** | ||
* Loop over a collection (a string, an array, an object (a map with pairs of {key:value})), or a function (over all | ||
* static properties). | ||
* Over a String or Array, fn is executed as: fn(value, index, collection) Otherwise: fn(key, value, collection). | ||
* Return Infinity on fn will stop the iteration. each returns an array of results returned by fn. | ||
*/ | ||
function each(collection, fn) { | ||
var iArray, iMap, iString, iFunction, item, i, r, v, len, result = []; | ||
if ( !collection || !fn) { return; } | ||
iString = isString(collection); | ||
iArray = isArray(collection) || iString; | ||
iMap = isMap(collection); | ||
iFunction = isFunction(collection); | ||
// convert to array if collection is not a collection itself | ||
if ( !iArray && !iMap && !iFunction) { | ||
collection = [ collection ]; | ||
iArray = 1; | ||
} | ||
if (iArray) { | ||
for (i = 0, len = collection.length; i < len; i++) { | ||
v = iString ? collection.charAt(i) : collection[i]; | ||
if ((r = fn(v, i, collection)) === Infinity) { break; } | ||
result.push(r); | ||
} | ||
} else { | ||
for (item in collection) { | ||
if ((r = fn(item, collection[item], collection)) === Infinity) { break; } | ||
result.push(r); | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Make $super method. | ||
@@ -143,12 +146,13 @@ */ | ||
// first: static properties | ||
if (isSubClass || isMap(subject)) { // only if meaningful | ||
each(subject, function(key, value) { | ||
if ( !ignoredKeys || !ignoredKeys.hasOwnProperty(key)) { // no copy ignored keys | ||
object[key] = value; // do copy | ||
if (iClass) { oPrototype[key] = value; } // class? copy to prototype as well | ||
} | ||
}); | ||
function copier(key, value) { | ||
if ( !ignoredKeys || !ignoredKeys.hasOwnProperty(key)) { // no copy ignored keys | ||
object[key] = value; // do copy | ||
if (iClass) { oPrototype[key] = value; } // class? copy to prototype as well | ||
} | ||
} | ||
// copy static properties and prototype.* to object | ||
if (isMap(subject)) { each(subject, copier); } | ||
if (isSubClass) { each(subject.prototype, copier); } | ||
// second: prototype properties | ||
@@ -155,0 +159,0 @@ if (iClass && isSubClass) { extend(oPrototype, subject.prototype, ignoredKeys); } |
@@ -1,5 +0,5 @@ | ||
(function(n){function h(a,b){var c,f,d,g,e=[];if(a&&b){c=o(a)||r(a);f=p(a);d=i(a);!c&&!f&&!d&&(a=[a],c=1);if(c)for(g=0,f=a.length;g<f&&!(Infinity===(c=b(a[g],g,a)));g++)e.push(c);else for(g in a){if(Infinity===(c=b(g,a[g],a)))break;e.push(c)}return e}}function p(a){return a&&typeof a===s&&!(typeof a.length===t&&!a.propertyIsEnumerable(u))}function o(a){return a&&typeof a===s&&typeof a.length===t&&!a.propertyIsEnumerable(u)}function i(a){return a&&"function"===typeof a}function r(a){return"[object String]"=== | ||
Object.prototype.toString.apply(a)}function m(a){return i(a)&&a===a.prototype.constructor}function x(a,b){a.prototype.$super=function(){var c=arguments.callee.caller,f=this.$super,d,g,e,k;if(c===a.prototype.constructor){if(e=b?b[0]:0,m(e))this.$super=e.prototype.$super}else if(b){h(a.prototype,function(a,b){if(b===c)return g=a,Infinity});for(d=b.length;d--&&!e;){k=b[d];if(m(k))k=k.prototype;e=k[g]||0}if(i(e))this.$super=k.$super}if(i(e))return d=e.apply(this,arguments),this.$super=f,d}}function l(a, | ||
b,c){if(o(b))return h(b,function(b){l(a,b,c)});var c=c||{constructor:1,$super:1,prototype:1},f=m(a),d=m(b),g=a.prototype;(d||p(b))&&h(b,function(b,d){if(!c||!c.hasOwnProperty(b))a[b]=d,f&&(g[b]=d)});f&&d&&l(g,b.prototype,c)}function j(a,b){b||(a=(b=a,0));var b=b||{},c,f,d,g,e={constructor:1,$singleton:1,$statics:1,prototype:1},a=a&&!o(a)?[a]:a,b=i(b)?b():b;if(!p(b))throw y;f=b.hasOwnProperty("constructor")?b.constructor:0;d=b.$singleton;g=b.$statics;h(j.plugins,function(a){e[a]=1});c=d?{}:f?j.overload? | ||
j.overload("constructor",f):f:function(){};h(a,function(a){l(c,a,e,!0)});l(d?c:c.prototype,b,e);l(c,g,e,!0);d||x(c,a);h(j.plugins,function(d,e){e(c,a,b)});return c}var s="object",t="number",u="length",y="Invalid params",v=[],w,q;j.plugins={$ready:function(a,b,c){var f=c.$ready,d=0,g=b?b.length:0;h(v,function(e){return h(b,function(f){f===e[0]&&e[1].call(f,a,c,b);if(d++>=g)return Infinity})});i(f)&&(f.call(a,a,c,b),v.push([a,f]))}};q={Class:j,extend:l,each:h,isMap:p,isArray:o,isFunction:i,isString:r, | ||
isClass:m};"undefined"!==typeof module&&module.exports?module.exports=q:(w=n.Class,n.Class=j,n.jsface=q,q.noConflict=function(){n.Class=w})})(this); | ||
(function(n){function o(a){return a&&typeof a===r&&!(typeof a.length===s&&!a.propertyIsEnumerable(t))}function p(a){return a&&typeof a===r&&typeof a.length===s&&!a.propertyIsEnumerable(t)}function j(a){return a&&"function"===typeof a}function u(a){return"[object String]"===Object.prototype.toString.apply(a)}function m(a){return j(a)&&a===a.prototype.constructor}function h(a,b){var c,e,f,g,d,i=[];if(a&&b){f=u(a);c=p(a)||f;e=o(a);g=j(a);!c&&!e&&!g&&(a=[a],c=1);if(c)for(d=0,e=a.length;d<e;d++){c=f?a.charAt(d): | ||
a[d];if(Infinity===(c=b(c,d,a)))break;i.push(c)}else for(d in a){if(Infinity===(c=b(d,a[d],a)))break;i.push(c)}return i}}function x(a,b){a.prototype.$super=function(){var c=arguments.callee.caller,e=this.$super,f,g,d,i;if(c===a.prototype.constructor){if(d=b?b[0]:0,m(d))this.$super=d.prototype.$super}else if(b){h(a.prototype,function(a,b){if(b===c)return g=a,Infinity});for(f=b.length;f--&&!d;){i=b[f];if(m(i))i=i.prototype;d=i[g]||0}if(j(d))this.$super=i.$super}if(j(d))return f=d.apply(this,arguments), | ||
this.$super=e,f}}function l(a,b,c){function e(b,e){if(!c||!c.hasOwnProperty(b))a[b]=e,f&&(d[b]=e)}if(p(b))return h(b,function(b){l(a,b,c)});var c=c||{constructor:1,$super:1,prototype:1},f=m(a),g=m(b),d=a.prototype;o(b)&&h(b,e);g&&h(b.prototype,e);f&&g&&l(d,b.prototype,c)}function k(a,b){b||(a=(b=a,0));var b=b||{},c,e,f,g,d={constructor:1,$singleton:1,$statics:1,prototype:1},a=a&&!p(a)?[a]:a,b=j(b)?b():b;if(!o(b))throw y;e=b.hasOwnProperty("constructor")?b.constructor:0;f=b.$singleton;g=b.$statics; | ||
h(k.plugins,function(a){d[a]=1});c=f?{}:e?k.overload?k.overload("constructor",e):e:function(){};h(a,function(a){l(c,a,d,!0)});l(f?c:c.prototype,b,d);l(c,g,d,!0);f||x(c,a);h(k.plugins,function(d,e){e(c,a,b)});return c}var r="object",s="number",t="length",y="Invalid params",v=[],w,q;k.plugins={$ready:function(a,b,c){var e=c.$ready,f=0,g=b?b.length:0;h(v,function(d){return h(b,function(e){e===d[0]&&d[1].call(e,a,c,b);if(f++>=g)return Infinity})});j(e)&&(e.call(a,a,c,b),v.push([a,e]))}};q={Class:k,extend:l, | ||
each:h,isMap:o,isArray:p,isFunction:j,isString:u,isClass:m};"undefined"!==typeof module&&module.exports?module.exports=q:(w=n.Class,n.Class=k,n.jsface=q,q.noConflict=function(){n.Class=w})})(this); |
@@ -0,1 +1,11 @@ | ||
/* | ||
* JsFace Object Oriented Programming Library | ||
* https://github.com/tannhu/jsface | ||
* | ||
* Copyright (c) 2009-2012 Tan Nhu | ||
* Licensed under MIT license (https://github.com/tannhu/jsface/blob/master/MIT-LICENSE.txt). | ||
* | ||
* Date: Saturday, March 07 2009 | ||
* Version: 2.0.1 | ||
*/ | ||
!function(){ | ||
@@ -2,0 +12,0 @@ /** |
@@ -1,2 +0,2 @@ | ||
Copyright (c) 2010 Tan Nhu | ||
Copyright (c) 2009-2012 Tan Nhu | ||
@@ -3,0 +3,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
@@ -6,4 +6,4 @@ { | ||
"author" : "Tan Nhu <tnhu AT me . com>", | ||
"version" : "2.0.0", | ||
"keywords" : [ "jsface", "JsFace", "OOP", "JavaScript OOP", "JavaScript Object Oriented Programming" ], | ||
"version" : "2.0.1", | ||
"keywords" : [ "jsface", "JSFace", "OOP", "JavaScript OOP", "JavaScript Object Oriented Programming" ], | ||
"homepage" : "https://github.com/tannhu/jsface", | ||
@@ -10,0 +10,0 @@ "licenses" : [{ |
@@ -8,3 +8,3 @@ JSFace is a library designed to facilitate object-oriented programming in JavaScript. | ||
* Support CommonJS. | ||
* Support singleton, trait, private properties. | ||
* Support singleton, mixin, private properties. | ||
* Plugins mechanism to extend itself. | ||
@@ -117,3 +117,3 @@ | ||
var Person = Class(function() { | ||
var MIN_AGE = 0, // private variables | ||
var MIN_AGE = 1, // private variables | ||
MAX_AGE = 150; | ||
@@ -169,3 +169,3 @@ | ||
var Student = Class([ Person, Options, Events ], { | ||
constructor: function(id, name, age) { | ||
constructor: function(id, name, age) {} | ||
}); | ||
@@ -204,3 +204,3 @@ | ||
``` javascript | ||
extend(Array.prototype, { | ||
extend(String.prototype, { | ||
trim: function() { | ||
@@ -213,2 +213,24 @@ return this.replace(/^\s+|\s+$/g, ""); | ||
``` | ||
## No conflict | ||
In browser environment, you might be using another library which also introduces the global namespace Class. JSFace can return the original Class back to the library claims it with a call to jsface.noConflict(). | ||
``` javascript | ||
jsface.noConflict(); | ||
// Code that uses other library's Class can follow here | ||
``` | ||
Actually, Class is an alias of jsface.Class: | ||
``` javascript | ||
jsface.noConflict(); | ||
// Code that uses other library's Class can follow here | ||
// Define classes by using jsface.Class directly | ||
var Person = jsface.Class({ | ||
}); | ||
``` | ||
# Bug tracker | ||
@@ -215,0 +237,0 @@ |
@@ -1,9 +0,9 @@ | ||
/* | ||
* QUnit - A JavaScript Unit Testing Framework | ||
/** | ||
* QUnit v1.3.0pre - A JavaScript Unit Testing Framework | ||
* | ||
* http://docs.jquery.com/QUnit | ||
* | ||
* Copyright (c) 2009 John Resig, Jörn Zaefferer | ||
* Copyright (c) 2011 John Resig, Jörn Zaefferer | ||
* Dual licensed under the MIT (MIT-LICENSE.txt) | ||
* and GPL (GPL-LICENSE.txt) licenses. | ||
* or GPL (GPL-LICENSE.txt) licenses. | ||
*/ | ||
@@ -13,214 +13,290 @@ | ||
var QUnit = { | ||
var defined = { | ||
setTimeout: typeof window.setTimeout !== "undefined", | ||
sessionStorage: (function() { | ||
try { | ||
return !!sessionStorage.getItem; | ||
} catch(e) { | ||
return false; | ||
} | ||
})() | ||
}; | ||
// call on start of module test to prepend name to all tests | ||
module: function(name, testEnvironment) { | ||
config.currentModule = name; | ||
var testId = 0, | ||
toString = Object.prototype.toString, | ||
hasOwn = Object.prototype.hasOwnProperty; | ||
synchronize(function() { | ||
var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { | ||
this.name = name; | ||
this.testName = testName; | ||
this.expected = expected; | ||
this.testEnvironmentArg = testEnvironmentArg; | ||
this.async = async; | ||
this.callback = callback; | ||
this.assertions = []; | ||
}; | ||
Test.prototype = { | ||
init: function() { | ||
var tests = id("qunit-tests"); | ||
if (tests) { | ||
var b = document.createElement("strong"); | ||
b.innerHTML = "Running " + this.name; | ||
var li = document.createElement("li"); | ||
li.appendChild( b ); | ||
li.className = "running"; | ||
li.id = this.id = "test-output" + testId++; | ||
tests.appendChild( li ); | ||
} | ||
}, | ||
setup: function() { | ||
if (this.module != config.previousModule) { | ||
if ( config.previousModule ) { | ||
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); | ||
runLoggingCallbacks('moduleDone', QUnit, { | ||
name: config.previousModule, | ||
failed: config.moduleStats.bad, | ||
passed: config.moduleStats.all - config.moduleStats.bad, | ||
total: config.moduleStats.all | ||
} ); | ||
} | ||
config.previousModule = config.currentModule; | ||
config.currentModule = name; | ||
config.moduleTestEnvironment = testEnvironment; | ||
config.previousModule = this.module; | ||
config.moduleStats = { all: 0, bad: 0 }; | ||
runLoggingCallbacks( 'moduleStart', QUnit, { | ||
name: this.module | ||
} ); | ||
} | ||
QUnit.moduleStart( name, testEnvironment ); | ||
config.current = this; | ||
this.testEnvironment = extend({ | ||
setup: function() {}, | ||
teardown: function() {} | ||
}, this.moduleTestEnvironment); | ||
if (this.testEnvironmentArg) { | ||
extend(this.testEnvironment, this.testEnvironmentArg); | ||
} | ||
runLoggingCallbacks( 'testStart', QUnit, { | ||
name: this.testName, | ||
module: this.module | ||
}); | ||
}, | ||
// allow utility functions to access the current test environment | ||
// TODO why?? | ||
QUnit.current_testEnvironment = this.testEnvironment; | ||
asyncTest: function(testName, expected, callback) { | ||
if ( arguments.length === 2 ) { | ||
callback = expected; | ||
expected = 0; | ||
try { | ||
if ( !config.pollution ) { | ||
saveGlobal(); | ||
} | ||
this.testEnvironment.setup.call(this.testEnvironment); | ||
} catch(e) { | ||
QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); | ||
} | ||
QUnit.test(testName, expected, callback, true); | ||
}, | ||
run: function() { | ||
config.current = this; | ||
if ( this.async ) { | ||
QUnit.stop(); | ||
} | ||
test: function(testName, expected, callback, async) { | ||
var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg; | ||
if ( config.notrycatch ) { | ||
this.callback.call(this.testEnvironment); | ||
return; | ||
} | ||
try { | ||
this.callback.call(this.testEnvironment); | ||
} catch(e) { | ||
fail("Test " + this.testName + " died, exception and test follows", e, this.callback); | ||
QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); | ||
// else next test will carry the responsibility | ||
saveGlobal(); | ||
if ( arguments.length === 2 ) { | ||
callback = expected; | ||
expected = null; | ||
// Restart the tests if they're blocking | ||
if ( config.blocking ) { | ||
QUnit.start(); | ||
} | ||
} | ||
// is 2nd argument a testEnvironment? | ||
if ( expected && typeof expected === 'object') { | ||
testEnvironmentArg = expected; | ||
expected = null; | ||
}, | ||
teardown: function() { | ||
config.current = this; | ||
try { | ||
this.testEnvironment.teardown.call(this.testEnvironment); | ||
checkPollution(); | ||
} catch(e) { | ||
QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); | ||
} | ||
if ( config.currentModule ) { | ||
name = '<span class="module-name">' + config.currentModule + "</span>: " + name; | ||
}, | ||
finish: function() { | ||
config.current = this; | ||
if ( this.expected != null && this.expected != this.assertions.length ) { | ||
QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); | ||
} | ||
if ( !validTest(config.currentModule + ": " + testName) ) { | ||
return; | ||
} | ||
var good = 0, bad = 0, | ||
tests = id("qunit-tests"); | ||
synchronize(function() { | ||
config.stats.all += this.assertions.length; | ||
config.moduleStats.all += this.assertions.length; | ||
testEnvironment = extend({ | ||
setup: function() {}, | ||
teardown: function() {} | ||
}, config.moduleTestEnvironment); | ||
if (testEnvironmentArg) { | ||
extend(testEnvironment,testEnvironmentArg); | ||
} | ||
if ( tests ) { | ||
var ol = document.createElement("ol"); | ||
QUnit.testStart( testName, testEnvironment ); | ||
for ( var i = 0; i < this.assertions.length; i++ ) { | ||
var assertion = this.assertions[i]; | ||
// allow utility functions to access the current test environment | ||
QUnit.current_testEnvironment = testEnvironment; | ||
var li = document.createElement("li"); | ||
li.className = assertion.result ? "pass" : "fail"; | ||
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); | ||
ol.appendChild( li ); | ||
config.assertions = []; | ||
config.expected = expected; | ||
var tests = id("qunit-tests"); | ||
if (tests) { | ||
var b = document.createElement("strong"); | ||
b.innerHTML = "Running " + name; | ||
var li = document.createElement("li"); | ||
li.appendChild( b ); | ||
li.id = "current-test-output"; | ||
tests.appendChild( li ); | ||
if ( assertion.result ) { | ||
good++; | ||
} else { | ||
bad++; | ||
config.stats.bad++; | ||
config.moduleStats.bad++; | ||
} | ||
} | ||
try { | ||
if ( !config.pollution ) { | ||
saveGlobal(); | ||
// store result when possible | ||
if ( QUnit.config.reorder && defined.sessionStorage ) { | ||
if (bad) { | ||
sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); | ||
} else { | ||
sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); | ||
} | ||
testEnvironment.setup.call(testEnvironment); | ||
} catch(e) { | ||
QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); | ||
} | ||
}); | ||
synchronize(function() { | ||
if ( async ) { | ||
QUnit.stop(); | ||
if (bad == 0) { | ||
ol.style.display = "none"; | ||
} | ||
try { | ||
callback.call(testEnvironment); | ||
} catch(e) { | ||
fail("Test " + name + " died, exception and test follows", e, callback); | ||
QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); | ||
// else next test will carry the responsibility | ||
saveGlobal(); | ||
var b = document.createElement("strong"); | ||
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; | ||
// Restart the tests if they're blocking | ||
if ( config.blocking ) { | ||
start(); | ||
var a = document.createElement("a"); | ||
a.innerHTML = "Rerun"; | ||
a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); | ||
addEvent(b, "click", function() { | ||
var next = b.nextSibling.nextSibling, | ||
display = next.style.display; | ||
next.style.display = display === "none" ? "block" : "none"; | ||
}); | ||
addEvent(b, "dblclick", function(e) { | ||
var target = e && e.target ? e.target : window.event.srcElement; | ||
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { | ||
target = target.parentNode; | ||
} | ||
} | ||
}); | ||
if ( window.location && target.nodeName.toLowerCase() === "strong" ) { | ||
window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); | ||
} | ||
}); | ||
synchronize(function() { | ||
try { | ||
checkPollution(); | ||
testEnvironment.teardown.call(testEnvironment); | ||
} catch(e) { | ||
QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); | ||
} | ||
}); | ||
var li = id(this.id); | ||
li.className = bad ? "fail" : "pass"; | ||
li.removeChild( li.firstChild ); | ||
li.appendChild( b ); | ||
li.appendChild( a ); | ||
li.appendChild( ol ); | ||
synchronize(function() { | ||
if ( config.expected && config.expected != config.assertions.length ) { | ||
QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); | ||
} else { | ||
for ( var i = 0; i < this.assertions.length; i++ ) { | ||
if ( !this.assertions[i].result ) { | ||
bad++; | ||
config.stats.bad++; | ||
config.moduleStats.bad++; | ||
} | ||
} | ||
} | ||
var good = 0, bad = 0, | ||
tests = id("qunit-tests"); | ||
try { | ||
QUnit.reset(); | ||
} catch(e) { | ||
fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); | ||
} | ||
config.stats.all += config.assertions.length; | ||
config.moduleStats.all += config.assertions.length; | ||
runLoggingCallbacks( 'testDone', QUnit, { | ||
name: this.testName, | ||
module: this.module, | ||
failed: bad, | ||
passed: this.assertions.length - bad, | ||
total: this.assertions.length | ||
} ); | ||
}, | ||
if ( tests ) { | ||
var ol = document.createElement("ol"); | ||
queue: function() { | ||
var test = this; | ||
synchronize(function() { | ||
test.init(); | ||
}); | ||
function run() { | ||
// each of these can by async | ||
synchronize(function() { | ||
test.setup(); | ||
}); | ||
synchronize(function() { | ||
test.run(); | ||
}); | ||
synchronize(function() { | ||
test.teardown(); | ||
}); | ||
synchronize(function() { | ||
test.finish(); | ||
}); | ||
} | ||
// defer when previous test run passed, if storage is available | ||
var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); | ||
if (bad) { | ||
run(); | ||
} else { | ||
synchronize(run, true); | ||
}; | ||
} | ||
for ( var i = 0; i < config.assertions.length; i++ ) { | ||
var assertion = config.assertions[i]; | ||
}; | ||
var li = document.createElement("li"); | ||
li.className = assertion.result ? "pass" : "fail"; | ||
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); | ||
ol.appendChild( li ); | ||
var QUnit = { | ||
if ( assertion.result ) { | ||
good++; | ||
} else { | ||
bad++; | ||
config.stats.bad++; | ||
config.moduleStats.bad++; | ||
} | ||
} | ||
if (bad == 0) { | ||
ol.style.display = "none"; | ||
} | ||
// call on start of module test to prepend name to all tests | ||
module: function(name, testEnvironment) { | ||
config.currentModule = name; | ||
config.currentModuleTestEnviroment = testEnvironment; | ||
}, | ||
var b = document.createElement("strong"); | ||
b.innerHTML = name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + config.assertions.length + ")</b>"; | ||
asyncTest: function(testName, expected, callback) { | ||
if ( arguments.length === 2 ) { | ||
callback = expected; | ||
expected = null; | ||
} | ||
addEvent(b, "click", function() { | ||
var next = b.nextSibling, display = next.style.display; | ||
next.style.display = display === "none" ? "block" : "none"; | ||
}); | ||
QUnit.test(testName, expected, callback, true); | ||
}, | ||
addEvent(b, "dblclick", function(e) { | ||
var target = e && e.target ? e.target : window.event.srcElement; | ||
if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { | ||
target = target.parentNode; | ||
} | ||
if ( window.location && target.nodeName.toLowerCase() === "strong" ) { | ||
window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); | ||
} | ||
}); | ||
test: function(testName, expected, callback, async) { | ||
var name = '<span class="test-name">' + escapeInnerText(testName) + '</span>', testEnvironmentArg; | ||
var li = id("current-test-output"); | ||
li.id = ""; | ||
li.className = bad ? "fail" : "pass"; | ||
li.style.display = resultDisplayStyle(!bad); | ||
li.removeChild( li.firstChild ); | ||
li.appendChild( b ); | ||
li.appendChild( ol ); | ||
if ( arguments.length === 2 ) { | ||
callback = expected; | ||
expected = null; | ||
} | ||
// is 2nd argument a testEnvironment? | ||
if ( expected && typeof expected === 'object') { | ||
testEnvironmentArg = expected; | ||
expected = null; | ||
} | ||
if ( bad ) { | ||
var toolbar = id("qunit-testrunner-toolbar"); | ||
if ( toolbar ) { | ||
toolbar.style.display = "block"; | ||
id("qunit-filter-pass").disabled = null; | ||
id("qunit-filter-missing").disabled = null; | ||
} | ||
} | ||
if ( config.currentModule ) { | ||
name = '<span class="module-name">' + config.currentModule + "</span>: " + name; | ||
} | ||
} else { | ||
for ( var i = 0; i < config.assertions.length; i++ ) { | ||
if ( !config.assertions[i].result ) { | ||
bad++; | ||
config.stats.bad++; | ||
config.moduleStats.bad++; | ||
} | ||
} | ||
} | ||
if ( !validTest(config.currentModule + ": " + testName) ) { | ||
return; | ||
} | ||
try { | ||
QUnit.reset(); | ||
} catch(e) { | ||
fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset); | ||
} | ||
QUnit.testDone( testName, bad, config.assertions.length ); | ||
if ( !window.setTimeout && !config.queue.length ) { | ||
done(); | ||
} | ||
}); | ||
synchronize( done ); | ||
var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); | ||
test.module = config.currentModule; | ||
test.moduleTestEnvironment = config.currentModuleTestEnviroment; | ||
test.queue(); | ||
}, | ||
@@ -232,3 +308,3 @@ | ||
expect: function(asserts) { | ||
config.expected = asserts; | ||
config.current.expected = asserts; | ||
}, | ||
@@ -246,5 +322,5 @@ | ||
}; | ||
msg = escapeHtml(msg); | ||
QUnit.log(a, msg, details); | ||
config.assertions.push({ | ||
msg = escapeInnerText(msg); | ||
runLoggingCallbacks( 'log', QUnit, details ); | ||
config.current.assertions.push({ | ||
result: a, | ||
@@ -291,16 +367,51 @@ message: msg | ||
raises: function(fn, message) { | ||
raises: function(block, expected, message) { | ||
var actual, ok = false; | ||
if (typeof expected === 'string') { | ||
message = expected; | ||
expected = null; | ||
} | ||
try { | ||
fn(); | ||
ok( false, message ); | ||
block(); | ||
} catch (e) { | ||
actual = e; | ||
} | ||
catch (e) { | ||
ok( true, message ); | ||
if (actual) { | ||
// we don't want to validate thrown error | ||
if (!expected) { | ||
ok = true; | ||
// expected is a regexp | ||
} else if (QUnit.objectType(expected) === "regexp") { | ||
ok = expected.test(actual); | ||
// expected is a constructor | ||
} else if (actual instanceof expected) { | ||
ok = true; | ||
// expected is a validation function which returns true is validation passed | ||
} else if (expected.call({}, actual) === true) { | ||
ok = true; | ||
} | ||
} | ||
QUnit.ok(ok, message); | ||
}, | ||
start: function() { | ||
start: function(count) { | ||
config.semaphore -= count || 1; | ||
if (config.semaphore > 0) { | ||
// don't start until equal number of stop-calls | ||
return; | ||
} | ||
if (config.semaphore < 0) { | ||
// ignore if start is called more often then stop | ||
config.semaphore = 0; | ||
} | ||
// A slight delay, to avoid any current callbacks | ||
if ( window.setTimeout ) { | ||
if ( defined.setTimeout ) { | ||
window.setTimeout(function() { | ||
if (config.semaphore > 0) { | ||
return; | ||
} | ||
if ( config.timeout ) { | ||
@@ -311,23 +422,34 @@ clearTimeout(config.timeout); | ||
config.blocking = false; | ||
process(); | ||
process(true); | ||
}, 13); | ||
} else { | ||
config.blocking = false; | ||
process(); | ||
process(true); | ||
} | ||
}, | ||
stop: function(timeout) { | ||
stop: function(count) { | ||
config.semaphore += count || 1; | ||
config.blocking = true; | ||
if ( timeout && window.setTimeout ) { | ||
if ( config.testTimeout && defined.setTimeout ) { | ||
clearTimeout(config.timeout); | ||
config.timeout = window.setTimeout(function() { | ||
QUnit.ok( false, "Test timed out" ); | ||
config.semaphore = 1; | ||
QUnit.start(); | ||
}, timeout); | ||
}, config.testTimeout); | ||
} | ||
} | ||
}; | ||
//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; | ||
})(); | ||
// Backwards compatibility, deprecated | ||
@@ -343,3 +465,25 @@ QUnit.equals = QUnit.equal; | ||
// block until document ready | ||
blocking: true | ||
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, | ||
urlConfig: ['noglobals', 'notrycatch'], | ||
//logging callback queues | ||
begin: [], | ||
done: [], | ||
log: [], | ||
testStart: [], | ||
testDone: [], | ||
moduleStart: [], | ||
moduleDone: [] | ||
}; | ||
@@ -350,18 +494,19 @@ | ||
var location = window.location || { search: "", protocol: "file:" }, | ||
GETParams = location.search.slice(1).split('&'); | ||
params = location.search.slice( 1 ).split( "&" ), | ||
length = params.length, | ||
urlParams = {}, | ||
current; | ||
for ( var i = 0; i < GETParams.length; i++ ) { | ||
GETParams[i] = decodeURIComponent( GETParams[i] ); | ||
if ( GETParams[i] === "noglobals" ) { | ||
GETParams.splice( i, 1 ); | ||
i--; | ||
config.noglobals = true; | ||
} else if ( GETParams[i].search('=') > -1 ) { | ||
GETParams.splice( i, 1 ); | ||
i--; | ||
if ( params[ 0 ] ) { | ||
for ( var 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 ]; | ||
} | ||
} | ||
// restrict modules/tests by get parameters | ||
config.filters = GETParams; | ||
QUnit.urlParams = urlParams; | ||
config.filter = urlParams.filter; | ||
@@ -396,10 +541,10 @@ // Figure out if we're running the tests from a server or not | ||
autorun: false, | ||
assertions: [], | ||
filters: [], | ||
queue: [] | ||
filter: "", | ||
queue: [], | ||
semaphore: 0 | ||
}); | ||
var tests = id("qunit-tests"), | ||
banner = id("qunit-banner"), | ||
result = id("qunit-testresult"); | ||
var tests = id( "qunit-tests" ), | ||
banner = id( "qunit-banner" ), | ||
result = id( "qunit-testresult" ); | ||
@@ -417,2 +562,10 @@ if ( tests ) { | ||
} | ||
if ( tests ) { | ||
result = document.createElement( "p" ); | ||
result.id = "qunit-testresult"; | ||
result.className = "result"; | ||
tests.parentNode.insertBefore( result, tests ); | ||
result.innerHTML = 'Running...<br/> '; | ||
} | ||
}, | ||
@@ -427,5 +580,5 @@ | ||
if ( window.jQuery ) { | ||
jQuery( "#main, #qunit-fixture" ).html( config.fixture ); | ||
jQuery( "#qunit-fixture" ).html( config.fixture ); | ||
} else { | ||
var main = id( 'main' ) || id( 'qunit-fixture' ); | ||
var main = id( 'qunit-fixture' ); | ||
if ( main ) { | ||
@@ -472,4 +625,3 @@ main.innerHTML = config.fixture; | ||
var type = Object.prototype.toString.call( obj ) | ||
.match(/^\[object\s(.*)\]$/)[1] || ''; | ||
var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; | ||
@@ -505,14 +657,23 @@ switch (type) { | ||
message = escapeHtml(message) || (result ? "okay" : "failed"); | ||
message = escapeInnerText(message) || (result ? "okay" : "failed"); | ||
message = '<span class="test-message">' + message + "</span>"; | ||
expected = escapeHtml(QUnit.jsDump.parse(expected)); | ||
actual = escapeHtml(QUnit.jsDump.parse(actual)); | ||
var output = message + ', expected: <span class="test-expected">' + expected + '</span>'; | ||
expected = escapeInnerText(QUnit.jsDump.parse(expected)); | ||
actual = escapeInnerText(QUnit.jsDump.parse(actual)); | ||
var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; | ||
if (actual != expected) { | ||
output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual); | ||
output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; | ||
output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; | ||
} | ||
if (!result) { | ||
var source = sourceFromStacktrace(); | ||
if (source) { | ||
details.source = source; | ||
output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>'; | ||
} | ||
} | ||
output += "</table>"; | ||
QUnit.log(result, message, details); | ||
runLoggingCallbacks( 'log', QUnit, details ); | ||
config.assertions.push({ | ||
config.current.assertions.push({ | ||
result: !!result, | ||
@@ -523,12 +684,42 @@ message: output | ||
// Logging callbacks | ||
begin: function() {}, | ||
done: function(failures, total) {}, | ||
log: function(result, message) {}, | ||
testStart: function(name, testEnvironment) {}, | ||
testDone: function(name, failures, total) {}, | ||
moduleStart: function(name, testEnvironment) {}, | ||
moduleDone: function(name, failures, total) {} | ||
url: function( params ) { | ||
params = extend( extend( {}, QUnit.urlParams ), params ); | ||
var querystring = "?", | ||
key; | ||
for ( key in params ) { | ||
if ( !hasOwn.call( params, key ) ) { | ||
continue; | ||
} | ||
querystring += encodeURIComponent( key ) + "=" + | ||
encodeURIComponent( params[ key ] ) + "&"; | ||
} | ||
return window.location.pathname + querystring.slice( 0, -1 ); | ||
}, | ||
extend: extend, | ||
id: id, | ||
addEvent: addEvent | ||
}); | ||
//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later | ||
//Doing this allows us to tell if the following methods have been overwritten on the actual | ||
//QUnit object, which is a deprecated way of using the callbacks. | ||
extend(QUnit.constructor.prototype, { | ||
// 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 } | ||
testDone: registerLoggingCallback('testDone'), | ||
// moduleStart: { name } | ||
moduleStart: registerLoggingCallback('moduleStart'), | ||
// moduleDone: { name, failed, passed, total } | ||
moduleDone: registerLoggingCallback('moduleDone') | ||
}); | ||
if ( typeof document === "undefined" || document.readyState === "complete" ) { | ||
@@ -538,4 +729,4 @@ config.autorun = true; | ||
addEvent(window, "load", function() { | ||
QUnit.begin(); | ||
QUnit.load = function() { | ||
runLoggingCallbacks( 'begin', QUnit, {} ); | ||
@@ -549,2 +740,8 @@ // Initialize the config, saving the execution queue | ||
var urlConfigHtml = '', len = config.urlConfig.length; | ||
for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { | ||
config[val] = QUnit.urlParams[val]; | ||
urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>'; | ||
} | ||
var userAgent = id("qunit-userAgent"); | ||
@@ -556,12 +753,8 @@ if ( userAgent ) { | ||
if ( banner ) { | ||
var paramsIndex = location.href.lastIndexOf(location.search); | ||
if ( paramsIndex > -1 ) { | ||
var mainPageLocation = location.href.slice(0, paramsIndex); | ||
if ( mainPageLocation == location.href ) { | ||
banner.innerHTML = '<a href=""> ' + banner.innerHTML + '</a> '; | ||
} else { | ||
var testName = decodeURIComponent(location.search.slice(1)); | ||
banner.innerHTML = '<a href="' + mainPageLocation + '">' + banner.innerHTML + '</a> › <a href="">' + testName + '</a>'; | ||
} | ||
} | ||
banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml; | ||
addEvent( banner, "change", function( event ) { | ||
var params = {}; | ||
params[ event.target.name ] = event.target.checked ? true : undefined; | ||
window.location = QUnit.url( params ); | ||
}); | ||
} | ||
@@ -571,16 +764,26 @@ | ||
if ( toolbar ) { | ||
toolbar.style.display = "none"; | ||
var filter = document.createElement("input"); | ||
filter.type = "checkbox"; | ||
filter.id = "qunit-filter-pass"; | ||
filter.disabled = true; | ||
addEvent( filter, "click", function() { | ||
var li = document.getElementsByTagName("li"); | ||
for ( var i = 0; i < li.length; i++ ) { | ||
if ( li[i].className.indexOf("pass") > -1 ) { | ||
li[i].style.display = filter.checked ? "none" : ""; | ||
var ol = document.getElementById("qunit-tests"); | ||
if ( filter.checked ) { | ||
ol.className = ol.className + " hidepass"; | ||
} else { | ||
var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; | ||
ol.className = tmp.replace(/ hidepass /, " "); | ||
} | ||
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; | ||
var ol = document.getElementById("qunit-tests"); | ||
ol.className = ol.className + " hidepass"; | ||
} | ||
toolbar.appendChild( filter ); | ||
@@ -592,24 +795,5 @@ | ||
toolbar.appendChild( label ); | ||
var missing = document.createElement("input"); | ||
missing.type = "checkbox"; | ||
missing.id = "qunit-filter-missing"; | ||
missing.disabled = true; | ||
addEvent( missing, "click", function() { | ||
var li = document.getElementsByTagName("li"); | ||
for ( var i = 0; i < li.length; i++ ) { | ||
if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { | ||
li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; | ||
} | ||
} | ||
}); | ||
toolbar.appendChild( missing ); | ||
label = document.createElement("label"); | ||
label.setAttribute("for", "qunit-filter-missing"); | ||
label.innerHTML = "Hide missing tests (untested code is broken code)"; | ||
toolbar.appendChild( label ); | ||
} | ||
var main = id('main') || id('qunit-fixture'); | ||
var main = id('qunit-fixture'); | ||
if ( main ) { | ||
@@ -622,22 +806,18 @@ config.fixture = main.innerHTML; | ||
} | ||
}); | ||
}; | ||
function done() { | ||
if ( config.doneTimer && window.clearTimeout ) { | ||
window.clearTimeout( config.doneTimer ); | ||
config.doneTimer = null; | ||
} | ||
addEvent(window, "load", QUnit.load); | ||
if ( config.queue.length ) { | ||
config.doneTimer = window.setTimeout(function(){ | ||
if ( !config.queue.length ) { | ||
done(); | ||
} else { | ||
synchronize( done ); | ||
} | ||
}, 13); | ||
return; | ||
// addEvent(window, "error") gives us a useless event object | ||
window.onerror = function( message, file, line ) { | ||
if ( QUnit.config.current ) { | ||
ok( false, message + ", " + file + ":" + line ); | ||
} else { | ||
test( "global failure", function() { | ||
ok( false, message + ", " + file + ":" + line ); | ||
}); | ||
} | ||
}; | ||
function done() { | ||
config.autorun = true; | ||
@@ -647,3 +827,8 @@ | ||
if ( config.currentModule ) { | ||
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); | ||
runLoggingCallbacks( 'moduleDone', QUnit, { | ||
name: config.currentModule, | ||
failed: config.moduleStats.bad, | ||
passed: config.moduleStats.all - config.moduleStats.bad, | ||
total: config.moduleStats.all | ||
} ); | ||
} | ||
@@ -653,5 +838,16 @@ | ||
tests = id("qunit-tests"), | ||
html = ['Tests completed in ', | ||
+new Date - config.started, ' milliseconds.<br/>', | ||
'<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join(''); | ||
runtime = +new Date - config.started, | ||
passed = config.stats.all - config.stats.bad, | ||
html = [ | ||
'Tests completed in ', | ||
runtime, | ||
' milliseconds.<br/>', | ||
'<span class="passed">', | ||
passed, | ||
'</span> tests of <span class="total">', | ||
config.stats.all, | ||
'</span> passed, <span class="failed">', | ||
config.stats.bad, | ||
'</span> failed.' | ||
].join(''); | ||
@@ -663,40 +859,41 @@ if ( banner ) { | ||
if ( tests ) { | ||
var result = id("qunit-testresult"); | ||
id( "qunit-testresult" ).innerHTML = html; | ||
} | ||
if ( !result ) { | ||
result = document.createElement("p"); | ||
result.id = "qunit-testresult"; | ||
result.className = "result"; | ||
tests.parentNode.insertBefore( result, tests.nextSibling ); | ||
} | ||
result.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(" "); | ||
} | ||
QUnit.done( config.stats.bad, config.stats.all ); | ||
runLoggingCallbacks( 'done', QUnit, { | ||
failed: config.stats.bad, | ||
passed: passed, | ||
total: config.stats.all, | ||
runtime: runtime | ||
} ); | ||
} | ||
function validTest( name ) { | ||
var i = config.filters.length, | ||
var filter = config.filter, | ||
run = false; | ||
if ( !i ) { | ||
if ( !filter ) { | ||
return true; | ||
} | ||
while ( i-- ) { | ||
var filter = config.filters[i], | ||
not = filter.charAt(0) == '!'; | ||
var not = filter.charAt( 0 ) === "!"; | ||
if ( not ) { | ||
filter = filter.slice( 1 ); | ||
} | ||
if ( not ) { | ||
filter = filter.slice(1); | ||
} | ||
if ( name.indexOf( filter ) !== -1 ) { | ||
return !not; | ||
} | ||
if ( name.indexOf(filter) !== -1 ) { | ||
return !not; | ||
} | ||
if ( not ) { | ||
run = true; | ||
} | ||
if ( not ) { | ||
run = true; | ||
} | ||
@@ -707,7 +904,23 @@ | ||
function resultDisplayStyle(passed) { | ||
return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : ''; | ||
// so far supports only Firefox, Chrome and Opera (buggy) | ||
// could be extended in the future to use something like https://github.com/csnover/TraceKit | ||
function sourceFromStacktrace() { | ||
try { | ||
throw new Error(); | ||
} catch ( e ) { | ||
if (e.stacktrace) { | ||
// Opera | ||
return e.stacktrace.split("\n")[6]; | ||
} else if (e.stack) { | ||
// Firefox, Chrome | ||
return e.stack.split("\n")[4]; | ||
} else if (e.sourceURL) { | ||
// Safari, PhantomJS | ||
// TODO sourceURL points at the 'throw new Error' line above, useless | ||
//return e.sourceURL + ":" + e.line; | ||
} | ||
} | ||
} | ||
function escapeHtml(s) { | ||
function escapeInnerText(s) { | ||
if (!s) { | ||
@@ -717,7 +930,5 @@ return ""; | ||
s = s + ""; | ||
return s.replace(/[\&"<>\\]/g, function(s) { | ||
return s.replace(/[\&<>]/g, function(s) { | ||
switch(s) { | ||
case "&": return "&"; | ||
case "\\": return "\\\\"; | ||
case '"': return '\"'; | ||
case "<": return "<"; | ||
@@ -730,22 +941,28 @@ case ">": return ">"; | ||
function synchronize( callback ) { | ||
function synchronize( callback, last ) { | ||
config.queue.push( callback ); | ||
if ( config.autorun && !config.blocking ) { | ||
process(); | ||
process(last); | ||
} | ||
} | ||
function process() { | ||
var start = (new Date()).getTime(); | ||
function process( last ) { | ||
var start = new Date().getTime(); | ||
config.depth = config.depth ? config.depth + 1 : 1; | ||
while ( config.queue.length && !config.blocking ) { | ||
if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { | ||
if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { | ||
config.queue.shift()(); | ||
} else { | ||
setTimeout( process, 13 ); | ||
window.setTimeout( function(){ | ||
process( last ); | ||
}, 13 ); | ||
break; | ||
} | ||
} | ||
config.depth--; | ||
if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { | ||
done(); | ||
} | ||
} | ||
@@ -758,2 +975,5 @@ | ||
for ( var key in window ) { | ||
if ( !hasOwn.call( window, key ) ) { | ||
continue; | ||
} | ||
config.pollution.push( key ); | ||
@@ -768,12 +988,10 @@ } | ||
var newGlobals = diff( old, config.pollution ); | ||
var newGlobals = diff( config.pollution, old ); | ||
if ( newGlobals.length > 0 ) { | ||
ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); | ||
config.expected++; | ||
} | ||
var deletedGlobals = diff( config.pollution, old ); | ||
var deletedGlobals = diff( old, config.pollution ); | ||
if ( deletedGlobals.length > 0 ) { | ||
ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); | ||
config.expected++; | ||
} | ||
@@ -801,2 +1019,3 @@ } | ||
console.error(exception); | ||
console.error(exception.stack); | ||
console.warn(callback.toString()); | ||
@@ -811,3 +1030,9 @@ | ||
for ( var prop in b ) { | ||
a[prop] = b[prop]; | ||
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]; | ||
} | ||
} | ||
@@ -833,172 +1058,212 @@ | ||
function registerLoggingCallback(key){ | ||
return function(callback){ | ||
config[key].push( callback ); | ||
}; | ||
} | ||
// Supports deprecated method of completely overwriting logging callbacks | ||
function runLoggingCallbacks(key, scope, args) { | ||
//debugger; | ||
var callbacks; | ||
if ( QUnit.hasOwnProperty(key) ) { | ||
QUnit[key].call(scope, args); | ||
} else { | ||
callbacks = config[key]; | ||
for( var i = 0; i < callbacks.length; i++ ) { | ||
callbacks[i].call( scope, args ); | ||
} | ||
} | ||
} | ||
// Test for equality any JavaScript type. | ||
// Discussions and reference: http://philrathe.com/articles/equiv | ||
// Test suites: http://philrathe.com/tests/equiv | ||
// Author: Philippe Rathé <prathe@gmail.com> | ||
QUnit.equiv = function () { | ||
var innerEquiv; // the real equiv function | ||
var callers = []; // stack to decide between skip/abort functions | ||
var parents = []; // stack to avoiding loops from circular referencing | ||
var innerEquiv; // the real equiv function | ||
var callers = []; // stack to decide between skip/abort functions | ||
var parents = []; // stack to avoiding loops from circular referencing | ||
// Call the o related callback with the given arguments. | ||
function bindCallbacks(o, callbacks, args) { | ||
var prop = QUnit.objectType(o); | ||
if (prop) { | ||
if (QUnit.objectType(callbacks[prop]) === "function") { | ||
return callbacks[prop].apply(callbacks, args); | ||
} else { | ||
return callbacks[prop]; // or undefined | ||
} | ||
} | ||
} | ||
// Call the o related callback with the given arguments. | ||
function bindCallbacks(o, callbacks, args) { | ||
var prop = QUnit.objectType(o); | ||
if (prop) { | ||
if (QUnit.objectType(callbacks[prop]) === "function") { | ||
return callbacks[prop].apply(callbacks, args); | ||
} else { | ||
return callbacks[prop]; // or undefined | ||
} | ||
} | ||
} | ||
var callbacks = function () { | ||
var getProto = Object.getPrototypeOf || function (obj) { | ||
return obj.__proto__; | ||
}; | ||
// for string, boolean, number and null | ||
function useStrictEquality(b, a) { | ||
if (b instanceof a.constructor || a instanceof b.constructor) { | ||
// to catch short annotaion VS 'new' annotation of a declaration | ||
// e.g. var i = 1; | ||
// var j = new Number(1); | ||
return a == b; | ||
} else { | ||
return a === b; | ||
} | ||
} | ||
var callbacks = function () { | ||
return { | ||
"string": useStrictEquality, | ||
"boolean": useStrictEquality, | ||
"number": useStrictEquality, | ||
"null": useStrictEquality, | ||
"undefined": useStrictEquality, | ||
// for string, boolean, number and null | ||
function useStrictEquality(b, a) { | ||
if (b instanceof a.constructor || a instanceof b.constructor) { | ||
// to catch short annotaion VS 'new' annotation of a | ||
// declaration | ||
// e.g. var i = 1; | ||
// var j = new Number(1); | ||
return a == b; | ||
} else { | ||
return a === b; | ||
} | ||
} | ||
"nan": function (b) { | ||
return isNaN(b); | ||
}, | ||
return { | ||
"string" : useStrictEquality, | ||
"boolean" : useStrictEquality, | ||
"number" : useStrictEquality, | ||
"null" : useStrictEquality, | ||
"undefined" : useStrictEquality, | ||
"date": function (b, a) { | ||
return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); | ||
}, | ||
"nan" : function(b) { | ||
return isNaN(b); | ||
}, | ||
"regexp": function (b, a) { | ||
return QUnit.objectType(b) === "regexp" && | ||
a.source === b.source && // the regex itself | ||
a.global === b.global && // and its modifers (gmi) ... | ||
a.ignoreCase === b.ignoreCase && | ||
a.multiline === b.multiline; | ||
}, | ||
"date" : function(b, a) { | ||
return QUnit.objectType(b) === "date" | ||
&& a.valueOf() === b.valueOf(); | ||
}, | ||
// - 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"; | ||
}, | ||
"regexp" : function(b, a) { | ||
return QUnit.objectType(b) === "regexp" | ||
&& a.source === b.source && // the regex itself | ||
a.global === b.global && // and its modifers | ||
// (gmi) ... | ||
a.ignoreCase === b.ignoreCase | ||
&& a.multiline === b.multiline; | ||
}, | ||
"array": function (b, a) { | ||
var i, j, loop; | ||
var len; | ||
// - 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"; | ||
}, | ||
// b could be an object literal here | ||
if ( ! (QUnit.objectType(b) === "array")) { | ||
return false; | ||
} | ||
"array" : function(b, a) { | ||
var i, j, loop; | ||
var len; | ||
len = a.length; | ||
if (len !== b.length) { // safe and faster | ||
return false; | ||
} | ||
// b could be an object literal here | ||
if (!(QUnit.objectType(b) === "array")) { | ||
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; | ||
}, | ||
len = a.length; | ||
if (len !== b.length) { // safe and faster | ||
return false; | ||
} | ||
"object": function (b, a) { | ||
var i, j, loop; | ||
var eq = true; // unless we can proove it | ||
var aProperties = [], bProperties = []; // collection of strings | ||
// 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; | ||
}, | ||
// comparing constructors is more strict than using instanceof | ||
if ( a.constructor !== b.constructor) { | ||
return false; | ||
} | ||
"object" : function(b, a) { | ||
var i, j, loop; | ||
var eq = true; // unless we can proove it | ||
var aProperties = [], bProperties = []; // collection of | ||
// strings | ||
// stack constructor before traversing properties | ||
callers.push(a.constructor); | ||
//track reference to avoid circular references | ||
parents.push(a); | ||
// 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; | ||
} | ||
} | ||
for (i in a) { // be strict: don't ensures hasOwnProperty and go deep | ||
loop = false; | ||
for(j=0;j<parents.length;j++){ | ||
if(parents[j] === a[i]) | ||
loop = true; //don't go down the same path twice | ||
} | ||
aProperties.push(i); // collect a's properties | ||
// stack constructor before traversing properties | ||
callers.push(a.constructor); | ||
// track reference to avoid circular references | ||
parents.push(a); | ||
if (!loop && ! innerEquiv(a[i], b[i])) { | ||
eq = false; | ||
break; | ||
} | ||
} | ||
for (i in a) { // be strict: don't ensures hasOwnProperty | ||
// and go deep | ||
loop = false; | ||
for (j = 0; j < parents.length; j++) { | ||
if (parents[j] === a[i]) | ||
loop = true; // don't go down the same path | ||
// twice | ||
} | ||
aProperties.push(i); // collect a's properties | ||
callers.pop(); // unstack, we are done | ||
parents.pop(); | ||
if (!loop && !innerEquiv(a[i], b[i])) { | ||
eq = false; | ||
break; | ||
} | ||
} | ||
for (i in b) { | ||
bProperties.push(i); // collect b's properties | ||
} | ||
callers.pop(); // unstack, we are done | ||
parents.pop(); | ||
// Ensures identical properties name | ||
return eq && innerEquiv(aProperties.sort(), bProperties.sort()); | ||
} | ||
}; | ||
}(); | ||
for (i in b) { | ||
bProperties.push(i); // collect b's properties | ||
} | ||
innerEquiv = function () { // can take multiple arguments | ||
var args = Array.prototype.slice.apply(arguments); | ||
if (args.length < 2) { | ||
return true; // end transition | ||
} | ||
// Ensures identical properties name | ||
return eq | ||
&& innerEquiv(aProperties.sort(), bProperties | ||
.sort()); | ||
} | ||
}; | ||
}(); | ||
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]); | ||
} | ||
innerEquiv = function() { // can take multiple arguments | ||
var args = Array.prototype.slice.apply(arguments); | ||
if (args.length < 2) { | ||
return true; // end transition | ||
} | ||
// apply transition with (1..n) arguments | ||
})(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); | ||
}; | ||
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 ]); | ||
} | ||
return innerEquiv; | ||
// 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 | ||
* 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. | ||
@@ -1026,7 +1291,7 @@ * @version 1.0.0 | ||
}; | ||
function array( arr ) { | ||
var i = arr.length, ret = Array(i); | ||
function array( arr, stack ) { | ||
var i = arr.length, ret = Array(i); | ||
this.up(); | ||
while ( i-- ) | ||
ret[i] = this.parse( arr[i] ); | ||
ret[i] = this.parse( arr[i] , undefined , stack); | ||
this.down(); | ||
@@ -1039,9 +1304,19 @@ return join( '[', ret, ']' ); | ||
var jsDump = { | ||
parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance | ||
var parser = this.parsers[ type || this.typeOf(obj) ]; | ||
parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance | ||
stack = stack || [ ]; | ||
var parser = this.parsers[ type || this.typeOf(obj) ]; | ||
type = typeof parser; | ||
return type == 'function' ? parser.call( this, obj ) : | ||
type == 'string' ? parser : | ||
this.parsers.error; | ||
var inStack = inArray(obj, stack); | ||
if (inStack != -1) { | ||
return 'recursion('+(inStack - stack.length)+')'; | ||
} | ||
//else | ||
if (type == 'function') { | ||
stack.push(obj); | ||
var res = parser.call( this, obj, stack ); | ||
stack.pop(); | ||
return res; | ||
} | ||
// else | ||
return (type == 'string') ? parser : this.parsers.error; | ||
}, | ||
@@ -1060,3 +1335,3 @@ typeOf:function( obj ) { | ||
type = "function"; | ||
} else if (obj.setInterval && obj.document && !obj.nodeType) { | ||
} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { | ||
type = "window"; | ||
@@ -1067,3 +1342,8 @@ } else if (obj.nodeType === 9) { | ||
type = "node"; | ||
} else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { | ||
} 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"; | ||
@@ -1108,3 +1388,3 @@ } else { | ||
'null':'null', | ||
undefined:'undefined', | ||
'undefined':'undefined', | ||
'function':function( fn ) { | ||
@@ -1117,4 +1397,4 @@ var ret = 'function', | ||
ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); | ||
return join( ret, this.parse(fn,'functionCode'), '}' ); | ||
ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); | ||
return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); | ||
}, | ||
@@ -1124,13 +1404,15 @@ array: array, | ||
arguments: array, | ||
object:function( map ) { | ||
object:function( map, stack ) { | ||
var ret = [ ]; | ||
this.up(); | ||
for ( var key in map ) | ||
ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); | ||
this.down(); | ||
QUnit.jsDump.up(); | ||
for ( var key in map ) { | ||
var val = map[key]; | ||
ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); | ||
} | ||
QUnit.jsDump.down(); | ||
return join( '{', ret, '}' ); | ||
}, | ||
node:function( node ) { | ||
var open = this.HTML ? '<' : '<', | ||
close = this.HTML ? '>' : '>'; | ||
var open = QUnit.jsDump.HTML ? '<' : '<', | ||
close = QUnit.jsDump.HTML ? '>' : '>'; | ||
@@ -1140,6 +1422,6 @@ var tag = node.nodeName.toLowerCase(), | ||
for ( var a in this.DOMAttrs ) { | ||
var val = node[this.DOMAttrs[a]]; | ||
for ( var a in QUnit.jsDump.DOMAttrs ) { | ||
var val = node[QUnit.jsDump.DOMAttrs[a]]; | ||
if ( val ) | ||
ret += ' ' + a + '=' + this.parse( val, 'attribute' ); | ||
ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); | ||
} | ||
@@ -1172,4 +1454,4 @@ return ret + close + open + '/' + tag + close; | ||
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) | ||
indentChar:' ',//indentation unit | ||
multiline:false //if true, items in a collection, are separated by a \n, else just a space. | ||
indentChar:' ',//indentation unit | ||
multiline:true //if true, items in a collection, are separated by a \n, else just a space. | ||
}; | ||
@@ -1200,2 +1482,17 @@ | ||
//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; | ||
} | ||
/* | ||
@@ -1216,5 +1513,5 @@ * Javascript Diff Algorithm | ||
QUnit.diff = (function() { | ||
function diff(o, n){ | ||
var ns = new Object(); | ||
var os = new Object(); | ||
function diff(o, n) { | ||
var ns = {}; | ||
var os = {}; | ||
@@ -1224,3 +1521,3 @@ for (var i = 0; i < n.length; i++) { | ||
ns[n[i]] = { | ||
rows: new Array(), | ||
rows: [], | ||
o: null | ||
@@ -1234,3 +1531,3 @@ }; | ||
os[o[i]] = { | ||
rows: new Array(), | ||
rows: [], | ||
n: null | ||
@@ -1242,2 +1539,5 @@ }; | ||
for (var i in ns) { | ||
if ( !hasOwn.call( ns, i ) ) { | ||
continue; | ||
} | ||
if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { | ||
@@ -1289,3 +1589,3 @@ n[ns[i].rows[0]] = { | ||
return function(o, n){ | ||
return function(o, n) { | ||
o = o.replace(/\s+$/, ''); | ||
@@ -1292,0 +1592,0 @@ n = n.replace(/\s+$/, ''); |
@@ -14,3 +14,3 @@ var context = this, | ||
each("123456", function(e, index, st) { | ||
ok(st[index] === e, "invalid each loop behavior over a string"); | ||
ok(st.charAt(index) === e, "invalid each loop behavior over a string"); | ||
}); | ||
@@ -82,12 +82,20 @@ }); | ||
ok( !isMap([ 1, 2, 3 ]), "jsface.isMap works incorrectly"); | ||
ok( !isMap(1), "jsface.isMap works incorrectly"); | ||
ok( !isMap(1234), "jsface.isMap works incorrectly"); | ||
}); | ||
test("Check type with jsface.isMap on iframe", function() { | ||
var iframe, IObject; | ||
var frame, iframe, IObject; | ||
iframe = document.createElement("iframe"); | ||
document.body.appendChild(iframe); | ||
IObject = window.frames[window.frames.length - 1].Object; | ||
frame = window.frames[window.frames.length - 1]; | ||
// getObject returns JavaScript Object in iframe context | ||
frame.getObject = function() { | ||
return Object; | ||
}; | ||
IObject = frame.getObject(); | ||
var map = new IObject(); | ||
@@ -115,8 +123,14 @@ map.one = 1; | ||
test("Check type with jsface.isArray on iframe", function() { | ||
var iframe, IArray, array; | ||
var frame, iframe, IArray, array; | ||
iframe = document.createElement("iframe"); | ||
document.body.appendChild(iframe); | ||
IArray = window.frames[window.frames.length - 1].Array; | ||
frame = window.frames[window.frames.length - 1]; | ||
frame.getArray = function() { | ||
return Array; | ||
}; | ||
IArray = frame.getArray(); | ||
array = new IArray(1, 2, 3); | ||
@@ -141,8 +155,14 @@ | ||
test("Check type with jsface.isFunction on iframe", function() { | ||
var iframe, IFunction, fn; | ||
var frame, iframe, IFunction, fn; | ||
iframe = document.createElement("iframe"); | ||
document.body.appendChild(iframe); | ||
IFunction = window.frames[window.frames.length - 1].Function; | ||
frame = window.frames[window.frames.length - 1]; | ||
frame.getFunction = function() { | ||
return Function; | ||
}; | ||
IFunction = frame.getFunction(); | ||
fn = new IFunction(); | ||
@@ -183,3 +203,28 @@ ok(isFunction(fn), "jsface.isFunction works incorrectly"); | ||
// Helper to inject a script and execute a callback when it's fully loaded | ||
// Note: Old versions of Opera don't work well with this method (11.60 works perfectly). | ||
function getScript(url, callback) { | ||
var head = document.getElementsByTagName("head")[0], | ||
script = document.createElement("script"), | ||
done = false; | ||
script.src = url; | ||
script.type = "text/javascript"; | ||
script.onload = script.onreadystatechange = function() { | ||
if ( !done && ( !this.readyState || this.readyState == "loaded" || this.readyState == "complete")) { | ||
done = true; | ||
callback(); | ||
script.onload = script.onreadystatechange = null; | ||
head.removeChild(script); | ||
} | ||
} | ||
head.appendChild(script); | ||
} | ||
asyncTest("CommonJS support", function() { | ||
var head = document.getElementsByTagName("head")[0]; | ||
// this unit test is run on browsers for sure | ||
@@ -193,8 +238,3 @@ ok(jsface, "jsface must be available globally"); | ||
// reload jsface.js | ||
var script = document.createElement("script"); | ||
script.src = "../jsface.js"; | ||
script.type = "text/javascript"; | ||
script.onload = function() { | ||
getScript("../jsface.js", function() { | ||
var exports = context.module.exports; | ||
@@ -211,28 +251,20 @@ start(); | ||
script.onload = null; | ||
delete context.module; | ||
} | ||
document.body.appendChild(script); | ||
context.module = undefined; | ||
}); | ||
}); | ||
asyncTest("noConflict support", function() { | ||
var clazz = context.Class; | ||
var head = document.getElementsByTagName("head")[0], | ||
clazz = context.Class; | ||
context.Class = function() { return 1; }; | ||
// reload jsface.js | ||
var script = document.createElement("script"); | ||
script.src = "../jsface.js"; | ||
script.type = "text/javascript"; | ||
script.onload = function() { | ||
getScript("../jsface.js", function() { | ||
jsface.noConflict(); | ||
ok(Class() === 1, "noConflict works incorrectly"); | ||
start(); | ||
script.onload = null; | ||
context.Class = clazz; | ||
} | ||
ok(Class() === 1, "noConflict works incorrectly"); | ||
document.body.appendChild(script); | ||
context.Class = jsface.Class; | ||
}); | ||
}); | ||
@@ -513,2 +545,143 @@ | ||
test("Mixin: instance extends class", function() { | ||
var Foo = Class({ | ||
constructor: function(name) { | ||
this.name = name; | ||
}, | ||
welcome: function() { | ||
return "Welcome " + this.name; | ||
}, | ||
sayHi: function() { | ||
return "Hello World " + this.name; | ||
} | ||
}); | ||
var Bar = Class({ | ||
constructor: function(name) { | ||
this.name = name; | ||
}, | ||
welcome: function() { | ||
return "invalid"; | ||
}, | ||
sayBye: function() { | ||
return "Bye!"; | ||
} | ||
}); | ||
var bar = new Bar("John Rambo"); | ||
extend(bar, Foo); | ||
ok(bar.name === "John Rambo", "Invalid extend() behavior, constructor must be bound correctly"); | ||
ok(bar.welcome() === "Welcome John Rambo", "Invalid extend() behavior, property must be overriden properly"); | ||
ok(bar.sayHi() === "Hello World John Rambo", "Invalid extend() behavior"); | ||
ok(bar.sayBye() === "Bye!", "Invalid extend() behavior"); | ||
}); | ||
test("Mixin: instance extends multiple classes", function() { | ||
var Foo = Class({ | ||
constructor: function(name) { | ||
this.name = name; | ||
}, | ||
welcome: function() { | ||
return "Welcome " + this.name; | ||
}, | ||
sayHi: function() { | ||
return "Hello World " + this.name; | ||
} | ||
}); | ||
var Properties = Class({ | ||
setProperty: function(key, value) { | ||
this[key] = value; | ||
}, | ||
getProperty: function(key) { | ||
return this[key]; | ||
} | ||
}); | ||
var Bar = Class({ | ||
constructor: function(name) { | ||
this.name = name; | ||
}, | ||
welcome: function() { | ||
return "invalid"; | ||
}, | ||
sayBye: function() { | ||
return "Bye!"; | ||
} | ||
}); | ||
var bar = new Bar("John Rambo"); | ||
extend(bar, [ Foo, Properties ]); | ||
bar.setProperty("fooKey", "fooValue"); | ||
ok(bar.name === "John Rambo", "Invalid extend() behavior, constructor must be bound correctly"); | ||
ok(bar.welcome() === "Welcome John Rambo", "Invalid extend() behavior, property must be overriden properly"); | ||
ok(bar.sayHi() === "Hello World John Rambo", "Invalid extend() behavior"); | ||
ok(bar.sayBye() === "Bye!", "Invalid extend() behavior"); | ||
ok(bar.getProperty("fooKey") === "fooValue", "Invalid extend() behavior"); | ||
}); | ||
test("Mixin: instance extends class and instance", function() { | ||
var Foo = Class({ | ||
constructor: function(name) { | ||
this.name = name; | ||
}, | ||
welcome: function() { | ||
return "Welcome " + this.name; | ||
}, | ||
sayHi: function() { | ||
return "Hello World " + this.name; | ||
} | ||
}); | ||
var Properties = Class({ | ||
setProperty: function(key, value) { | ||
this[key] = value; | ||
}, | ||
getProperty: function(key) { | ||
return this[key]; | ||
} | ||
}); | ||
var Bar = Class({ | ||
constructor: function(name) { | ||
this.name = name; | ||
}, | ||
welcome: function() { | ||
return "invalid"; | ||
}, | ||
sayBye: function() { | ||
return "Bye!"; | ||
} | ||
}); | ||
var bar = new Bar("John Rambo"); | ||
extend(bar, [ Foo, new Properties() ]); | ||
bar.setProperty("fooKey", "fooValue"); | ||
ok(bar.name === "John Rambo", "Invalid extend() behavior, constructor must be bound correctly"); | ||
ok(bar.welcome() === "Welcome John Rambo", "Invalid extend() behavior, property must be overriden properly"); | ||
ok(bar.sayHi() === "Hello World John Rambo", "Invalid extend() behavior"); | ||
ok(bar.sayBye() === "Bye!", "Invalid extend() behavior"); | ||
ok(bar.getProperty("fooKey") === "fooValue", "Invalid extend() behavior"); | ||
}); | ||
test("Mixin: class extends singleton", function() { | ||
@@ -568,2 +741,3 @@ var Foo = Class({ | ||
}, | ||
constructor: function(name) { | ||
@@ -607,3 +781,3 @@ this.name = name; | ||
this.name = name; | ||
}, | ||
} | ||
}); | ||
@@ -642,3 +816,3 @@ | ||
this.name = name; | ||
}, | ||
} | ||
}); | ||
@@ -660,2 +834,10 @@ | ||
test("Mixin: extending native objects", function() { | ||
extend(String, { | ||
trim: function() { | ||
return this.replace(/^\s+|\s+$/g, ""); | ||
} | ||
}); | ||
ok(" Hello World ".trim() === "Hello World", "Invalid extend() binding String.prototype"); | ||
extend(Array, { | ||
@@ -767,3 +949,1 @@ sum: function() { | ||
}); | ||
Sorry, the diff of this file is not supported yet
110818
3047
245