Created
December 12, 2009 18:59
-
-
Save sidonath/255007 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function($) { | |
function print_array(obj, opts) { | |
var result = []; | |
for (var i = 0; i < Math.min(opts.max_array, obj.length); i++) | |
result.push($.print(obj[i], $.extend({}, opts, { max_array: 3, max_string: 40 }))); | |
if (obj.length > opts.max_array) | |
result.push((obj.length - opts.max_array) + ' more...'); | |
if (result.length == 0) return "[]" | |
return "[ " + result.join(", ") + " ]"; | |
} | |
function print_element(obj) { | |
if (obj.nodeType == 1) { | |
var result = []; | |
var properties = [ 'className', 'id' ]; | |
var extra = { | |
'input': ['type', 'name', 'value'], | |
'a': ['href', 'target'], | |
'form': ['method', 'action'], | |
'script': ['src'], | |
'link': ['href'], | |
'img': ['src'] | |
}; | |
$.each(properties.concat(extra[obj.tagName.toLowerCase()] || []), function(){ | |
if (obj[this]) | |
result.push(' ' + this.replace('className', 'class') + "=" + $.print(obj[this])) | |
}); | |
return "<" + obj.tagName.toLowerCase() | |
+ result.join('') + ">"; | |
} | |
} | |
function print_object(obj, opts) { | |
var seen = opts.seen || [ obj ]; | |
var result = [], key, value; | |
for (var k in obj) { | |
if (obj.hasOwnProperty(k) && $.inArray(obj[k], seen) < 0) { | |
seen.push(obj[k]); | |
value = $.print(obj[k], $.extend({}, opts, { max_array: 6, max_string: 40, seen: seen })); | |
} else | |
value = "..."; | |
result.push(k + ": " + value); | |
} | |
if (result.length == 0) return "{}"; | |
return "{ " + result.join(", ") + " }"; | |
} | |
function print_string(value, opts) { | |
var character_substitutions = { | |
'\b': '\\b', | |
'\t': '\\t', | |
'\n': '\\n', | |
'\f': '\\f', | |
'\r': '\\r', | |
'"' : '\\"', | |
'\\': '\\\\' | |
}; | |
var r = /["\\\x00-\x1f\x7f-\x9f]/g; | |
var str = r.test(value) | |
? '"' + value.replace(r, function (a) { | |
var c = character_substitutions[a]; | |
if (c) return c; | |
c = a.charCodeAt(); | |
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); | |
}) + '"' | |
: '"' + value + '"'; | |
if (str.length > opts.max_string) | |
return str.slice(0, opts.max_string + 1) + '..."'; | |
else | |
return str; | |
} | |
$.print = function(obj, options) { | |
var opts = $.extend({}, { max_array: 10, max_string: 100 }, options); | |
if (typeof obj == 'undefined') | |
return "undefined"; | |
else if (typeof obj == 'boolean') | |
return obj.toString(); | |
else if (!obj && typeof obj == 'number') | |
return 'NaN'; | |
else if (!obj) | |
return "null"; | |
else if (typeof obj == 'string') | |
return print_string(obj, opts); | |
else if (obj instanceof RegExp) | |
return obj.toString(); | |
else if (obj instanceof Array || obj.callee || obj.item) | |
return print_array(obj, opts); | |
else if (typeof obj == 'function' || obj instanceof Function) | |
return obj.toString().match(/^([^)]*\))/)[1]; | |
else if (obj.nodeType) | |
return print_element(obj); | |
else if (obj instanceof jQuery) | |
return "$(" + $.print(obj.get()) + ")"; | |
else if (obj instanceof Error) | |
return print_object(obj, $.extend({}, options, { max_string: 200 })); | |
else if (obj instanceof Object) | |
return print_object(obj, opts); | |
else | |
return obj.toString().replace(/\n\s*/g, ''); | |
}; | |
})(jQuery); | |
var Screw = (function() { | |
function Context(name) { | |
this.tc = null; | |
this.name = ''; | |
if (name) { | |
this.tc = TestCase(name); | |
this.name = name; | |
} | |
this.befores = []; | |
this.afters = []; | |
this.parent = null; | |
/** Returns function that executes a given array of functions */ | |
function functionListInvoker(fns) { | |
return function() { | |
var i; | |
for (i = 0; i < fns.length; i++) { | |
fns[i].call(); | |
} | |
}; | |
}; | |
/** Push new context as a child of the current one | |
* A Screw.Unit's `define` maps to JTD's `TestCase` | |
*/ | |
this.push = function(name) { | |
var | |
i, oldBefores, oldAfters, | |
c = new Context((this.name ? this.name + ': ' : '') + name) | |
; | |
// simply copy all parent's befores and afters to current context | |
c.befores = []; | |
c.afters = []; | |
for(i = 0; i < this.befores.length; i++) { | |
c.befores.push(this.befores[i]); | |
} | |
for(i = 0; i < this.afters.length; i++) { | |
c.afters.push(this.afters[i]); | |
} | |
c.parent = this; | |
return c; | |
}; | |
/** Adds an `it` as a test to the current TestCase */ | |
this.defineTest = function(name, fn) { | |
this.tc.prototype.setUp = functionListInvoker(this.befores); | |
this.tc.prototype.tearDown = functionListInvoker(this.afters); | |
this.tc.prototype['test ' + name] = fn; | |
}; | |
} | |
var screw = { | |
Unit: function(fn) { | |
var contents = fn.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]; | |
var fn = new Function("matchers", "specifications", | |
"with (specifications) { with (matchers) { " + contents + " } }" | |
); | |
fn.call(this, Screw.Matchers, Screw.Specifications); | |
}, | |
Specifications: { | |
context: new Context(), | |
describe: function(name, fn) { | |
var dc = this.context.push(name) | |
this.context = dc; | |
fn.call(); | |
this.context = dc.parent; | |
}, | |
it: function(name, fn) { | |
this.context.defineTest(name, fn); | |
}, | |
before: function(fn) { | |
this.context.befores.push(fn); | |
}, | |
after: function(fn) { | |
this.context.afters.push(fn); | |
} | |
} | |
}; | |
return screw; | |
})(); | |
Screw.Matchers = (function($) { | |
return matchers = { | |
expect: function(actual) { | |
return { | |
to: function(matcher, expected, not) { | |
var matched = matcher.match(expected, actual); | |
if (not ? matched : !matched) { | |
fail(matcher.failure_message(expected, actual, not)); | |
} | |
}, | |
to_not: function(matcher, expected) { | |
this.to(matcher, expected, true); | |
} | |
} | |
}, | |
equal: { | |
match: function(expected, actual) { | |
if (expected instanceof Array) { | |
for (var i = 0; i < actual.length; i++) | |
if (!Screw.Matchers.equal.match(expected[i], actual[i])) return false; | |
return actual.length == expected.length; | |
} else if (expected instanceof Object) { | |
for (var key in expected) { | |
if (!Screw.Matchers.equal.match(expected[key], actual[key])) { | |
return false; | |
} | |
} | |
for (var key in actual) { | |
if (!Screw.Matchers.equal.match(actual[key], expected[key])) { | |
return false; | |
} | |
} | |
return true; | |
} else { | |
return expected == actual; | |
} | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected \n' + $.print(actual) + | |
(not ? ' to not equal ' : ' to equal ') + '\n' + $.print(expected); | |
} | |
}, | |
match: { | |
match: function(expected, actual) { | |
if (expected.constructor == RegExp) | |
return expected.exec(actual.toString()); | |
else | |
return actual.indexOf(expected) > -1; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not match ' : ' to match ') + $.print(expected); | |
} | |
}, | |
be_empty: { | |
match: function(expected, actual) { | |
if (actual.length == undefined) throw(actual.toString() + " does not respond to length"); | |
return actual.length == 0; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not be empty' : ' to be empty'); | |
} | |
}, | |
have_length: { | |
match: function(expected, actual) { | |
if (actual.length == undefined) throw(actual.toString() + " does not respond to length"); | |
return actual.length == expected; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not' : ' to') + ' have length ' + expected; | |
} | |
}, | |
be_null: { | |
match: function(expected, actual) { | |
return actual == null; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not be null' : ' to be null'); | |
} | |
}, | |
be_undefined: { | |
match: function(expected, actual) { | |
return actual == undefined; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not be undefined' : ' to be undefined'); | |
} | |
}, | |
be_true: { | |
match: function(expected, actual) { | |
return actual; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not be true' : ' to be true'); | |
} | |
}, | |
be_false: { | |
match: function(expected, actual) { | |
return !actual; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not be false' : ' to be false'); | |
} | |
}, | |
match_selector: { | |
match: function(expected, actual) { | |
if (!(actual instanceof jQuery)) { | |
throw expected.toString() + " must be an instance of jQuery to match against a selector" | |
} | |
return actual.is(expected); | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not match selector ' : ' to match selector ') + expected; | |
} | |
}, | |
contain_selector: { | |
match: function(expected, actual) { | |
if (!(actual instanceof jQuery)) { | |
throw expected.toString() + " must be an instance of jQuery to match against a selector" | |
} | |
return actual.find(expected).length > 0; | |
}, | |
failure_message: function(expected, actual, not) { | |
return 'expected ' + $.print(actual) + (not ? ' to not contain selector ' : ' to contain selector ') + expected; | |
} | |
} | |
} | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment