lib/jspec.js in visionmedia-jspec-1.1.3 vs lib/jspec.js in visionmedia-jspec-1.1.4
- old
+ new
@@ -3,12 +3,12 @@
(function(){
JSpec = {
- version : '1.1.3',
- main : this,
+ version : '1.1.4',
+ file : '',
suites : [],
matchers : {},
stats : { specs : 0, assertions : 0, failures : 0, passes : 0 },
options : { profile : false },
@@ -27,10 +27,11 @@
* in order to provide specific helper methods to specific suites.
*
* To reset (usually in after hook) simply set to null like below:
*
* JSpec.context = null
+ *
*/
defaultContext : {
sandbox : function(name) {
sandbox = document.createElement('div')
@@ -39,110 +40,11 @@
return sandbox
}
},
// --- Objects
-
- /**
- * Matcher.
- *
- * There are many ways to define a matcher within JSpec. The first being
- * a string that is less than 4 characters long, which is considered a simple
- * binary operation between two expressions. For example the matcher '==' simply
- * evaluates to 'actual == expected'.
- *
- * The second way to create a matcher is with a larger string, which is evaluated,
- * and then returned such as 'actual.match(expected)'.
- *
- * You may alias simply by starting a string with 'alias', such as 'be' : 'alias eql'.
- *
- * Finally an object may be used, and must contain a 'match' method, which is passed
- * both the expected, and actual values. Optionally a 'message' method may be used to
- * specify a custom message. Example:
- *
- * match : function(actual, expected) {
- * return typeof actual == expected
- * }
- *
- * @param {string} name
- * @param {hash, string} matcher
- * @param {object} actual
- * @param {array} expected
- * @param {bool} negate
- * @return {Matcher}
- * @api private
- */
-
- Matcher : function (name, matcher, actual, expected, negate) {
- self = this
- this.name = name
- this.message = ''
- this.passed = false
-
- // Define matchers from strings
-
- if (typeof matcher == 'string') {
- if (matcher.match(/^alias (\w+)/)) matcher = JSpec.matchers[matcher.match(/^alias (\w+)/)[1]]
- if (matcher.length < 4) body = 'actual ' + matcher + ' expected'
- else body = matcher
- matcher = { match : function(actual, expected) { return eval(body) } }
- }
-
- // Generate matcher message
-
- function generateMessage() {
- // TODO: clone expected instead of unshifting in this.match()
- expectedMessage = print.apply(this, expected.slice(1))
- return 'expected ' + print(actual) + ' to ' + (negate ? ' not ' : '') + name.replace(/_/g, ' ') + ' ' + expectedMessage
- }
-
- // Set message to matcher callback invocation or auto-generated message
-
- function setMessage() {
- self.message = typeof matcher.message == 'function' ?
- matcher.message(actual, expected, negate):
- generateMessage()
- }
-
- // Pass the matcher
-
- function pass() {
- setMessage()
- JSpec.stats.passes += 1
- self.passed = true
- }
-
- // Fail the matcher
-
- function fail() {
- setMessage()
- JSpec.stats.failures += 1
- }
-
- // Return result of match
-
- this.match = function() {
- expected.unshift(actual == null ? null : actual.valueOf())
- return matcher.match.apply(JSpec, expected)
- }
-
- // Boolean match result
-
- this.passes = function() {
- this.result = this.match()
- return negate? !this.result : this.result
- }
-
- // Performs match, and passes / fails the matcher
-
- this.exec = function() {
- this.passes() ? pass() : fail()
- return this
- }
- },
-
-
+
formatters : {
/**
* Default formatter, outputting to the DOM.
*
@@ -154,27 +56,27 @@
*/
DOM : function(results, options) {
id = option('reportToId') || 'jspec'
report = document.getElementById(id)
+ failuresOnly = option('failuresOnly')
classes = results.stats.failures ? 'has-failures' : ''
if (!report) error('requires the element #' + id + ' to output its reports')
markup =
'<div id="jspec-report" class="' + classes + '"><div class="heading"> \
<span class="passes">Passes: <em>' + results.stats.passes + '</em></span> \
<span class="failures">Failures: <em>' + results.stats.failures + '</em></span> \
</div><table class="suites">'
- function renderSuite(suite) {
- failuresOnly = option('failuresOnly')
+ renderSuite = function(suite) {
displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
if (displaySuite && suite.hasSpecs()) {
markup += '<tr class="description"><td colspan="2">' + suite.description + '</td></tr>'
each(suite.specs, function(i, spec){
markup += '<tr class="' + (i % 2 ? 'odd' : 'even') + '">'
- if (spec.requiresImplementation() && !failuresOnly)
+ if (spec.requiresImplementation())
markup += '<td class="requires-implementation" colspan="2">' + spec.description + '</td>'
else if (spec.passed() && !failuresOnly)
markup += '<td class="pass">' + spec.description+ '</td><td>' + spec.assertionsGraph() + '</td>'
else if(!spec.passed())
markup += '<td class="fail">' + spec.description + ' <em>' + spec.failure().message + '</em>' + '</td><td>' + spec.assertionsGraph() + '</td>'
@@ -182,11 +84,11 @@
})
markup += '</tr>'
}
}
- function renderSuites(suites) {
+ renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
@@ -195,22 +97,67 @@
markup += '</table></div>'
report.innerHTML = markup
},
+
+ /**
+ * Terminal formatter.
+ *
+ * @api public
+ */
+
+ Terminal : function(results, options) {
+ failuresOnly = option('failuresOnly')
+ puts(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
+ color(" Failures: ", 'bold') + color(results.stats.failures, 'red') + "\n")
+
+ indent = function(string) {
+ return string.replace(/^(.)/gm, ' $1')
+ }
+ renderSuite = function(suite) {
+ displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
+ if (displaySuite && suite.hasSpecs()) {
+ puts(color(' ' + suite.description, 'bold'))
+ results.each(suite.specs, function(spec){
+ assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
+ return graph + color('.', assertion.passed ? 'green' : 'red')
+ })
+ if (spec.requiresImplementation())
+ puts(color(' ' + spec.description, 'blue') + assertionsGraph)
+ else if (spec.passed() && !failuresOnly)
+ puts(color(' ' + spec.description, 'green') + assertionsGraph)
+ else
+ puts(color(' ' + spec.description, 'red') + assertionsGraph +
+ "\n" + indent(spec.failure().message) + "\n")
+ })
+ puts('')
+ }
+ }
+
+ renderSuites = function(suites) {
+ each(suites, function(suite){
+ renderSuite(suite)
+ if (suite.hasSuites()) renderSuites(suite.suites)
+ })
+ }
+
+ renderSuites(results.suites)
+ },
+
/**
* Console formatter, tested with Firebug and Safari 4.
*
* @api public
*/
Console : function(results, options) {
console.log('')
console.log('Passes: ' + results.stats.passes + ' Failures: ' + results.stats.failures)
- function renderSuite(suite) {
+ renderSuite = function(suite) {
if (suite.ran) {
console.group(suite.description)
results.each(suite.specs, function(spec){
assertionCount = spec.assertions.length + ':'
if (spec.requiresImplementation())
@@ -222,86 +169,115 @@
})
console.groupEnd()
}
}
- function renderSuites(suites) {
+ renderSuites = function(suites) {
each(suites, function(suite){
renderSuite(suite)
if (suite.hasSuites()) renderSuites(suite.suites)
})
}
renderSuites(results.suites)
}
},
-
+
+ Assertion : function(matcher, actual, expected, negate) {
+ extend(this, {
+ message : '',
+ passed : false,
+ actual : actual,
+ negate : negate,
+ matcher : matcher,
+ expected : expected,
+ record : function(result) {
+ result ? JSpec.stats.passes++ : JSpec.stats.failures++
+ },
+
+ exec : function() {
+ // TODO: remove unshifting of expected
+ expected.unshift(actual == null ? null : actual.valueOf())
+ result = matcher.match.apply(JSpec, expected)
+ this.passed = negate ? !result : result
+ this.record(this.passed)
+ if (!this.passed) this.message = matcher.message(actual, expected, negate, matcher.name)
+ return this
+ }
+ })
+ },
+
/**
* Specification Suite block object.
*
* @param {string} description
* @param {function} body
* @api private
*/
Suite : function(description, body) {
- this.body = body, this.suites = [], this.specs = []
- this.description = description, this.ran = false
- this.hooks = { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] }
+ extend(this, {
+ body: body,
+ description: description,
+ suites: [],
+ specs: [],
+ ran: false,
+ hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] },
+
+ // Add a spec to the suite
- // Add a spec to the suite
+ it : function(description, body) {
+ spec = new JSpec.Spec(description, body)
+ this.specs.push(spec)
+ spec.suite = this
+ },
- this.addSpec = function(description, body) {
- spec = new JSpec.Spec(description, body)
- this.specs.push(spec)
- spec.suite = this
- }
-
- // Add a hook to the suite
-
- this.addHook = function(hook, body) {
- this.hooks[hook].push(body)
- }
-
- // Add a nested suite
-
- this.addSuite = function(description, body) {
- suite = new JSpec.Suite(description, body)
- suite.description = this.description + ' ' + suite.description
- this.suites.push(suite)
- suite.suite = this
- }
+ // Add a hook to the suite
- // Invoke a hook in context to this suite
+ addHook : function(hook, body) {
+ this.hooks[hook].push(body)
+ },
- this.hook = function(hook) {
- each(this.hooks[hook], function(body) {
- JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + this.description + "': ")
- })
- }
-
- // Check if nested suites are present
-
- this.hasSuites = function() {
- return this.suites.length
- }
-
- // Check if this suite has specs
-
- this.hasSpecs = function() {
- return this.specs.length
- }
-
- // Check if the entire suite passed
+ // Add a nested suite
- this.passed = function() {
- var passed = true
- each(this.specs, function(spec){
- if (!spec.passed()) passed = false
- })
- return passed
- }
+ describe : function(description, body) {
+ suite = new JSpec.Suite(description, body)
+ suite.description = this.description + ' ' + suite.description
+ this.suites.push(suite)
+ suite.suite = this
+ },
+
+ // Invoke a hook in context to this suite
+
+ hook : function(hook) {
+ each(this.hooks[hook], function(body) {
+ JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + this.description + "': ")
+ })
+ },
+
+ // Check if nested suites are present
+
+ hasSuites : function() {
+ return this.suites.length
+ },
+
+ // Check if this suite has specs
+
+ hasSpecs : function() {
+ return this.specs.length
+ },
+
+ // Check if the entire suite passed
+
+ passed : function() {
+ var passed = true
+ each(this.specs, function(spec){
+ if (!spec.passed()) passed = false
+ })
+ return passed
+ }
+ })
},
/**
* Specification block object.
*
@@ -309,65 +285,153 @@
* @param {function} body
* @api private
*/
Spec : function(description, body) {
- this.body = body, this.description = description, this.assertions = []
+ extend(this, {
+ body : body,
+ description : description,
+ assertions : [],
+
+ // Find first failing assertion
- // Find first failing assertion
+ failure : function() {
+ return inject(this.assertions, null, function(failure, assertion){
+ return !assertion.passed && !failure ? assertion : failure
+ })
+ },
- this.failure = function() {
- return inject(this.assertions, null, function(failure, assertion){
- return !assertion.passed && !failure ? assertion : failure
- })
- }
-
- // Find all failing assertions
-
- this.failures = function() {
- return inject(this.assertions, [], function(failures, assertion){
- if (!assertion.passed) failures.push(assertion)
- return failures
- })
- }
+ // Find all failing assertions
- // Weither or not the spec passed
+ failures : function() {
+ return inject(this.assertions, [], function(failures, assertion){
+ if (!assertion.passed) failures.push(assertion)
+ return failures
+ })
+ },
- this.passed = function() {
- return !this.failure()
- }
+ // Weither or not the spec passed
- // Weither or not the spec requires implementation (no assertions)
+ passed : function() {
+ return !this.failure()
+ },
- this.requiresImplementation = function() {
- return this.assertions.length == 0
- }
-
- // Sprite based assertions graph
-
- this.assertionsGraph = function() {
- return map(this.assertions, function(assertion){
- return '<span class="assertion ' + (assertion.passed ? 'passed' : 'failed') + '"></span>'
- }).join('')
- }
+ // Weither or not the spec requires implementation (no assertions)
+
+ requiresImplementation : function() {
+ return this.assertions.length == 0
+ },
+
+ // Sprite based assertions graph
+
+ assertionsGraph : function() {
+ return map(this.assertions, function(assertion){
+ return '<span class="assertion ' + (assertion.passed ? 'passed' : 'failed') + '"></span>'
+ }).join('')
+ }
+ })
},
// --- Methods
/**
+ * Return ANSI-escaped colored string.
+ *
+ * @param {string} string
+ * @param {string} color
+ * @return {string}
+ * @api public
+ */
+
+ color : function(string, color) {
+ return "\u001B[" + {
+ bold : 1,
+ black : 30,
+ red : 31,
+ green : 32,
+ yellow : 33,
+ blue : 34,
+ magenta : 35,
+ cyan : 36,
+ white : 37,
+ }[color] + 'm' + string + "\u001B[0m"
+ },
+
+ /**
+ * Default matcher message callback.
+ *
+ * @api private
+ */
+
+ defaultMatcherMessage : function(actual, expected, negate, name) {
+ return 'expected ' + print(actual) + ' to ' +
+ (negate ? 'not ' : '') +
+ name.replace(/_/g, ' ') +
+ ' ' + print.apply(this, expected.slice(1))
+ },
+
+ /**
+ * Normalize a matcher message.
+ *
+ * When no messge callback is present the defaultMatcherMessage
+ * will be assigned, will suffice for most matchers.
+ *
+ * @param {hash} matcher
+ * @return {hash}
+ * @api public
+ */
+
+ normalizeMatcherMessage : function(matcher) {
+ if (typeof matcher.message != 'function')
+ matcher.message = this.defaultMatcherMessage
+ return matcher
+ },
+
+ /**
+ * Normalize a matcher body
+ *
+ * This process allows the following conversions until
+ * the matcher is in its final normalized hash state.
+ *
+ * - '==' becomes 'actual == expected'
+ * - 'actual == expected' becomes 'return actual == expected'
+ * - function(actual, expected) { return actual == expected } becomes
+ * { match : function(actual, expected) { return actual == expected }}
+ *
+ * @param {mixed} body
+ * @return {hash}
+ * @api public
+ */
+
+ normalizeMatcherBody : function(body) {
+ switch (body.constructor) {
+ case String:
+ if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
+ if (body.length < 4) body = 'actual ' + body + ' expected'
+ return { match : function(actual, expected) { return eval(body) }}
+
+ case Function:
+ return { match : body }
+
+ default:
+ return body
+ }
+ },
+
+ /**
* Get option value. This method first checks if
* the option key has been set via the query string,
* otherwise returning the options hash value.
*
* @param {string} key
* @return {mixed}
* @api public
*/
option : function(key) {
- if ((value = query(key)) !== null) return value
- else return JSpec.options[key] || null
+ return (value = query(key)) !== null ? value :
+ JSpec.options[key] || null
},
/**
* Generates a hash of the object passed.
*
@@ -471,13 +535,13 @@
* @api private
*/
match : function(actual, negate, name, expected) {
if (typeof negate == 'string') negate = negate == 'should' ? false : true
- matcher = new this.Matcher(name, this.matchers[name], actual, expected, negate)
- this.currentSpec.assertions.push(matcher.exec())
- return matcher.result
+ assertion = new JSpec.Assertion(this.matchers[name], actual, expected, negate)
+ this.currentSpec.assertions.push(assertion.exec())
+ return assertion.passed
},
/**
* Iterate an object, invoking the given callback.
*
@@ -491,11 +555,11 @@
if (typeof object == 'string') object = object.split(' ')
for (key in object) {
if (object.hasOwnProperty(key))
callback.length == 1 ?
callback.call(JSpec, object[key]):
- callback.call(JSpec, key, object[key])
+ callback.call(JSpec, key, object[key])
}
return JSpec
},
/**
@@ -510,11 +574,11 @@
inject : function(object, initial, callback) {
each(object, function(key, value){
initial = callback.length == 2 ?
callback.call(JSpec, initial, value):
- callback.call(JSpec, initial, key, value) || initial
+ callback.call(JSpec, initial, key, value) || initial
})
return initial
},
/**
@@ -529,10 +593,24 @@
strip : function(string, chars) {
return string.
replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
},
+
+ /**
+ * Extend an object with another.
+ *
+ * @param {object} object
+ * @param {object} other
+ * @api public
+ */
+
+ extend : function(object, other) {
+ each(other, function(property, value){
+ object[property] = value
+ })
+ },
/**
* Map callback return values.
*
* @param {hash, array} object
@@ -543,11 +621,11 @@
map : function(object, callback) {
return inject(object, [], function(memo, key, value){
memo.push(callback.length == 1 ?
callback.call(JSpec, value):
- callback.call(JSpec, key, value))
+ callback.call(JSpec, key, value))
})
},
/**
* Returns true if the callback returns true at least once.
@@ -561,11 +639,11 @@
any : function(object, callback) {
return inject(object, false, function(state, key, value){
if (state) return true
return callback.length == 1 ?
callback.call(JSpec, value):
- callback.call(JSpec, key, value)
+ callback.call(JSpec, key, value)
})
},
/**
* Define matchers.
@@ -574,24 +652,41 @@
* @return {JSpec}
* @api public
*/
addMatchers : function(matchers) {
- each(matchers, function(name, body){ this.matchers[name] = body })
+ each(matchers, function(name, body){
+ this.addMatcher(name, body)
+ })
return this
},
/**
+ * Define a matcher.
+ *
+ * @param {string} name
+ * @param {hash, function, string} body
+ * @return {JSpec}
+ * @api public
+ */
+
+ addMatcher : function(name, body) {
+ this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
+ this.matchers[name].name = name
+ return this
+ },
+
+ /**
* Add a root suite to JSpec.
*
* @param {string} description
* @param {body} function
* @return {JSpec}
* @api public
*/
- addSuite : function(description, body) {
+ describe : function(description, body) {
this.suites.push(new JSpec.Suite(description, body))
return this
},
/**
@@ -616,20 +711,20 @@
* @api private
*/
preprocess : function(input) {
return input.
- replace(/describe (.*?)$/m, 'JSpec.addSuite($1, function(){').
- replace(/describe (.*?)$/gm, 'this.addSuite($1, function(){').
- replace(/it (.*?)$/gm, 'this.addSpec($1, function(){').
+ replace(/describe (.*?)$/m, 'JSpec.describe($1, function(){').
+ replace(/describe (.*?)$/gm, 'this.describe($1, function(){').
+ replace(/it (.*?)$/gm, 'this.it($1, function(){').
replace(/^(?: *)(before_each|after_each|before|after)(?= |\n|$)/gm, 'this.addHook("$1", function(){').
replace(/end(?= |\n|$)/gm, '});').
- replace(/-{/g, 'function(){').
+ replace(/-\{/g, 'function(){').
replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
replace(/([\s\(]+)\./gm, '$1this.').
replace(/\.should([_\.]not)?[_\.](\w+)(?: |$)(.*)$/gm, '.should$1_$2($3)').
- replace(/(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)$/gm, 'JSpec.match($1, "$2", "$3", [$4]);')
+ replace(/([\/ ]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)$/gm, '$1 JSpec.match($2, "$3", "$4", [$5]);')
},
/**
* Create a range string which can be evaluated to a native array.
*
@@ -647,29 +742,30 @@
},
/**
* Report on the results.
*
- * @return {JSpec}
* @api public
*/
report : function() {
this.options.formatter ?
new this.options.formatter(this, this.options):
- new this.formatters.DOM(this, this.options)
- return this
+ new this.formatters.DOM(this, this.options)
},
/**
- * Run the spec suites.
+ * Run the spec suites. Options are merged
+ * with JSpec options when present.
*
+ * @param {hash} options
* @return {JSpec}
* @api public
*/
- run : function() {
+ run : function(options) {
+ if (options) extend(this.options, options)
if (option('profile')) console.group('Profile')
each(this.suites, function(suite) { this.runSuite(suite) })
if (option('profile')) console.groupEnd()
return this
},
@@ -697,10 +793,22 @@
this.runSuite(suite)
})
}
return this
},
+
+ /**
+ * Report a failure for the current spec.
+ *
+ * @param {string} message
+ * @api public
+ */
+
+ fail : function(message) {
+ JSpec.currentSpec.assertions.push({ passed : false, message : message })
+ JSpec.stats.failures++
+ },
/**
* Run a spec.
*
* @param {Spec} spec
@@ -709,11 +817,12 @@
runSpec : function(spec) {
this.currentSpec = spec
this.stats.specs++
if (option('profile')) console.time(spec.description)
- this.evalBody(spec.body, "Error in spec '" + spec.description + "': ")
+ try { this.evalBody(spec.body) }
+ catch (e) { fail(e) }
if (option('profile')) console.timeEnd(spec.description)
this.stats.assertions += spec.assertions.length
},
/**
@@ -724,28 +833,28 @@
* @api public
*/
requires : function(dependency, message) {
try { eval(dependency) }
- catch (e) { error('depends on ' + dependency + ' ' + (message || '')) }
+ catch (e) { error('JSpec depends on ' + dependency + ' ' + message, '') }
},
/**
* Query against the current query strings keys
* or the queryString specified.
*
* @param {string} key
* @param {string} queryString
* @return {string, null}
- * @api public
+ * @api private
*/
query : function(key, queryString) {
- queryString = (queryString || window.location.search || '').substring(1)
+ queryString = (queryString || (main.location ? main.location.search : null) || '').substring(1)
return inject(queryString.split('&'), null, function(value, pair){
parts = pair.split('=')
- return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
+ return parts[0] == key ? parts[1].replace(/%20|\+/gmi, ' ') : value
})
},
/**
* Throw a JSpec related error.
@@ -754,30 +863,83 @@
* @param {Exception} e
* @api public
*/
error : function(message, e) {
- throw 'jspec: ' + message + (e ? e.message : '') + ' near line ' + e.line
+ throw (message ? message : '') + e.toString() +
+ (e.line ? ' near line ' + e.line : '') +
+ (JSpec.file ? ' in ' + JSpec.file : '')
},
+
+ /**
+ * Ad-hoc POST request for JSpec server usage.
+ *
+ * @param {string} url
+ * @param {string} data
+ * @api private
+ */
+
+ post : function(url, data) {
+ request = this.xhr()
+ request.open('POST', url, false)
+ request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
+ request.send(data)
+ },
/**
+ * Report back to server with statistics.
+ *
+ * @api private
+ */
+
+ reportToServer : function() {
+ JSpec.post('http://localhost:4444', 'passes=' + JSpec.stats.passes + '&failures=' + JSpec.stats.failures)
+ if ('close' in window) window.close()
+ },
+
+ /**
+ * Instantiate an XMLHttpRequest.
+ *
+ * @return {ActiveXObject, XMLHttpRequest}
+ * @api private
+ */
+
+ xhr : function() {
+ return window.ActiveXObject ?
+ new ActiveXObject("Microsoft.XMLHTTP"):
+ new XMLHttpRequest()
+ },
+
+ /**
+ * Check for HTTP request support.
+ *
+ * @return {bool}
+ * @api private
+ */
+
+ hasXhr : function() {
+ return 'XMLHttpRequest' in main || 'ActiveXObject' in main
+ },
+
+ /**
* Load a files contents.
*
* @param {string} file
* @return {string}
* @api public
*/
load : function(file) {
- if ('XMLHttpRequest' in this.main) {
- request = new XMLHttpRequest
+ this.file = file
+ if (this.hasXhr()) {
+ request = this.xhr()
request.open('GET', file, false)
request.send(null)
if (request.readyState == 4) return request.responseText
}
- else if ('load' in this.main)
- load(file) // TODO: workaround for IO issue / preprocessing
+ else if ('readFile' in main)
+ return readFile(file)
else
error('cannot load ' + file)
},
/**
@@ -794,36 +956,42 @@
}
}
// --- Utility functions
+ main = this
+ puts = print
map = JSpec.map
any = JSpec.any
last = JSpec.last
+ fail = JSpec.fail
range = JSpec.range
each = JSpec.each
option = JSpec.option
inject = JSpec.inject
error = JSpec.error
escape = JSpec.escape
+ extend = JSpec.extend
print = JSpec.print
hash = JSpec.hash
query = JSpec.query
strip = JSpec.strip
+ color = JSpec.color
addMatchers = JSpec.addMatchers
// --- Matchers
addMatchers({
- be : "alias eql",
equal : "===",
+ be : "alias equal",
be_greater_than : ">",
be_less_than : "<",
be_at_least : ">=",
be_at_most : "<=",
be_a : "actual.constructor == expected",
be_an : "alias be_a",
+ be_an_instance_of : "actual instanceof expected",
be_null : "actual == null",
be_empty : "actual.length == 0",
be_true : "actual == true",
be_false : "actual == false",
be_type : "typeof actual == expected",
@@ -831,16 +999,18 @@
respond_to : "typeof actual[expected] == 'function'",
have_length : "actual.length == expected",
be_within : "actual >= expected[0] && actual <= last(expected)",
have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
- eql : { match : function(actual, expected) {
- if (actual.constructor == Array || actual.constructor == Object) return hash(actual) == hash(expected)
- else return actual == expected
- }},
+ eql : function(actual, expected) {
+ return actual.constructor == Array ||
+ actual instanceof Object ?
+ hash(actual) == hash(expected):
+ actual == expected
+ },
- include : { match : function(actual) {
+ include : function(actual) {
for (state = true, i = 1; i < arguments.length; i++) {
arg = arguments[i]
switch (actual.constructor) {
case String:
case Number:
@@ -858,46 +1028,53 @@
break
}
if (!state) return false
}
return true
- }},
+ },
- throw_error : { match : function(actual, expected) {
+ throw_error : function(actual, expected) {
try { actual() }
catch (e) {
- if (expected == undefined) return true
- else return expected.constructor == RegExp ?
- expected.test(e) : e.toString() == expected
+ if (expected == undefined) return true
+ switch (expected.constructor) {
+ case RegExp: return expected.test(e)
+ case Function: return e instanceof expected
+ case String: return expected == e.toString()
+ }
}
- }},
+ },
- have : { match : function(actual, length, property) {
+ have : function(actual, length, property) {
return actual[property].length == length
- }},
+ },
- have_at_least : { match : function(actual, length, property) {
+ have_at_least : function(actual, length, property) {
return actual[property].length >= length
- }},
+ },
- have_at_most : { match : function(actual, length, property) {
+ have_at_most :function(actual, length, property) {
return actual[property].length <= length
- }},
+ },
- have_within : { match : function(actual, range, property) {
+ have_within : function(actual, range, property) {
length = actual[property].length
return length >= range.shift() && length <= range.pop()
- }},
+ },
- have_prop : { match : function(actual, property, value) {
- if (actual[property] == null || typeof actual[property] == 'function') return false
- return value == null ? true : JSpec.matchers['eql'].match(actual[property], value)
- }},
+ have_prop : function(actual, property, value) {
+ return actual[property] == null ||
+ actual[property] instanceof Function ? false:
+ value == null ? true:
+ JSpec.matchers.eql.match(actual[property], value)
+ },
- have_property : { match : function(actual, property, value) {
- if (actual[property] == null || typeof actual[property] == 'function') return false
- return value == null ? true : value === actual[property]
- }}
+ have_property : function(actual, property, value) {
+ return actual[property] == null ||
+ actual[property] instanceof Function ? false:
+ value == null ? true:
+ value === actual[property]
+ }
})
// --- Expose
this.JSpec = JSpec