summaryrefslogtreecommitdiff
path: root/share/www
diff options
context:
space:
mode:
authorJan Lehnardt <jan@apache.org>2010-06-02 17:45:56 +0000
committerJan Lehnardt <jan@apache.org>2010-06-02 17:45:56 +0000
commitbaf25cefa01d305a47087686becef7de7147321a (patch)
tree4325283b8e57064be26daf421d8ded8930b36c9c /share/www
parent7fe84eba9982ebb3bcaa48b7aa28fdd2e130422d (diff)
Add tests for couch.js and jquery.couch.js
Patch by Lena Herrmann. Closes COUCHDB-783. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@950689 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'share/www')
-rw-r--r--share/www/script/couch.js31
-rw-r--r--share/www/script/jquery.couch.js50
-rw-r--r--share/www/script/jspec/jspec.css149
-rw-r--r--share/www/script/jspec/jspec.jquery.js72
-rw-r--r--share/www/script/jspec/jspec.js1756
-rw-r--r--share/www/script/jspec/jspec.xhr.js195
-rw-r--r--share/www/spec/couch_js_class_methods_spec.js389
-rw-r--r--share/www/spec/couch_js_instance_methods_1_spec.js299
-rw-r--r--share/www/spec/couch_js_instance_methods_2_spec.js234
-rw-r--r--share/www/spec/couch_js_instance_methods_3_spec.js203
-rw-r--r--share/www/spec/custom_helpers.js39
-rw-r--r--share/www/spec/jquery_couch_js_class_methods_spec.js511
-rw-r--r--share/www/spec/jquery_couch_js_instance_methods_1_spec.js190
-rw-r--r--share/www/spec/jquery_couch_js_instance_methods_2_spec.js421
-rw-r--r--share/www/spec/jquery_couch_js_instance_methods_3_spec.js528
-rw-r--r--share/www/spec/run.html33
16 files changed, 5064 insertions, 36 deletions
diff --git a/share/www/script/couch.js b/share/www/script/couch.js
index c5495424..dbffa7ce 100644
--- a/share/www/script/couch.js
+++ b/share/www/script/couch.js
@@ -22,10 +22,10 @@ function CouchDB(name, httpHeaders) {
this.last_req = null;
this.request = function(method, uri, requestOptions) {
- requestOptions = requestOptions || {}
- requestOptions.headers = combine(requestOptions.headers, httpHeaders)
- return CouchDB.request(method, uri, requestOptions);
- }
+ requestOptions = requestOptions || {}
+ requestOptions.headers = combine(requestOptions.headers, httpHeaders)
+ return CouchDB.request(method, uri, requestOptions);
+ }
// Creates the database on the server
this.createDb = function() {
@@ -198,12 +198,6 @@ function CouchDB(name, httpHeaders) {
return JSON.parse(this.last_req.responseText);
}
- this.viewCleanup = function() {
- this.last_req = this.request("POST", this.uri + "_view_cleanup");
- CouchDB.maybeThrowError(this.last_req);
- return JSON.parse(this.last_req.responseText);
- }
-
this.allDocs = function(options,keys) {
if(!keys) {
this.last_req = this.request("GET", this.uri + "_all_docs"
@@ -223,18 +217,11 @@ function CouchDB(name, httpHeaders) {
return this.allDocs({startkey:"_design", endkey:"_design0"});
};
- this.changes = function(options,keys) {
- var req = null;
- if(!keys) {
- req = this.request("GET", this.uri + "_changes" + encodeOptions(options));
- } else {
- req = this.request("POST", this.uri + "_changes" + encodeOptions(options), {
- headers: {"Content-Type": "application/json"},
- body: JSON.stringify({keys:keys})
- });
- }
- CouchDB.maybeThrowError(req);
- return JSON.parse(req.responseText);
+ this.changes = function(options) {
+ this.last_req = this.request("GET", this.uri + "_changes"
+ + encodeOptions(options));
+ CouchDB.maybeThrowError(this.last_req);
+ return JSON.parse(this.last_req.responseText);
}
this.compact = function() {
diff --git a/share/www/script/jquery.couch.js b/share/www/script/jquery.couch.js
index 4923dafe..cf29525c 100644
--- a/share/www/script/jquery.couch.js
+++ b/share/www/script/jquery.couch.js
@@ -1,4 +1,4 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+a// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
@@ -276,7 +276,7 @@
}
});
} else {
- alert("please provide an eachApp function for allApps()");
+ alert("Please provide an eachApp function for allApps()");
}
},
openDoc: function(docId, options, ajaxOptions) {
@@ -327,7 +327,7 @@
beforeSend : beforeSend,
complete: function(req) {
var resp = $.httpData(req, "json");
- if (req.status == 201) {
+ if (req.status == 201 || req.status == 202) {
doc._id = resp.id;
doc._rev = resp.rev;
if (versioned) {
@@ -372,13 +372,27 @@
"The document could not be deleted"
);
},
- copyDoc: function(doc, options, ajaxOptions) {
+ bulkRemove: function(docs, options){
+ docs.docs = $.each(
+ docs.docs, function(i, doc){
+ doc._deleted = true;
+ }
+ );
+ $.extend(options, {successStatus: 201});
+ ajax({
+ type: "POST",
+ url: this.uri + "_bulk_docs" + encodeOptions(options),
+ data: toJSON(docs)
+ },
+ options,
+ "The documents could not be deleted"
+ );
+ },
+ copyDoc: function(docId, options, ajaxOptions) {
ajaxOptions = $.extend(ajaxOptions, {
complete: function(req) {
var resp = $.httpData(req, "json");
if (req.status == 201) {
- doc._id = resp.id;
- doc._rev = resp.rev;
if (options.success) options.success(resp);
} else if (options.error) {
options.error(req.status, resp.error, resp.reason);
@@ -389,9 +403,7 @@
});
ajax({
type: "COPY",
- url: this.uri +
- encodeDocId(doc._id) +
- encodeOptions({rev: doc._rev})
+ url: this.uri + encodeDocId(docId)
},
options,
"The document could not be copied",
@@ -490,13 +502,14 @@
);
},
- replicate: function(source, target, options) {
+ replicate: function(source, target, ajaxOptions, replicationOptions) {
+ replicationOptions = $.extend({source: source, target: target}, replicationOptions);
ajax({
type: "POST", url: this.urlPrefix + "/_replicate",
- data: JSON.stringify({source: source, target: target}),
+ data: JSON.stringify(replicationOptions),
contentType: "application/json"
},
- options,
+ ajaxOptions,
"Replication failed"
);
},
@@ -516,7 +529,6 @@
}
return uuidCache.shift();
}
-
});
function ajax(obj, options, errorMessage, ajaxOptions) {
@@ -525,8 +537,18 @@
$.ajax($.extend($.extend({
type: "GET", dataType: "json",
+ beforeSend: function(xhr){
+ if(ajaxOptions && ajaxOptions.headers){
+ for (var header in ajaxOptions.headers){
+ xhr.setRequestHeader(header, ajaxOptions.headers[header]);
+ }
+ }
+ },
complete: function(req) {
var resp = $.httpData(req, "json");
+ if (options.ajaxStart) {
+ options.ajaxStart(resp);
+ }
if (req.status == options.successStatus) {
if (options.beforeSuccess) options.beforeSuccess(req, resp);
if (options.success) options.success(resp);
@@ -556,7 +578,7 @@
var buf = [];
if (typeof(options) === "object" && options !== null) {
for (var name in options) {
- if ($.inArray(name, ["error", "success"]) >= 0)
+ if ($.inArray(name, ["error", "success", "ajaxStart"]) >= 0)
continue;
var value = options[name];
if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) {
diff --git a/share/www/script/jspec/jspec.css b/share/www/script/jspec/jspec.css
new file mode 100644
index 00000000..629d41c5
--- /dev/null
+++ b/share/www/script/jspec/jspec.css
@@ -0,0 +1,149 @@
+body.jspec {
+ margin: 45px 0;
+ font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+ background: #efefef url(images/bg.png) top left repeat-x;
+ text-align: center;
+}
+#jspec {
+ margin: 0 auto;
+ padding-top: 30px;
+ width: 1008px;
+ background: url(images/vr.png) top left repeat-y;
+ text-align: left;
+}
+#jspec-top {
+ position: relative;
+ margin: 0 auto;
+ width: 1008px;
+ height: 40px;
+ background: url(images/sprites.bg.png) top left no-repeat;
+}
+#jspec-bottom {
+ margin: 0 auto;
+ width: 1008px;
+ height: 15px;
+ background: url(images/sprites.bg.png) bottom left no-repeat;
+}
+#jspec .loading {
+ margin-top: -45px;
+ width: 1008px;
+ height: 80px;
+ background: url(images/loading.gif) 50% 50% no-repeat;
+}
+#jspec-title {
+ position: absolute;
+ top: 15px;
+ left: 20px;
+ width: 160px;
+ font-size: 22px;
+ font-weight: normal;
+ background: url(images/sprites.png) 0 -126px no-repeat;
+ text-align: center;
+}
+#jspec-title em {
+ font-size: 10px;
+ font-style: normal;
+ color: #BCC8D1;
+}
+#jspec-report * {
+ margin: 0;
+ padding: 0;
+ background: none;
+ border: none;
+}
+#jspec-report {
+ padding: 15px 40px;
+ font: 11px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+ color: #7B8D9B;
+}
+#jspec-report.has-failures {
+ padding-bottom: 30px;
+}
+#jspec-report .hidden {
+ display: none;
+}
+#jspec-report .heading {
+ margin-bottom: 15px;
+}
+#jspec-report .heading span {
+ padding-right: 10px;
+}
+#jspec-report .heading .passes em {
+ color: #0ea0eb;
+}
+#jspec-report .heading .failures em {
+ color: #FA1616;
+}
+#jspec-report table {
+ font-size: 11px;
+ border-collapse: collapse;
+}
+#jspec-report td {
+ padding: 8px;
+ text-indent: 30px;
+ color: #7B8D9B;
+}
+#jspec-report tr.body {
+ display: none;
+}
+#jspec-report tr.body pre {
+ margin: 0;
+ padding: 0 0 5px 25px;
+}
+#jspec-report tr.even:hover + tr.body,
+#jspec-report tr.odd:hover + tr.body {
+ display: block;
+}
+#jspec-report tr td:first-child em {
+ display: block;
+ clear: both;
+ font-style: normal;
+ font-weight: normal;
+ color: #7B8D9B;
+}
+#jspec-report tr.even:hover,
+#jspec-report tr.odd:hover {
+ text-shadow: 1px 1px 1px #fff;
+ background: #F2F5F7;
+}
+#jspec-report td + td {
+ padding-right: 0;
+ width: 15px;
+}
+#jspec-report td.pass {
+ background: url(images/sprites.png) 3px -7px no-repeat;
+}
+#jspec-report td.fail {
+ background: url(images/sprites.png) 3px -158px no-repeat;
+ font-weight: bold;
+ color: #FC0D0D;
+}
+#jspec-report td.requires-implementation {
+ background: url(images/sprites.png) 3px -333px no-repeat;
+}
+#jspec-report tr.description td {
+ margin-top: 25px;
+ padding-top: 25px;
+ font-size: 12px;
+ font-weight: bold;
+ text-indent: 0;
+ color: #1a1a1a;
+}
+#jspec-report tr.description:first-child td {
+ border-top: none;
+}
+#jspec-report .assertion {
+ display: block;
+ float: left;
+ margin: 0 0 0 1px;
+ padding: 0;
+ width: 1px;
+ height: 5px;
+ background: #7B8D9B;
+}
+#jspec-report .assertion.failed {
+ background: red;
+}
+.jspec-sandbox {
+ display: none;
+} \ No newline at end of file
diff --git a/share/www/script/jspec/jspec.jquery.js b/share/www/script/jspec/jspec.jquery.js
new file mode 100644
index 00000000..fcad7ab9
--- /dev/null
+++ b/share/www/script/jspec/jspec.jquery.js
@@ -0,0 +1,72 @@
+
+// JSpec - jQuery - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
+
+JSpec
+.requires('jQuery', 'when using jspec.jquery.js')
+.include({
+ name: 'jQuery',
+
+ // --- Initialize
+
+ init : function() {
+ jQuery.ajaxSetup({ async: false })
+ },
+
+ // --- Utilities
+
+ utilities : {
+ element: jQuery,
+ elements: jQuery,
+ sandbox : function() {
+ return jQuery('<div class="sandbox"></div>')
+ }
+ },
+
+ // --- Matchers
+
+ matchers : {
+ have_tag : "jQuery(expected, actual).length === 1",
+ have_one : "alias have_tag",
+ have_tags : "jQuery(expected, actual).length > 1",
+ have_many : "alias have_tags",
+ have_any : "alias have_tags",
+ have_child : "jQuery(actual).children(expected).length === 1",
+ have_children : "jQuery(actual).children(expected).length > 1",
+ have_text : "jQuery(actual).text() === expected",
+ have_value : "jQuery(actual).val() === expected",
+ be_enabled : "!jQuery(actual).attr('disabled')",
+ have_class : "jQuery(actual).hasClass(expected)",
+
+ be_visible : function(actual) {
+ return jQuery(actual).css('display') != 'none' &&
+ jQuery(actual).css('visibility') != 'hidden' &&
+ jQuery(actual).attr('type') != 'hidden'
+ },
+
+ be_hidden : function(actual) {
+ return !JSpec.does(actual, 'be_visible')
+ },
+
+ have_classes : function(actual) {
+ return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
+ return !JSpec.does(actual, 'have_class', arg)
+ })
+ },
+
+ have_attr : function(actual, attr, value) {
+ return value ? jQuery(actual).attr(attr) == value:
+ jQuery(actual).attr(attr)
+ },
+
+ 'be disabled selected checked' : function(attr) {
+ return 'jQuery(actual).attr("' + attr + '")'
+ },
+
+ 'have type id title alt href src sel rev name target' : function(attr) {
+ return function(actual, value) {
+ return JSpec.does(actual, 'have_attr', attr, value)
+ }
+ }
+ }
+})
+
diff --git a/share/www/script/jspec/jspec.js b/share/www/script/jspec/jspec.js
new file mode 100644
index 00000000..d6daf5ef
--- /dev/null
+++ b/share/www/script/jspec/jspec.js
@@ -0,0 +1,1756 @@
+
+// JSpec - Core - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
+
+;(function(){
+
+ JSpec = {
+ version : '3.3.2',
+ assert : true,
+ cache : {},
+ suites : [],
+ modules : [],
+ allSuites : [],
+ matchers : {},
+ stubbed : [],
+ options : {},
+ request : 'XMLHttpRequest' in this ? XMLHttpRequest : null,
+ stats : { specs: 0, assertions: 0, failures: 0, passes: 0, specsFinished: 0, suitesFinished: 0 },
+
+ /**
+ * Default context in which bodies are evaluated.
+ *
+ * Replace context simply by setting JSpec.context
+ * to your own like below:
+ *
+ * JSpec.context = { foo : 'bar' }
+ *
+ * Contexts can be changed within any body, this can be useful
+ * 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 : {
+
+ /**
+ * Return an object used for proxy assertions.
+ * This object is used to indicate that an object
+ * should be an instance of _object_, not the constructor
+ * itself.
+ *
+ * @param {function} constructor
+ * @return {hash}
+ * @api public
+ */
+
+ an_instance_of : function(constructor) {
+ return { an_instance_of : constructor }
+ },
+
+ /**
+ * Load fixture at _path_.
+ *
+ * Fixtures are resolved as:
+ *
+ * - <path>
+ * - <path>.html
+ *
+ * @param {string} path
+ * @return {string}
+ * @api public
+ */
+
+ fixture : function(path) {
+ if (JSpec.cache[path]) return JSpec.cache[path]
+ return JSpec.cache[path] =
+ JSpec.tryLoading(JSpec.options.fixturePath + '/' + path) ||
+ JSpec.tryLoading(JSpec.options.fixturePath + '/' + path + '.html')
+ }
+ },
+
+ // --- Objects
+
+ reporters : {
+
+ /**
+ * Report to server.
+ *
+ * Options:
+ * - uri specific uri to report to.
+ * - verbose weither or not to output messages
+ * - failuresOnly output failure messages only
+ *
+ * @api public
+ */
+
+ Server : function(results, options) {
+ var uri = options.uri || 'http://' + window.location.host + '/results'
+ JSpec.post(uri, {
+ stats: JSpec.stats,
+ options: options,
+ results: map(results.allSuites, function(suite) {
+ if (suite.hasSpecs())
+ return {
+ description: suite.description,
+ specs: map(suite.specs, function(spec) {
+ return {
+ description: spec.description,
+ message: !spec.passed() ? spec.failure().message : null,
+ status: spec.requiresImplementation() ? 'pending' :
+ spec.passed() ? 'pass' :
+ 'fail',
+ assertions: map(spec.assertions, function(assertion){
+ return {
+ passed: assertion.passed
+ }
+ })
+ }
+ })
+ }
+ })
+ })
+ if ('close' in main) main.close()
+ },
+
+ /**
+ * Default reporter, outputting to the DOM.
+ *
+ * Options:
+ * - reportToId id of element to output reports to, defaults to 'jspec'
+ * - failuresOnly displays only suites with failing specs
+ *
+ * @api public
+ */
+
+ DOM : function(results, options) {
+ var id = option('reportToId') || 'jspec',
+ report = document.getElementById(id),
+ failuresOnly = option('failuresOnly'),
+ classes = results.stats.failures ? 'has-failures' : ''
+ if (!report) throw 'JSpec requires the element #' + id + ' to output its reports'
+
+ function bodyContents(body) {
+ return JSpec.
+ escape(JSpec.contentsOf(body)).
+ replace(/^ */gm, function(a){ return (new Array(Math.round(a.length / 3))).join(' ') }).
+ replace(/\r\n|\r|\n/gm, '<br/>')
+ }
+
+ report.innerHTML = '<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> \
+ <span class="passes">Duration: <em>' + results.duration + '</em> ms</span> \
+ </div><table class="suites">' + map(results.allSuites, function(suite) {
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
+ if (displaySuite && suite.hasSpecs())
+ return '<tr class="description"><td colspan="2">' + escape(suite.description) + '</td></tr>' +
+ map(suite.specs, function(i, spec) {
+ return '<tr class="' + (i % 2 ? 'odd' : 'even') + '">' +
+ (spec.requiresImplementation() ?
+ '<td class="requires-implementation" colspan="2">' + escape(spec.description) + '</td>' :
+ (spec.passed() && !failuresOnly) ?
+ '<td class="pass">' + escape(spec.description)+ '</td><td>' + spec.assertionsGraph() + '</td>' :
+ !spec.passed() ?
+ '<td class="fail">' + escape(spec.description) +
+ map(spec.failures(), function(a){ return '<em>' + escape(a.message) + '</em>' }).join('') +
+ '</td><td>' + spec.assertionsGraph() + '</td>' :
+ '') +
+ '<tr class="body"><td colspan="2"><pre>' + bodyContents(spec.body) + '</pre></td></tr>'
+ }).join('') + '</tr>'
+ }).join('') + '</table></div>'
+ },
+
+ /**
+ * Terminal reporter.
+ *
+ * @api public
+ */
+
+ Terminal : function(results, options) {
+ var failuresOnly = option('failuresOnly')
+ print(color("\n Passes: ", 'bold') + color(results.stats.passes, 'green') +
+ color(" Failures: ", 'bold') + color(results.stats.failures, 'red') +
+ color(" Duration: ", 'bold') + color(results.duration, 'green') + " ms \n")
+
+ function indent(string) {
+ return string.replace(/^(.)/gm, ' $1')
+ }
+
+ each(results.allSuites, function(suite) {
+ var displaySuite = failuresOnly ? suite.ran && !suite.passed() : suite.ran
+ if (displaySuite && suite.hasSpecs()) {
+ print(color(' ' + suite.description, 'bold'))
+ each(suite.specs, function(spec){
+ var assertionsGraph = inject(spec.assertions, '', function(graph, assertion){
+ return graph + color('.', assertion.passed ? 'green' : 'red')
+ })
+ if (spec.requiresImplementation())
+ print(color(' ' + spec.description, 'blue') + assertionsGraph)
+ else if (spec.passed() && !failuresOnly)
+ print(color(' ' + spec.description, 'green') + assertionsGraph)
+ else if (!spec.passed())
+ print(color(' ' + spec.description, 'red') + assertionsGraph +
+ "\n" + indent(map(spec.failures(), function(a){ return a.message }).join("\n")) + "\n")
+ })
+ print("")
+ }
+ })
+
+ quit(results.stats.failures)
+ }
+ },
+
+ Assertion : function(matcher, actual, expected, negate) {
+ extend(this, {
+ message: '',
+ passed: false,
+ actual: actual,
+ negate: negate,
+ matcher: matcher,
+ expected: expected,
+
+ // Report assertion results
+
+ report : function() {
+ if (JSpec.assert)
+ this.passed ? JSpec.stats.passes++ : JSpec.stats.failures++
+ return this
+ },
+
+ // Run the assertion
+
+ run : function() {
+ // TODO: remove unshifting
+ expected.unshift(actual)
+ this.result = matcher.match.apply(this, expected)
+ this.passed = negate ? !this.result : this.result
+ if (!this.passed) this.message = matcher.message.call(this, actual, expected, negate, matcher.name)
+ return this
+ }
+ })
+ },
+
+ ProxyAssertion : function(object, method, times, negate) {
+ var self = this
+ var old = object[method]
+
+ // Proxy
+
+ object[method] = function(){
+ args = toArray(arguments)
+ result = old.apply(object, args)
+ self.calls.push({ args : args, result : result })
+ return result
+ }
+
+ // Times
+
+ this.times = {
+ once : 1,
+ twice : 2
+ }[times] || times || 1
+
+ extend(this, {
+ calls: [],
+ message: '',
+ defer: true,
+ passed: false,
+ negate: negate,
+ object: object,
+ method: method,
+
+ // Proxy return value
+
+ and_return : function(result) {
+ this.expectedResult = result
+ return this
+ },
+
+ // Proxy arguments passed
+
+ with_args : function() {
+ this.expectedArgs = toArray(arguments)
+ return this
+ },
+
+ // Check if any calls have failing results
+
+ anyResultsFail : function() {
+ return any(this.calls, function(call){
+ return self.expectedResult.an_instance_of ?
+ call.result.constructor != self.expectedResult.an_instance_of:
+ !equal(self.expectedResult, call.result)
+ })
+ },
+
+ // Check if any calls have passing results
+
+ anyResultsPass : function() {
+ return any(this.calls, function(call){
+ return self.expectedResult.an_instance_of ?
+ call.result.constructor == self.expectedResult.an_instance_of:
+ equal(self.expectedResult, call.result)
+ })
+ },
+
+ // Return the passing result
+
+ passingResult : function() {
+ return this.anyResultsPass().result
+ },
+
+ // Return the failing result
+
+ failingResult : function() {
+ return this.anyResultsFail().result
+ },
+
+ // Check if any arguments fail
+
+ anyArgsFail : function() {
+ return any(this.calls, function(call){
+ return any(self.expectedArgs, function(i, arg){
+ if (arg == null) return call.args[i] == null
+ return arg.an_instance_of ?
+ call.args[i].constructor != arg.an_instance_of:
+ !equal(arg, call.args[i])
+
+ })
+ })
+ },
+
+ // Check if any arguments pass
+
+ anyArgsPass : function() {
+ return any(this.calls, function(call){
+ return any(self.expectedArgs, function(i, arg){
+ return arg.an_instance_of ?
+ call.args[i].constructor == arg.an_instance_of:
+ equal(arg, call.args[i])
+
+ })
+ })
+ },
+
+ // Return the passing args
+
+ passingArgs : function() {
+ return this.anyArgsPass().args
+ },
+
+ // Return the failing args
+
+ failingArgs : function() {
+ return this.anyArgsFail().args
+ },
+
+ // Report assertion results
+
+ report : function() {
+ if (JSpec.assert)
+ this.passed ? ++JSpec.stats.passes : ++JSpec.stats.failures
+ return this
+ },
+
+ // Run the assertion
+
+ run : function() {
+ var methodString = 'expected ' + object.toString() + '.' + method + '()' + (negate ? ' not' : '' )
+
+ function times(n) {
+ return n > 2 ? n + ' times' : { 1: 'once', 2: 'twice' }[n]
+ }
+
+ if (this.expectedResult != null && (negate ? this.anyResultsPass() : this.anyResultsFail()))
+ this.message = methodString + ' to return ' + puts(this.expectedResult) +
+ ' but ' + (negate ? 'it did' : 'got ' + puts(this.failingResult()))
+
+ if (this.expectedArgs && (negate ? !this.expectedResult && this.anyArgsPass() : this.anyArgsFail()))
+ this.message = methodString + ' to be called with ' + puts.apply(this, this.expectedArgs) +
+ ' but was' + (negate ? '' : ' called with ' + puts.apply(this, this.failingArgs()))
+
+ if (negate ? !this.expectedResult && !this.expectedArgs && this.calls.length >= this.times : this.calls.length != this.times)
+ this.message = methodString + ' to be called ' + times(this.times) +
+ ', but ' + (this.calls.length == 0 ? ' was not called' : ' was called ' + times(this.calls.length))
+
+ if (!this.message.length)
+ this.passed = true
+
+ return this
+ }
+ })
+ },
+
+ /**
+ * Specification Suite block object.
+ *
+ * @param {string} description
+ * @param {function} body
+ * @api private
+ */
+
+ Suite : function(description, body) {
+ var self = this
+ extend(this, {
+ body: body,
+ description: description,
+ suites: [],
+ specs: [],
+ ran: false,
+ hooks: { 'before' : [], 'after' : [], 'before_each' : [], 'after_each' : [] },
+
+ // Add a spec to the suite
+
+ addSpec : function(description, body) {
+ var spec = new JSpec.Spec(description, body)
+ this.specs.push(spec)
+ JSpec.stats.specs++ // TODO: abstract
+ spec.suite = this
+ },
+
+ // Add a hook to the suite
+
+ addHook : function(hook, body) {
+ this.hooks[hook].push(body)
+ },
+
+ // Add a nested suite
+
+ addSuite : function(description, body) {
+ var suite = new JSpec.Suite(description, body)
+ JSpec.allSuites.push(suite)
+ suite.name = suite.description
+ suite.description = this.description + ' ' + suite.description
+ this.suites.push(suite)
+ suite.suite = this
+ },
+
+ // Invoke a hook in context to this suite
+
+ hook : function(hook) {
+ if (this.suite) this.suite.hook(hook)
+ each(this.hooks[hook], function(body) {
+ JSpec.evalBody(body, "Error in hook '" + hook + "', suite '" + self.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() {
+ return !any(this.specs, function(spec){
+ return !spec.passed()
+ })
+ }
+ })
+ },
+
+ /**
+ * Specification block object.
+ *
+ * @param {string} description
+ * @param {function} body
+ * @api private
+ */
+
+ Spec : function(description, body) {
+ extend(this, {
+ body: body,
+ description: description,
+ assertions: [],
+
+ // Add passing assertion
+
+ pass : function(message) {
+ this.assertions.push({ passed: true, message: message })
+ if (JSpec.assert) ++JSpec.stats.passes
+ },
+
+ // Add failing assertion
+
+ fail : function(message) {
+ this.assertions.push({ passed: false, message: message })
+ if (JSpec.assert) ++JSpec.stats.failures
+ },
+
+ // Run deferred assertions
+
+ runDeferredAssertions : function() {
+ each(this.assertions, function(assertion){
+ if (assertion.defer) assertion.run().report(), hook('afterAssertion', assertion)
+ })
+ },
+
+ // Find first failing assertion
+
+ failure : function() {
+ return find(this.assertions, function(assertion){
+ return !assertion.passed
+ })
+ },
+
+ // Find all failing assertions
+
+ failures : function() {
+ return select(this.assertions, function(assertion){
+ return !assertion.passed
+ })
+ },
+
+ // Weither or not the spec passed
+
+ passed : function() {
+ return !this.failure()
+ },
+
+ // 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('')
+ }
+ })
+ },
+
+ Module : function(methods) {
+ extend(this, methods)
+ },
+
+ JSON : {
+
+ /**
+ * Generic sequences.
+ */
+
+ meta : {
+ '\b' : '\\b',
+ '\t' : '\\t',
+ '\n' : '\\n',
+ '\f' : '\\f',
+ '\r' : '\\r',
+ '"' : '\\"',
+ '\\' : '\\\\'
+ },
+
+ /**
+ * Escapable sequences.
+ */
+
+ escapable : /[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+
+ /**
+ * JSON encode _object_.
+ *
+ * @param {mixed} object
+ * @return {string}
+ * @api private
+ */
+
+ encode : function(object) {
+ var self = this
+ if (object == undefined || object == null) return 'null'
+ if (object === true) return 'true'
+ if (object === false) return 'false'
+ switch (typeof object) {
+ case 'number': return object
+ case 'string': return this.escapable.test(object) ?
+ '"' + object.replace(this.escapable, function (a) {
+ return typeof self.meta[a] === 'string' ? self.meta[a] :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)
+ }) + '"' :
+ '"' + object + '"'
+ case 'object':
+ if (object.constructor == Array)
+ return '[' + map(object, function(val){
+ return self.encode(val)
+ }).join(', ') + ']'
+ else if (object)
+ return '{' + map(object, function(key, val){
+ return self.encode(key) + ':' + self.encode(val)
+ }).join(', ') + '}'
+ }
+ return 'null'
+ }
+ },
+
+ // --- DSLs
+
+ DSLs : {
+ snake : {
+ expect : function(actual){
+ return JSpec.expect(actual)
+ },
+
+ describe : function(description, body) {
+ return JSpec.currentSuite.addSuite(description, body)
+ },
+
+ it : function(description, body) {
+ return JSpec.currentSuite.addSpec(description, body)
+ },
+
+ before : function(body) {
+ return JSpec.currentSuite.addHook('before', body)
+ },
+
+ after : function(body) {
+ return JSpec.currentSuite.addHook('after', body)
+ },
+
+ before_each : function(body) {
+ return JSpec.currentSuite.addHook('before_each', body)
+ },
+
+ after_each : function(body) {
+ return JSpec.currentSuite.addHook('after_each', body)
+ },
+
+ should_behave_like : function(description) {
+ return JSpec.shareBehaviorsOf(description)
+ }
+ }
+ },
+
+ // --- Methods
+
+ /**
+ * Check if _value_ is 'stop'. For use as a
+ * utility callback function.
+ *
+ * @param {mixed} value
+ * @return {bool}
+ * @api public
+ */
+
+ haveStopped : function(value) {
+ return value === 'stop'
+ },
+
+ /**
+ * Include _object_ which may be a hash or Module instance.
+ *
+ * @param {hash, Module} object
+ * @return {JSpec}
+ * @api public
+ */
+
+ include : function(object) {
+ var module = object.constructor == JSpec.Module ? object : new JSpec.Module(object)
+ this.modules.push(module)
+ if ('init' in module) module.init()
+ if ('utilities' in module) extend(this.defaultContext, module.utilities)
+ if ('matchers' in module) this.addMatchers(module.matchers)
+ if ('reporters' in module) extend(this.reporters, module.reporters)
+ if ('DSLs' in module)
+ each(module.DSLs, function(name, methods){
+ JSpec.DSLs[name] = JSpec.DSLs[name] || {}
+ extend(JSpec.DSLs[name], methods)
+ })
+ return this
+ },
+
+ /**
+ * Add a module hook _name_, which is immediately
+ * called per module with the _args_ given. An array of
+ * hook return values is returned.
+ *
+ * @param {name} string
+ * @param {...} args
+ * @return {array}
+ * @api private
+ */
+
+ hook : function(name, args) {
+ args = toArray(arguments, 1)
+ return inject(JSpec.modules, [], function(results, module){
+ if (typeof module[name] == 'function')
+ results.push(JSpec.evalHook(module, name, args))
+ })
+ },
+
+ /**
+ * Eval _module_ hook _name_ with _args_. Evaluates in context
+ * to the module itself, JSpec, and JSpec.context.
+ *
+ * @param {Module} module
+ * @param {string} name
+ * @param {array} args
+ * @return {mixed}
+ * @api private
+ */
+
+ evalHook : function(module, name, args) {
+ hook('evaluatingHookBody', module, name)
+ try { return module[name].apply(module, args) }
+ catch(e) { error('Error in hook ' + module.name + '.' + name + ': ', e) }
+ },
+
+ /**
+ * Same as hook() however accepts only one _arg_ which is
+ * considered immutable. This function passes the arg
+ * to the first module, then passes the return value of the last
+ * module called, to the following module.
+ *
+ * @param {string} name
+ * @param {mixed} arg
+ * @return {mixed}
+ * @api private
+ */
+
+ hookImmutable : function(name, arg) {
+ return inject(JSpec.modules, arg, function(result, module){
+ if (typeof module[name] == 'function')
+ return JSpec.evalHook(module, name, [result])
+ })
+ },
+
+ /**
+ * Find a suite by its description or name.
+ *
+ * @param {string} description
+ * @return {Suite}
+ * @api private
+ */
+
+ findSuite : function(description) {
+ return find(this.allSuites, function(suite){
+ return suite.name == description || suite.description == description
+ })
+ },
+
+ /**
+ * Share behaviors (specs) of the given suite with
+ * the current suite.
+ *
+ * @param {string} description
+ * @api public
+ */
+
+ shareBehaviorsOf : function(description) {
+ if (suite = this.findSuite(description)) this.copySpecs(suite, this.currentSuite)
+ else throw 'failed to share behaviors. ' + puts(description) + ' is not a valid Suite name'
+ },
+
+ /**
+ * Copy specs from one suite to another.
+ *
+ * @param {Suite} fromSuite
+ * @param {Suite} toSuite
+ * @api public
+ */
+
+ copySpecs : function(fromSuite, toSuite) {
+ each(fromSuite.specs, function(spec){
+ var newSpec = new Object();
+ extend(newSpec, spec);
+ newSpec.assertions = [];
+ toSuite.specs.push(newSpec);
+ })
+ },
+
+ /**
+ * Convert arguments to an array.
+ *
+ * @param {object} arguments
+ * @param {int} offset
+ * @return {array}
+ * @api public
+ */
+
+ toArray : function(arguments, offset) {
+ return Array.prototype.slice.call(arguments, offset || 0)
+ },
+
+ /**
+ * 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 ' + puts(actual) + ' to ' +
+ (negate ? 'not ' : '') +
+ name.replace(/_/g, ' ') +
+ ' ' + (expected.length > 1 ?
+ puts.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) {
+ return (value = query(key)) !== null ? value :
+ JSpec.options[key] || null
+ },
+
+ /**
+ * Check if object _a_, is equal to object _b_.
+ *
+ * @param {object} a
+ * @param {object} b
+ * @return {bool}
+ * @api private
+ */
+
+ equal: function(a, b) {
+ if (typeof a != typeof b) return
+ if (a === b) return true
+ if (a instanceof RegExp)
+ return a.toString() === b.toString()
+ if (a instanceof Date)
+ return Number(a) === Number(b)
+ if (typeof a != 'object') return
+ if (a.length !== undefined)
+ if (a.length !== b.length) return
+ else
+ for (var i = 0, len = a.length; i < len; ++i)
+ if (!equal(a[i], b[i]))
+ return
+ for (var key in a)
+ if (!equal(a[key], b[key]))
+ return
+ return true
+ },
+
+ /**
+ * Return last element of an array.
+ *
+ * @param {array} array
+ * @return {object}
+ * @api public
+ */
+
+ last : function(array) {
+ return array[array.length - 1]
+ },
+
+ /**
+ * Convert object(s) to a print-friend string.
+ *
+ * @param {...} object
+ * @return {string}
+ * @api public
+ */
+
+ puts : function(object) {
+ if (arguments.length > 1)
+ return map(toArray(arguments), function(arg){
+ return puts(arg)
+ }).join(', ')
+ if (object === undefined) return 'undefined'
+ if (object === null) return 'null'
+ if (object === true) return 'true'
+ if (object === false) return 'false'
+ if (object.an_instance_of) return 'an instance of ' + object.an_instance_of.name
+ if (object.jquery && object.selector.length > 0) return 'selector ' + puts(object.selector)
+ if (object.jquery) return object.get(0).outerHTML
+ if (object.nodeName) return object.outerHTML
+ switch (object.constructor) {
+ case Function: return object.name || object
+ case String:
+ return '"' + object
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, '\\n')
+ .replace(/\t/g, '\\t')
+ + '"'
+ case Array:
+ return inject(object, '[', function(b, v){
+ return b + ', ' + puts(v)
+ }).replace('[,', '[') + ' ]'
+ case Object:
+ object.__hit__ = true
+ return inject(object, '{', function(b, k, v) {
+ if (k == '__hit__') return b
+ return b + ', ' + k + ': ' + (v && v.__hit__ ? '<circular reference>' : puts(v))
+ }).replace('{,', '{') + ' }'
+ default:
+ return object.toString()
+ }
+ },
+
+ /**
+ * Escape HTML.
+ *
+ * @param {string} html
+ * @return {string}
+ * @api public
+ */
+
+ escape : function(html) {
+ return html.toString()
+ .replace(/&/gmi, '&amp;')
+ .replace(/"/gmi, '&quot;')
+ .replace(/>/gmi, '&gt;')
+ .replace(/</gmi, '&lt;')
+ },
+
+ /**
+ * Perform an assertion without reporting.
+ *
+ * This method is primarily used for internal
+ * matchers in order retain DRYness. May be invoked
+ * like below:
+ *
+ * does('foo', 'eql', 'foo')
+ * does([1,2], 'include', 1, 2)
+ *
+ * External hooks are not run for internal assertions
+ * performed by does().
+ *
+ * @param {mixed} actual
+ * @param {string} matcher
+ * @param {...} expected
+ * @return {mixed}
+ * @api private
+ */
+
+ does : function(actual, matcher, expected) {
+ var assertion = new JSpec.Assertion(JSpec.matchers[matcher], actual, toArray(arguments, 2))
+ return assertion.run().result
+ },
+
+ /**
+ * Perform an assertion.
+ *
+ * expect(true).to('be', true)
+ * expect('foo').not_to('include', 'bar')
+ * expect([1, [2]]).to('include', 1, [2])
+ *
+ * @param {mixed} actual
+ * @return {hash}
+ * @api public
+ */
+
+ expect : function(actual) {
+ function assert(matcher, args, negate) {
+ var expected = toArray(args, 1)
+ matcher.negate = negate
+ assertion = new JSpec.Assertion(matcher, actual, expected, negate)
+ hook('beforeAssertion', assertion)
+ if (matcher.defer) assertion.run()
+ else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
+ return assertion.result
+ }
+
+ function to(matcher) {
+ return assert(matcher, arguments, false)
+ }
+
+ function not_to(matcher) {
+ return assert(matcher, arguments, true)
+ }
+
+ return {
+ to : to,
+ should : to,
+ not_to: not_to,
+ should_not : not_to
+ }
+ },
+
+ /**
+ * Strim whitespace or chars.
+ *
+ * @param {string} string
+ * @param {string} chars
+ * @return {string}
+ * @api public
+ */
+
+ strip : function(string, chars) {
+ return string.
+ replace(new RegExp('[' + (chars || '\\s') + ']*$'), '').
+ replace(new RegExp('^[' + (chars || '\\s') + ']*'), '')
+ },
+
+ /**
+ * Call an iterator callback with arguments a, or b
+ * depending on the arity of the callback.
+ *
+ * @param {function} callback
+ * @param {mixed} a
+ * @param {mixed} b
+ * @return {mixed}
+ * @api private
+ */
+
+ callIterator : function(callback, a, b) {
+ return callback.length == 1 ? callback(b) : callback(a, b)
+ },
+
+ /**
+ * 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
+ })
+ },
+
+ /**
+ * Iterate an object, invoking the given callback.
+ *
+ * @param {hash, array} object
+ * @param {function} callback
+ * @return {JSpec}
+ * @api public
+ */
+
+ each : function(object, callback) {
+ if (object.constructor == Array)
+ for (var i = 0, len = object.length; i < len; ++i)
+ callIterator(callback, i, object[i])
+ else
+ for (var key in object)
+ if (object.hasOwnProperty(key))
+ callIterator(callback, key, object[key])
+ },
+
+ /**
+ * Iterate with memo.
+ *
+ * @param {hash, array} object
+ * @param {object} memo
+ * @param {function} callback
+ * @return {object}
+ * @api public
+ */
+
+ inject : function(object, memo, callback) {
+ each(object, function(key, value){
+ memo = (callback.length == 2 ?
+ callback(memo, value):
+ callback(memo, key, value)) ||
+ memo
+ })
+ return memo
+ },
+
+ /**
+ * Destub _object_'s _method_. When no _method_ is passed
+ * all stubbed methods are destubbed. When no arguments
+ * are passed every object found in JSpec.stubbed will be
+ * destubbed.
+ *
+ * @param {mixed} object
+ * @param {string} method
+ * @api public
+ */
+
+ destub : function(object, method) {
+ if (method) {
+ if (object['__prototype__' + method])
+ delete object[method]
+ else
+ object[method] = object['__original__' + method]
+ delete object['__prototype__' + method]
+ delete object['__original____' + method]
+ }
+ else if (object) {
+ for (var key in object)
+ if (captures = key.match(/^(?:__prototype__|__original__)(.*)/))
+ destub(object, captures[1])
+ }
+ else
+ while (JSpec.stubbed.length)
+ destub(JSpec.stubbed.shift())
+ },
+
+ /**
+ * Stub _object_'s _method_.
+ *
+ * stub(foo, 'toString').and_return('bar')
+ *
+ * @param {mixed} object
+ * @param {string} method
+ * @return {hash}
+ * @api public
+ */
+
+ stub : function(object, method) {
+ hook('stubbing', object, method)
+ JSpec.stubbed.push(object)
+ var type = object.hasOwnProperty(method) ? '__original__' : '__prototype__'
+ object[type + method] = object[method]
+ object[method] = function(){}
+ return {
+ and_return : function(value) {
+ if (typeof value == 'function') object[method] = value
+ else object[method] = function(){ return value }
+ }
+ }
+ },
+
+ /**
+ * Map callback return values.
+ *
+ * @param {hash, array} object
+ * @param {function} callback
+ * @return {array}
+ * @api public
+ */
+
+ map : function(object, callback) {
+ return inject(object, [], function(memo, key, value){
+ memo.push(callIterator(callback, key, value))
+ })
+ },
+
+ /**
+ * Returns the first matching expression or null.
+ *
+ * @param {hash, array} object
+ * @param {function} callback
+ * @return {mixed}
+ * @api public
+ */
+
+ any : function(object, callback) {
+ return inject(object, null, function(state, key, value){
+ if (state == undefined)
+ return callIterator(callback, key, value) ? value : state
+ })
+ },
+
+ /**
+ * Returns an array of values collected when the callback
+ * given evaluates to true.
+ *
+ * @param {hash, array} object
+ * @return {function} callback
+ * @return {array}
+ * @api public
+ */
+
+ select : function(object, callback) {
+ return inject(object, [], function(selected, key, value){
+ if (callIterator(callback, key, value))
+ selected.push(value)
+ })
+ },
+
+ /**
+ * Define matchers.
+ *
+ * @param {hash} matchers
+ * @api public
+ */
+
+ addMatchers : function(matchers) {
+ each(matchers, function(name, body){
+ JSpec.addMatcher(name, body)
+ })
+ },
+
+ /**
+ * Define a matcher.
+ *
+ * @param {string} name
+ * @param {hash, function, string} body
+ * @api public
+ */
+
+ addMatcher : function(name, body) {
+ hook('addingMatcher', name, body)
+ if (name.indexOf(' ') != -1) {
+ var matchers = name.split(/\s+/)
+ var prefix = matchers.shift()
+ each(matchers, function(name) {
+ JSpec.addMatcher(prefix + '_' + name, body(name))
+ })
+ }
+ this.matchers[name] = this.normalizeMatcherMessage(this.normalizeMatcherBody(body))
+ this.matchers[name].name = name
+ },
+
+ /**
+ * Add a root suite to JSpec.
+ *
+ * @param {string} description
+ * @param {body} function
+ * @api public
+ */
+
+ describe : function(description, body) {
+ var suite = new JSpec.Suite(description, body)
+ hook('addingSuite', suite)
+ this.allSuites.push(suite)
+ this.suites.push(suite)
+ },
+
+ /**
+ * Return the contents of a function body.
+ *
+ * @param {function} body
+ * @return {string}
+ * @api public
+ */
+
+ contentsOf : function(body) {
+ return body.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1]
+ },
+
+ /**
+ * Evaluate a JSpec capture body.
+ *
+ * @param {function} body
+ * @param {string} errorMessage (optional)
+ * @return {Type}
+ * @api private
+ */
+
+ evalBody : function(body, errorMessage) {
+ var dsl = this.DSL || this.DSLs.snake
+ var matchers = this.matchers
+ var context = this.context || this.defaultContext
+ var contents = this.contentsOf(body)
+ hook('evaluatingBody', dsl, matchers, context, contents)
+ try { with (dsl){ with (context) { with (matchers) { eval(contents) }}} }
+ catch(e) { error(errorMessage, e) }
+ },
+
+ /**
+ * Pre-process a string of JSpec.
+ *
+ * @param {string} input
+ * @return {string}
+ * @api private
+ */
+
+ preprocess : function(input) {
+ if (typeof input != 'string') return
+ input = hookImmutable('preprocessing', input)
+ return input.
+ replace(/\t/g, ' ').
+ replace(/\r\n|\n|\r/g, '\n').
+ split('__END__')[0].
+ replace(/([\w\.]+)\.(stub|destub)\((.*?)\)$/gm, '$2($1, $3)').
+ replace(/describe\s+(.*?)$/gm, 'describe($1, function(){').
+ replace(/^\s+it\s+(.*?)$/gm, ' it($1, function(){').
+ replace(/^ *(before_each|after_each|before|after)(?= |\n|$)/gm, 'JSpec.currentSuite.addHook("$1", function(){').
+ replace(/^\s*end(?=\s|$)/gm, '});').
+ replace(/-\{/g, 'function(){').
+ replace(/(\d+)\.\.(\d+)/g, function(_, a, b){ return range(a, b) }).
+ replace(/\.should([_\.]not)?[_\.](\w+)(?: |;|$)(.*)$/gm, '.should$1_$2($3)').
+ replace(/([\/\s]*)(.+?)\.(should(?:[_\.]not)?)[_\.](\w+)\((.*)\)\s*;?$/gm, '$1 expect($2).$3($4, $5)').
+ replace(/, \)/g, ')').
+ replace(/should\.not/g, 'should_not')
+ },
+
+ /**
+ * Create a range string which can be evaluated to a native array.
+ *
+ * @param {int} start
+ * @param {int} end
+ * @return {string}
+ * @api public
+ */
+
+ range : function(start, end) {
+ var current = parseInt(start), end = parseInt(end), values = [current]
+ if (end > current) while (++current <= end) values.push(current)
+ else while (--current >= end) values.push(current)
+ return '[' + values + ']'
+ },
+
+ /**
+ * Report on the results.
+ *
+ * @api public
+ */
+
+ report : function() {
+ this.duration = Number(new Date) - this.start
+ hook('reporting', JSpec.options)
+ new (JSpec.options.reporter || JSpec.reporters.DOM)(JSpec, JSpec.options)
+ },
+
+ /**
+ * Run the spec suites. Options are merged
+ * with JSpec options when present.
+ *
+ * @param {hash} options
+ * @return {JSpec}
+ * @api public
+ */
+
+ run : function(options) {
+ if (any(hook('running'), haveStopped)) return this
+ if (options) extend(this.options, options)
+ this.start = Number(new Date)
+ each(this.suites, function(suite) { JSpec.runSuite(suite) })
+ return this
+ },
+
+ /**
+ * Run a suite.
+ *
+ * @param {Suite} suite
+ * @api public
+ */
+
+ runSuite : function(suite) {
+ this.currentSuite = suite
+ this.evalBody(suite.body)
+ suite.ran = true
+ hook('beforeSuite', suite), suite.hook('before')
+ each(suite.specs, function(spec) {
+ hook('beforeSpec', spec)
+ suite.hook('before_each')
+ JSpec.runSpec(spec)
+ hook('afterSpec', spec)
+ suite.hook('after_each')
+ })
+ if (suite.hasSuites()) {
+ each(suite.suites, function(suite) {
+ JSpec.runSuite(suite)
+ })
+ }
+ hook('afterSuite', suite), suite.hook('after')
+ this.stats.suitesFinished++
+ },
+
+ /**
+ * Report a failure for the current spec.
+ *
+ * @param {string} message
+ * @api public
+ */
+
+ fail : function(message) {
+ JSpec.currentSpec.fail(message)
+ },
+
+ /**
+ * Report a passing assertion for the current spec.
+ *
+ * @param {string} message
+ * @api public
+ */
+
+ pass : function(message) {
+ JSpec.currentSpec.pass(message)
+ },
+
+ /**
+ * Run a spec.
+ *
+ * @param {Spec} spec
+ * @api public
+ */
+
+ runSpec : function(spec) {
+ this.currentSpec = spec
+ try { this.evalBody(spec.body) }
+ catch (e) { fail(e) }
+ spec.runDeferredAssertions()
+ destub()
+ this.stats.specsFinished++
+ this.stats.assertions += spec.assertions.length
+ },
+
+ /**
+ * Require a dependency, with optional message.
+ *
+ * @param {string} dependency
+ * @param {string} message (optional)
+ * @return {JSpec}
+ * @api public
+ */
+
+ requires : function(dependency, message) {
+ hook('requiring', dependency, message)
+ try { eval(dependency) }
+ catch (e) { throw 'JSpec depends on ' + dependency + ' ' + message }
+ return this
+ },
+
+ /**
+ * Query against the current query strings keys
+ * or the queryString specified.
+ *
+ * @param {string} key
+ * @param {string} queryString
+ * @return {string, null}
+ * @api private
+ */
+
+ query : function(key, queryString) {
+ var 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
+ })
+ },
+
+ /**
+ * Throw a JSpec related error.
+ *
+ * @param {string} message
+ * @param {Exception} e
+ * @api public
+ */
+
+ error : function(message, e) {
+ throw (message ? message : '') + e.toString() +
+ (e.line ? ' near line ' + e.line : '')
+ },
+
+ /**
+ * Ad-hoc POST request for JSpec server usage.
+ *
+ * @param {string} uri
+ * @param {string} data
+ * @api private
+ */
+
+ post : function(uri, data) {
+ if (any(hook('posting', uri, data), haveStopped)) return
+ var request = this.xhr()
+ request.open('POST', uri, false)
+ request.setRequestHeader('Content-Type', 'application/json')
+ request.send(JSpec.JSON.encode(data))
+ },
+
+ /**
+ * Instantiate an XMLHttpRequest.
+ *
+ * Here we utilize IE's lame ActiveXObjects first which
+ * allow IE access serve files via the file: protocol, otherwise
+ * we then default to XMLHttpRequest.
+ *
+ * @return {XMLHttpRequest, ActiveXObject}
+ * @api private
+ */
+
+ xhr : function() {
+ return this.ieXhr() || new JSpec.request
+ },
+
+ /**
+ * Return Microsoft piece of crap ActiveXObject.
+ *
+ * @return {ActiveXObject}
+ * @api public
+ */
+
+ ieXhr : function() {
+ function object(str) {
+ try { return new ActiveXObject(str) } catch(e) {}
+ }
+ return object('Msxml2.XMLHTTP.6.0') ||
+ object('Msxml2.XMLHTTP.3.0') ||
+ object('Msxml2.XMLHTTP') ||
+ object('Microsoft.XMLHTTP')
+ },
+
+ /**
+ * Check for HTTP request support.
+ *
+ * @return {bool}
+ * @api private
+ */
+
+ hasXhr : function() {
+ return JSpec.request || 'ActiveXObject' in main
+ },
+
+ /**
+ * Try loading _file_ returning the contents
+ * string or null. Chain to locate / read a file.
+ *
+ * @param {string} file
+ * @return {string}
+ * @api public
+ */
+
+ tryLoading : function(file) {
+ try { return JSpec.load(file) } catch (e) {}
+ },
+
+ /**
+ * Load a _file_'s contents.
+ *
+ * @param {string} file
+ * @param {function} callback
+ * @return {string}
+ * @api public
+ */
+
+ load : function(file, callback) {
+ if (any(hook('loading', file), haveStopped)) return
+ if ('readFile' in main)
+ return readFile(file)
+ else if (this.hasXhr()) {
+ var request = this.xhr()
+ request.open('GET', file, false)
+ request.send(null)
+ if (request.readyState == 4 &&
+ (request.status == 0 ||
+ request.status.toString().charAt(0) == 2))
+ return request.responseText
+ }
+ else
+ error("failed to load `" + file + "'")
+ },
+
+ /**
+ * Load, pre-process, and evaluate a file.
+ *
+ * @param {string} file
+ * @param {JSpec}
+ * @api public
+ */
+
+ exec : function(file) {
+ if (any(hook('executing', file), haveStopped)) return this
+ eval('with (JSpec){' + this.preprocess(this.load(file)) + '}')
+ return this
+ }
+ }
+
+ // --- Node.js support
+
+ if (typeof GLOBAL === 'object' && typeof exports === 'object')
+ quit = process.exit,
+ print = require('sys').puts,
+ readFile = require('fs').readFileSync
+
+ // --- Utility functions
+
+ var main = this,
+ find = JSpec.any,
+ utils = 'haveStopped stub hookImmutable hook destub map any last pass fail range each option inject select \
+ error escape extend puts query strip color does addMatchers callIterator toArray equal'.split(/\s+/)
+ while (utils.length) eval('var ' + utils[0] + ' = JSpec.' + utils.shift())
+ if (!main.setTimeout) main.setTimeout = function(callback){ callback() }
+
+ // --- Matchers
+
+ addMatchers({
+ equal : "===",
+ eql : "equal(actual, expected)",
+ 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_true : "actual == true",
+ be_false : "actual == false",
+ be_undefined : "typeof actual == 'undefined'",
+ be_type : "typeof actual == expected",
+ match : "typeof actual == 'string' ? actual.match(expected) : false",
+ 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)",
+
+ receive : { defer : true, match : function(actual, method, times) {
+ proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
+ JSpec.currentSpec.assertions.push(proxy)
+ return proxy
+ }},
+
+ be_empty : function(actual) {
+ if (actual.constructor == Object && actual.length == undefined)
+ for (var key in actual)
+ return false;
+ return !actual.length
+ },
+
+ include : function(actual) {
+ for (state = true, i = 1; i < arguments.length; i++) {
+ arg = arguments[i]
+ switch (actual.constructor) {
+ case String:
+ case Number:
+ case RegExp:
+ case Function:
+ state = actual.toString().indexOf(arg) !== -1
+ break
+
+ case Object:
+ state = arg in actual
+ break
+
+ case Array:
+ state = any(actual, function(value){ return equal(value, arg) })
+ break
+ }
+ if (!state) return false
+ }
+ return true
+ },
+
+ throw_error : { match : function(actual, expected, message) {
+ try { actual() }
+ catch (e) {
+ this.e = e
+ var assert = function(arg) {
+ switch (arg.constructor) {
+ case RegExp : return arg.test(e.message || e.toString())
+ case String : return arg == (e.message || e.toString())
+ case Function : return e instanceof arg || e.name == arg.name
+ }
+ }
+ return message ? assert(expected) && assert(message) :
+ expected ? assert(expected) :
+ true
+ }
+ }, message : function(actual, expected, negate) {
+ // TODO: refactor when actual is not in expected [0]
+ var message_for = function(i) {
+ if (expected[i] == undefined) return 'exception'
+ switch (expected[i].constructor) {
+ case RegExp : return 'exception matching ' + puts(expected[i])
+ case String : return 'exception of ' + puts(expected[i])
+ case Function : return expected[i].name || 'Error'
+ }
+ }
+ exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
+ return 'expected ' + exception + (negate ? ' not ' : '' ) +
+ ' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
+ }},
+
+ have : function(actual, length, property) {
+ return actual[property].length == length
+ },
+
+ have_at_least : function(actual, length, property) {
+ return actual[property].length >= length
+ },
+
+ have_at_most :function(actual, length, property) {
+ return actual[property].length <= length
+ },
+
+ have_within : function(actual, range, property) {
+ length = actual[property].length
+ return length >= range.shift() && length <= range.pop()
+ },
+
+ have_prop : function(actual, property, value) {
+ return actual[property] == null ||
+ actual[property] instanceof Function ? false:
+ value == null ? true:
+ does(actual[property], 'eql', value)
+ },
+
+ have_property : function(actual, property, value) {
+ return actual[property] == null ||
+ actual[property] instanceof Function ? false:
+ value == null ? true:
+ value === actual[property]
+ }
+ })
+
+})()
diff --git a/share/www/script/jspec/jspec.xhr.js b/share/www/script/jspec/jspec.xhr.js
new file mode 100644
index 00000000..61648795
--- /dev/null
+++ b/share/www/script/jspec/jspec.xhr.js
@@ -0,0 +1,195 @@
+
+// JSpec - XHR - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
+
+(function(){
+
+ var lastRequest
+
+ // --- Original XMLHttpRequest
+
+ var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
+ XMLHttpRequest :
+ function(){}
+ var OriginalActiveXObject = 'ActiveXObject' in this ?
+ ActiveXObject :
+ undefined
+
+ // --- MockXMLHttpRequest
+
+ var MockXMLHttpRequest = function() {
+ this.requestHeaders = {}
+ }
+
+ MockXMLHttpRequest.prototype = {
+ status: 0,
+ async: true,
+ readyState: 0,
+ responseText: '',
+ abort: function(){},
+ onreadystatechange: function(){},
+
+ /**
+ * Return response headers hash.
+ */
+
+ getAllResponseHeaders : function(){
+ return this.responseHeaders
+ },
+
+ /**
+ * Return case-insensitive value for header _name_.
+ */
+
+ getResponseHeader : function(name) {
+ return this.responseHeaders[name.toLowerCase()]
+ },
+
+ /**
+ * Set case-insensitive _value_ for header _name_.
+ */
+
+ setRequestHeader : function(name, value) {
+ this.requestHeaders[name.toLowerCase()] = value
+ },
+
+ /**
+ * Open mock request.
+ */
+
+ open : function(method, url, async, user, password) {
+ this.user = user
+ this.password = password
+ this.url = url
+ this.readyState = 1
+ this.method = method.toUpperCase()
+ if (async != undefined) this.async = async
+ if (this.async) this.onreadystatechange()
+ },
+
+ /**
+ * Send request _data_.
+ */
+
+ send : function(data) {
+ var self = this
+ this.data = data
+ this.readyState = 4
+ if (this.method == 'HEAD') this.responseText = null
+ this.responseHeaders['content-length'] = (this.responseText || '').length
+ if(this.async) this.onreadystatechange()
+ lastRequest = function(){
+ return self
+ }
+ }
+ }
+
+ // --- Response status codes
+
+ JSpec.statusCodes = {
+ 100: 'Continue',
+ 101: 'Switching Protocols',
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 300: 'Multiple Choice',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 307: 'Temporary Redirect',
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Failed',
+ 413: 'Request Entity Too Large',
+ 414: 'Request-URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Requested Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 422: 'Unprocessable Entity',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported'
+ }
+
+ /**
+ * Mock XMLHttpRequest requests.
+ *
+ * mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' })
+ *
+ * @return {hash}
+ * @api public
+ */
+
+ function mockRequest() {
+ return { and_return : function(body, type, status, headers) {
+ XMLHttpRequest = MockXMLHttpRequest
+ ActiveXObject = false
+ status = status || 200
+ headers = headers || {}
+ headers['content-type'] = type
+ JSpec.extend(XMLHttpRequest.prototype, {
+ responseText: body,
+ responseHeaders: headers,
+ status: status,
+ statusText: JSpec.statusCodes[status]
+ })
+ }}
+ }
+
+ /**
+ * Unmock XMLHttpRequest requests.
+ *
+ * @api public
+ */
+
+ function unmockRequest() {
+ XMLHttpRequest = OriginalXMLHttpRequest
+ ActiveXObject = OriginalActiveXObject
+ }
+
+ JSpec.include({
+ name: 'Mock XHR',
+
+ // --- Utilities
+
+ utilities : {
+ mockRequest: mockRequest,
+ unmockRequest: unmockRequest
+ },
+
+ // --- Hooks
+
+ afterSpec : function() {
+ unmockRequest()
+ },
+
+ // --- DSLs
+
+ DSLs : {
+ snake : {
+ mock_request: mockRequest,
+ unmock_request: unmockRequest,
+ last_request: function(){ return lastRequest() }
+ }
+ }
+
+ })
+})() \ No newline at end of file
diff --git a/share/www/spec/couch_js_class_methods_spec.js b/share/www/spec/couch_js_class_methods_spec.js
new file mode 100644
index 00000000..3c2ec3be
--- /dev/null
+++ b/share/www/spec/couch_js_class_methods_spec.js
@@ -0,0 +1,389 @@
+// Specs for couch.js lines 313-470
+
+describe 'CouchDB class'
+ describe 'session stuff'
+ before
+ useTestUserDb();
+ end
+
+ after
+ useOldUserDb();
+ end
+
+ before_each
+ userDoc = users_db.save(CouchDB.prepareUserDoc({name: "Gaius Baltar", roles: ["president"]}, "secretpass"));
+ end
+
+ after_each
+ users_db.deleteDoc({_id : userDoc.id, _rev : userDoc.rev})
+ end
+
+ describe '.login'
+ it 'should return ok true'
+ CouchDB.login("Gaius Baltar", "secretpass").ok.should.be_true
+ end
+
+ it 'should return the name of the logged in user'
+ CouchDB.login("Gaius Baltar", "secretpass").name.should.eql "Gaius Baltar"
+ end
+
+ it 'should return the roles of the logged in user'
+ CouchDB.login("Gaius Baltar", "secretpass").roles.should.eql ["president"]
+ end
+
+ it 'should post _session'
+ CouchDB.should.receive("request", "once").with_args("POST", "/_session")
+ CouchDB.login("Gaius Baltar", "secretpass");
+ end
+
+ it 'should create a session'
+ CouchDB.login("Gaius Baltar", "secretpass");
+ CouchDB.session().userCtx.name.should.eql "Gaius Baltar"
+ end
+ end
+
+ describe '.logout'
+ before_each
+ CouchDB.login("Gaius Baltar", "secretpass");
+ end
+
+ it 'should return ok true'
+ CouchDB.logout().ok.should.be_true
+ end
+
+ it 'should delete _session'
+ CouchDB.should.receive("request", "once").with_args("DELETE", "/_session")
+ CouchDB.logout();
+ end
+
+ it 'should result in an invalid session'
+ CouchDB.logout();
+ CouchDB.session().name.should.be_null
+ end
+ end
+
+ describe '.session'
+ before_each
+ CouchDB.login("Gaius Baltar", "secretpass");
+ end
+
+ it 'should return ok true'
+ CouchDB.session().ok.should.be_true
+ end
+
+ it 'should return the users name'
+ CouchDB.session().userCtx.name.should.eql "Gaius Baltar"
+ end
+
+ it 'should return the users roles'
+ CouchDB.session().userCtx.roles.should.eql ["president"]
+ end
+
+ it 'should return the name of the authentication db'
+ CouchDB.session().info.authentication_db.should.eql "spec_users_db"
+ end
+
+ it 'should return the active authentication handler'
+ CouchDB.session().info.authenticated.should.eql "cookie"
+ end
+ end
+ end
+
+ describe 'db stuff'
+ before_each
+ db = new CouchDB("spec_db", {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ end
+
+ after_each
+ db.deleteDb();
+ end
+
+ describe '.prepareUserDoc'
+ before_each
+ userDoc = CouchDB.prepareUserDoc({name: "Laura Roslin"}, "secretpass");
+ end
+
+ it 'should return the users name'
+ userDoc.name.should.eql "Laura Roslin"
+ end
+
+ it 'should prefix the id with the CouchDB user_prefix'
+ userDoc._id.should.eql "org.couchdb.user:Laura Roslin"
+ end
+
+ it 'should return the users roles'
+ var userDocWithRoles = CouchDB.prepareUserDoc({name: "William Adama", roles: ["admiral", "commander"]}, "secretpass")
+ userDocWithRoles.roles.should.eql ["admiral", "commander"]
+ end
+
+ it 'should return the hashed password'
+ userDoc.password_sha.length.should.be_at_least 30
+ userDoc.password_sha.should.be_a String
+ end
+ end
+
+ describe '.allDbs'
+ it 'should get _all_dbs'
+ CouchDB.should.receive("request", "once").with_args("GET", "/_all_dbs");
+ CouchDB.allDbs();
+ end
+
+ it 'should return an array that includes a created database'
+ temp_db = new CouchDB("temp_spec_db", {"X-Couch-Full-Commit":"false"});
+ temp_db.createDb();
+ CouchDB.allDbs().should.include("temp_spec_db");
+ temp_db.deleteDb();
+ end
+
+ it 'should return an array that does not include a database that does not exist'
+ CouchDB.allDbs().should.not.include("not_existing_temp_spec_db");
+ end
+ end
+
+ describe '.allDesignDocs'
+ it 'should return the total number of documents'
+ CouchDB.allDesignDocs().spec_db.total_rows.should.eql 0
+ db.save({'type':'battlestar', 'name':'galactica'});
+ CouchDB.allDesignDocs().spec_db.total_rows.should.eql 1
+ end
+
+ it 'should return undefined when the db does not exist'
+ CouchDB.allDesignDocs().non_existing_db.should.be_undefined
+ end
+
+ it 'should return no documents when there are no design documents'
+ CouchDB.allDesignDocs().spec_db.rows.should.eql []
+ end
+
+ it 'should return all design documents'
+ var designDoc = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc); }"
+ }
+ },
+ "_id" : "_design/spec_db"
+ };
+ db.save(designDoc);
+
+ var allDesignDocs = CouchDB.allDesignDocs();
+ allDesignDocs.spec_db.rows[0].id.should.eql "_design/spec_db"
+ allDesignDocs.spec_db.rows[0].key.should.eql "_design/spec_db"
+ allDesignDocs.spec_db.rows[0].value.rev.length.should.be_at_least 30
+ end
+ end
+
+ describe '.getVersion'
+ it 'should get the CouchDB version'
+ CouchDB.should.receive("request", "once").with_args("GET", "/")
+ CouchDB.getVersion();
+ end
+
+ it 'should return the CouchDB version'
+ CouchDB.getVersion().should_match /^\d\d?\.\d\d?\.\d\d?.*/
+ end
+ end
+
+ describe '.replicate'
+ before_each
+ db2 = new CouchDB("spec_db_2", {"X-Couch-Full-Commit":"false"});
+ db2.createDb();
+ host = window.location.protocol + "//" + window.location.host ;
+ end
+
+ after_each
+ db2.deleteDb();
+ end
+
+ it 'should return no_changes true when there are no changes between the dbs'
+ CouchDB.replicate(host + db.uri, host + db2.uri).no_changes.should.be_true
+ end
+
+ it 'should return the session ID'
+ db.save({'type':'battlestar', 'name':'galactica'});
+ CouchDB.replicate(host + db.uri, host + db2.uri).session_id.length.should.be_at_least 30
+ end
+
+ it 'should return source_last_seq'
+ db.save({'type':'battlestar', 'name':'galactica'});
+ db.save({'type':'battlestar', 'name':'pegasus'});
+
+ CouchDB.replicate(host + db.uri, host + db2.uri).source_last_seq.should.eql 2
+ end
+
+ it 'should return the replication history'
+ db.save({'type':'battlestar', 'name':'galactica'});
+ db.save({'type':'battlestar', 'name':'pegasus'});
+
+ var result = CouchDB.replicate(host + db.uri, host + db2.uri);
+ result.history[0].docs_written.should.eql 2
+ result.history[0].start_last_seq.should.eql 0
+ end
+
+ it 'should pass through replication options'
+ db.save({'type':'battlestar', 'name':'galactica'});
+ db2.deleteDb();
+ -{CouchDB.replicate(host + db.uri, host + db2.uri)}.should.throw_error
+ var result = CouchDB.replicate(host + db.uri, host + db2.uri, {"body" : {"create_target":true}});
+
+ result.ok.should.eql true
+ result.history[0].docs_written.should.eql 1
+ db2.info().db_name.should.eql "spec_db_2"
+ end
+ end
+
+ describe '.newXhr'
+ it 'should return a XMLHTTPRequest'
+ CouchDB.newXhr().should.have_prop 'readyState'
+ CouchDB.newXhr().should.have_prop 'responseText'
+ CouchDB.newXhr().should.have_prop 'status'
+ end
+ end
+
+ describe '.request'
+ it 'should return a XMLHttpRequest'
+ var req = CouchDB.request("GET", '/');
+ req.should.include "readyState"
+ req.should.include "responseText"
+ req.should.include "statusText"
+ end
+
+ it 'should pass through the options headers'
+ var xhr = CouchDB.newXhr();
+ stub(CouchDB, 'newXhr').and_return(xhr);
+
+ xhr.should.receive("setRequestHeader", "once").with_args("X-Couch-Full-Commit", "true")
+ CouchDB.request("GET", "/", {'headers': {"X-Couch-Full-Commit":"true"}});
+ end
+
+ it 'should pass through the options body'
+ var xhr = CouchDB.newXhr();
+ stub(CouchDB, 'newXhr').and_return(xhr);
+
+ xhr.should.receive("send", "once").with_args({"body_key":"body_value"})
+ CouchDB.request("GET", "/", {'body': {"body_key":"body_value"}});
+ end
+
+ it 'should prepend the urlPrefix to the uri'
+ var oldPrefix = CouchDB.urlPrefix;
+ CouchDB.urlPrefix = "/_utils";
+
+ var xhr = CouchDB.newXhr();
+ stub(CouchDB, 'newXhr').and_return(xhr);
+
+ xhr.should.receive("open", "once").with_args("GET", "/_utils/", false)
+ CouchDB.request("GET", "/", {'headers': {"X-Couch-Full-Commit":"true"}});
+
+ CouchDB.urlPrefix = oldPrefix;
+ end
+ end
+
+ describe '.requestStats'
+ it 'should get the stats for specified module and key'
+ var stats = CouchDB.requestStats('couchdb', 'open_databases', null);
+ stats.description.should.eql 'number of open databases'
+ stats.current.should.be_a Number
+ end
+
+ it 'should add flush true to the request when there is a test argument'
+ CouchDB.should.receive("request", "once").with_args("GET", "/_stats/httpd/requests?flush=true")
+ CouchDB.requestStats('httpd', 'requests', 'test');
+ end
+
+ it 'should still work when there is a test argument'
+ var stats = CouchDB.requestStats('httpd_status_codes', '200', 'test');
+ stats.description.should.eql 'number of HTTP 200 OK responses'
+ stats.sum.should.be_a Number
+ end
+ end
+
+ describe '.newUuids'
+ after_each
+ CouchDB.uuids_cache = [];
+ end
+
+ it 'should return the specified amount of uuids'
+ var uuids = CouchDB.newUuids(45);
+ uuids.should.have_length 45
+ end
+
+ it 'should return an array with uuids'
+ var uuids = CouchDB.newUuids(1);
+ uuids[0].should.be_a String
+ uuids[0].should.have_length 32
+ end
+
+ it 'should leave the uuids_cache with 100 uuids when theres no buffer size specified'
+ CouchDB.newUuids(23);
+ CouchDB.uuids_cache.should.have_length 100
+ end
+
+ it 'should leave the uuids_cache with the specified buffer size'
+ CouchDB.newUuids(23, 150);
+ CouchDB.uuids_cache.should.have_length 150
+ end
+
+ it 'should get the uuids from the uuids_cache when there are enough uuids in there'
+ CouchDB.newUuids(10);
+ CouchDB.newUuids(25);
+ CouchDB.uuids_cache.should.have_length 75
+ end
+
+ it 'should create new uuids and add as many as specified to the uuids_cache when there are not enough uuids in the cache'
+ CouchDB.newUuids(10);
+ CouchDB.newUuids(125, 60);
+ CouchDB.uuids_cache.should.have_length 160
+ end
+ end
+
+ describe '.maybeThrowError'
+ it 'should throw an error when the request has status 404'
+ var req = CouchDB.request("GET", "/nonexisting_db");
+ -{CouchDB.maybeThrowError(req)}.should.throw_error
+ end
+
+ it 'should throw an error when the request has status 412'
+ var req = CouchDB.request("PUT", "/spec_db");
+ -{CouchDB.maybeThrowError(req)}.should.throw_error
+ end
+
+ it 'should throw an error when the request has status 405'
+ var req = CouchDB.request("DELETE", "/_utils");
+ -{CouchDB.maybeThrowError(req)}.should.throw_error
+ end
+
+ it 'should throw the responseText of the request'
+ var req = CouchDB.request("GET", "/nonexisting_db");
+ try {
+ CouchDB.maybeThrowError(req)
+ } catch(e) {
+ e.error.should.eql JSON.parse(req.responseText).error
+ e.reason.should.eql JSON.parse(req.responseText).reason
+ }
+ end
+
+ it 'should throw an unknown error when the responseText is invalid json'
+ mock_request().and_return("invalid json...", "application/json", 404, {})
+ try {
+ CouchDB.maybeThrowError(CouchDB.newXhr())
+ } catch(e) {
+ e.error.should.eql "unknown"
+ e.reason.should.eql "invalid json..."
+ }
+ end
+ end
+
+ describe '.params'
+ it 'should turn a json object into a http params string'
+ var params = CouchDB.params({"president":"laura", "cag":"lee"})
+ params.should.eql "president=laura&cag=lee"
+ end
+
+ it 'should return a blank string when the object is empty'
+ var params = CouchDB.params({})
+ params.should.eql ""
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/couch_js_instance_methods_1_spec.js b/share/www/spec/couch_js_instance_methods_1_spec.js
new file mode 100644
index 00000000..81a06179
--- /dev/null
+++ b/share/www/spec/couch_js_instance_methods_1_spec.js
@@ -0,0 +1,299 @@
+// Specs for couch.js lines 1-130
+
+describe 'CouchDB instance'
+ before_each
+ db = new CouchDB("spec_db", {"X-Couch-Full-Commit":"false"});
+ end
+
+ describe '.request'
+ before_each
+ db.createDb();
+ end
+
+ after_each
+ db.deleteDb();
+ end
+
+ it 'should return a XMLHttpRequest'
+ var req = db.request("GET", "/spec_db");
+ req.should.include "readyState"
+ req.should.include "responseText"
+ req.should.include "statusText"
+ // in Safari a XMLHttpRequest is actually a XMLHttpRequestConstructor,
+ // otherwise we could just do:
+ // req.should.be_a XMLHttpRequest
+ end
+
+ it 'should set the options the CouchDB instance has got as httpHeaders'
+ CouchDB.should.receive("request", "once").with_args("GET", "/spec_db", {headers: {"X-Couch-Full-Commit": "false"}})
+ db.request("GET", "/spec_db");
+ end
+
+ it 'should pass through the options'
+ CouchDB.should.receive("request", "once").with_args("GET", "/spec_db", {"X-Couch-Persist": "true", headers: {"X-Couch-Full-Commit": "false"}})
+ db.request("GET", "/spec_db", {"X-Couch-Persist":"true"});
+ end
+ end
+
+ describe '.createDb'
+ after_each
+ db.deleteDb();
+ end
+
+ it 'should create the db'
+ db.createDb();
+ db.last_req.status.should.eql 201
+ end
+
+ it 'should return the ok true'
+ db.createDb().should.eql {"ok" : true}
+ end
+
+ it 'should result in a created db'
+ db.createDb();
+ try{
+ db.createDb();
+ } catch(e) {
+ e.error.should.eql "file_exists"
+ }
+ end
+
+ it 'should have create a db with update sequence 0'
+ db.createDb();
+ db.info().update_seq.should.eql 0
+ end
+ end
+
+ describe '.deleteDb'
+ before_each
+ db.createDb();
+ end
+
+ it 'should delete the db'
+ db.deleteDb();
+ db.last_req.status.should.eql 200
+ end
+
+ it 'should return the responseText of the request'
+ db.deleteDb().should.eql {"ok" : true}
+ end
+
+ it 'should result in a deleted db'
+ db.deleteDb();
+ db.deleteDb();
+ db.last_req.status.should.eql 404
+ end
+ end
+
+ describe 'document methods'
+ before_each
+ doc = {"Name" : "Kara Thrace", "Callsign" : "Starbuck"};
+ db.createDb();
+ end
+
+ after_each
+ db.deleteDb();
+ end
+
+ describe '.save'
+ it 'should save the document'
+ db.save(doc);
+ db.last_req.status.should.eql 201
+ end
+
+ it 'should return ok true'
+ db.save(doc).ok.should.be_true
+ end
+
+ it 'should return ID and revision of the document'
+ var response = db.save(doc);
+ response.id.should.be_a String
+ response.id.should.have_length 32
+ response.rev.should.be_a String
+ response.rev.length.should.be_at_least 30
+ end
+
+ it 'should result in a saved document with generated ID'
+ var response = db.save(doc);
+ var saved_doc = db.open(response.id);
+ saved_doc.Name.should.eql "Kara Thrace"
+ saved_doc.Callsign.should.eql "Starbuck"
+ end
+
+ it 'should save the document with the specified ID'
+ doc._id = "123";
+ var response = db.save(doc);
+ response.id.should.eql "123"
+ end
+
+ it 'should pass through the options'
+ doc._id = "123";
+ CouchDB.should.receive("request", "once").with_args("PUT", "/spec_db/123?batch=ok")
+ db.save(doc, {"batch" : "ok"});
+ end
+ end
+
+ describe '.open'
+ before_each
+ doc._id = "123";
+ db.save(doc);
+ end
+
+ it 'should open the document'
+ db.open("123").should.eql doc
+ end
+
+ it 'should return null when there is no document with the given ID'
+ db.open("non_existing").should.be_null
+ end
+
+ it 'should pass through the options'
+ CouchDB.should.receive("request", "once").with_args("GET", "/spec_db/123?revs=true")
+ db.open("123", {"revs" : "true"});
+ end
+ end
+
+ describe '.deleteDoc'
+ before_each
+ doc._id = "123";
+ saved_doc = db.save(doc);
+ delete_response = db.deleteDoc({_id : "123", _rev : saved_doc.rev});
+ delete_last_req = db.last_req;
+ db.open("123");
+ end
+
+ it 'should send a successful request'
+ delete_last_req.status.should.eql 200
+ end
+
+ it 'should result in a deleted document'
+ db.open("123").should.be_null
+ end
+
+ it 'should return ok true, the ID and the revision of the deleted document'
+ delete_response.ok.should.be_true
+ delete_response.id.should.eql "123"
+ delete_response.rev.should.be_a String
+ delete_response.rev.length.should.be_at_least 30
+ end
+
+ it 'should mark the document as deleted'
+ var responseText = db.request("GET", "/spec_db/123").responseText;
+ JSON.parse(responseText).should.eql {"error":"not_found","reason":"deleted"}
+ end
+
+ it 'should record the revision in the deleted document'
+ var responseText = db.request("GET", "/spec_db/123?rev=" + delete_response.rev).responseText;
+ var deleted_doc = JSON.parse(responseText);
+ deleted_doc._rev.should.eql delete_response.rev
+ deleted_doc._id.should.eql delete_response.id
+ deleted_doc._deleted.should.be_true
+ end
+ end
+
+ describe '.deleteDocAttachment'
+ before_each
+ doc._id = "123";
+ doc._attachments = {
+ "friend.txt" : {
+ "content_type": "text\/plain",
+ // base64 encoded
+ "data": "TGVlIEFkYW1hIGlzIGEgZm9ybWVyIENvbG9uaWFsIEZsZWV0IFJlc2VydmUgb2ZmaWNlci4="
+ }
+ };
+ saved_doc = db.save(doc);
+ end
+
+ it 'should be executed on a document with attachment'
+ db.open("123")._attachments.should.include "friend.txt"
+ db.open("123")._attachments["friend.txt"].stub.should.be_true
+ end
+
+ describe 'after delete'
+ before_each
+ delete_response = db.deleteDocAttachment({_id : "123", _rev : saved_doc.rev}, "friend.txt");
+ db.open("123");
+ end
+
+ it 'should send a successful request'
+ db.last_req.status.should.eql 200
+ end
+
+ it 'should leave the document untouched'
+ db.open("123").Callsign.should.eql "Starbuck"
+ end
+
+ it 'should result in a deleted document attachment'
+ db.open("123").should.not.include "_attachments"
+ end
+
+ it 'should record the revision in the document whose attachment has been deleted'
+ var responseText = db.request("GET", "/spec_db/123?rev=" + delete_response.rev).responseText;
+ var deleted_doc = JSON.parse(responseText);
+ deleted_doc._rev.should.eql delete_response.rev
+ deleted_doc._id.should.eql delete_response.id
+ end
+
+ it 'should return ok true, the ID and the revision of the document whose attachment has been deleted'
+ delete_response.ok.should.be_true
+ delete_response.id.should.eql "123"
+ delete_response.should.have_property 'rev'
+ end
+ end
+ end
+
+ describe '.bulkSave'
+ before_each
+ doc = {"Name" : "Kara Thrace", "Callsign" : "Starbuck"};
+ doc2 = {"Name" : "Karl C. Agathon", "Callsign" : "Helo"};
+ doc3 = {"Name" : "Sharon Valerii", "Callsign" : "Boomer"};
+ docs = [doc, doc2, doc3];
+ end
+
+ it 'should save the documents'
+ db.bulkSave(docs);
+ db.last_req.status.should.eql 201
+ end
+
+ it 'should return ID and revision of the documents'
+ var response = db.bulkSave(docs);
+ response[0].id.should.be_a String
+ response[0].id.should.have_length 32
+ response[0].rev.should.be_a String
+ response[0].rev.length.should.be_at_least 30
+ response[1].id.should.be_a String
+ response[1].id.should.have_length 32
+ response[1].rev.should.be_a String
+ response[1].rev.length.should.be_at_least 30
+ response[2].id.should.be_a String
+ response[2].id.should.have_length 32
+ response[2].rev.should.be_a String
+ response[2].rev.length.should.be_at_least 30
+ end
+
+ it 'should result in saved documents'
+ var response = db.bulkSave(docs);
+ db.open(response[0].id).Name.should.eql "Kara Thrace"
+ db.open(response[1].id).Name.should.eql "Karl C. Agathon"
+ db.open(response[2].id).Name.should.eql "Sharon Valerii"
+ end
+
+ it 'should save the document with specified IDs'
+ doc._id = "123";
+ doc2._id = "456";
+ docs = [doc, doc2, doc3];
+ var response = db.bulkSave(docs);
+ response[0].id.should.eql "123"
+ response[1].id.should.eql "456"
+ response[2].id.should.have_length 32
+ end
+
+ it 'should pass through the options'
+ doc._id = "123";
+ docs = [doc];
+ CouchDB.should.receive("request", "once").with_args("POST", "/spec_db/_bulk_docs", {body: '{"docs":[{"Name":"Kara Thrace","Callsign":"Starbuck","_id":"123"}],"batch":"ok"}'})
+ db.bulkSave(docs, {"batch" : "ok"});
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/couch_js_instance_methods_2_spec.js b/share/www/spec/couch_js_instance_methods_2_spec.js
new file mode 100644
index 00000000..4df0c9a6
--- /dev/null
+++ b/share/www/spec/couch_js_instance_methods_2_spec.js
@@ -0,0 +1,234 @@
+// Specs for couch.js lines 132-199
+
+describe 'CouchDB instance'
+ before_each
+ db = new CouchDB("spec_db", {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ end
+
+ after_each
+ db.deleteDb();
+ end
+
+ describe '.ensureFullCommit'
+ it 'should return ok true'
+ db.ensureFullCommit().ok.should.be_true
+ end
+
+ it 'should return the instance start time'
+ db.ensureFullCommit().instance_start_time.should.have_length 16
+ end
+
+ it 'should post _ensure_full_commit to the db'
+ db.should.receive("request", "once").with_args("POST", "/spec_db/_ensure_full_commit")
+ db.ensureFullCommit();
+ end
+ end
+
+ describe '.query'
+ before_each
+ db.save({"Name" : "Cally Tyrol", "job" : "deckhand", "_id" : "789"});
+ db.save({"Name" : "Felix Gaeta", "job" : "officer", "_id" : "123"});
+ db.save({"Name" : "Samuel T. Anders", "job" : "pilot", "_id" : "456"});
+ map_function = "function(doc) { emit(doc._id, 1); }";
+ reduce_function = "function(key, values, rereduce) { return sum(values); }";
+ end
+
+ it 'should apply the map function'
+ var result = db.query(map_function);
+
+ result.rows.should.have_length 3
+ result.rows[0].id.should.eql "123"
+ result.rows[0].key.should.eql "123"
+ result.rows[0].value.should.eql 1
+ result.rows[1].id.should.eql "456"
+ result.rows[1].key.should.eql "456"
+ result.rows[1].value.should.eql 1
+ result.rows[2].id.should.eql "789"
+ result.rows[2].key.should.eql "789"
+ result.rows[2].value.should.eql 1
+ end
+
+ it 'should apply the reduce function'
+ var result = db.query(map_function, reduce_function);
+
+ result.rows.should.have_length 1
+ result.rows[0].key.should.be_null
+ result.rows[0].value.should_eql 3
+ end
+
+ it 'should pass through the options'
+ var result = db.query(map_function, null, {"startkey":"456"});
+
+ result.rows.should.have_length 2
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.should.eql 1
+ result.rows[1].id.should.eql "789"
+ result.rows[1].key.should.eql "789"
+ result.rows[1].value.should.eql 1
+ end
+
+ it 'should pass through the keys'
+ var result = db.query(map_function, null, {}, ["456", "123"]);
+
+ result.rows.should.have_length 2
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.should.eql 1
+ result.rows[1].id.should.eql "123"
+ result.rows[1].key.should.eql "123"
+ result.rows[1].value.should.eql 1
+ end
+
+ it 'should pass through the options and the keys'
+ var result = db.query(map_function, null, {"include_docs":"true"}, ["456"]);
+
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.should.eql 1
+ result.rows[0].doc["job"].should.eql "pilot"
+ result.rows[0].doc["_rev"].length.should.be_at_least 30
+ end
+
+ it 'should apply a view in erlang also'
+ // when this test fails, read this: http://wiki.apache.org/couchdb/EnableErlangViews
+ var erlang_map = 'fun({Doc}) -> ' +
+ 'ID = proplists:get_value(<<"_id">>, Doc, null), ' +
+ 'Emit(ID, 1) ' +
+ 'end.';
+ var result = db.query(erlang_map, null, null, null, "erlang");
+
+ result.rows.should.have_length 3
+ result.rows[0].id.should.eql "123"
+ result.rows[0].key.should.eql "123"
+ result.rows[0].value.should.eql 1
+ result.rows[1].id.should.eql "456"
+ result.rows[1].key.should.eql "456"
+ result.rows[1].value.should.eql 1
+ result.rows[2].id.should.eql "789"
+ result.rows[2].key.should.eql "789"
+ result.rows[2].value.should.eql 1
+ end
+ end
+
+ describe '.view'
+ before_each
+ db.save({"Name" : "Cally Tyrol", "job" : "deckhand", "_id" : "789"});
+ db.save({"Name" : "Felix Gaeta", "job" : "officer", "_id" : "123"});
+ db.save({"Name" : "Samuel T. Anders", "job" : "pilot", "_id" : "456"});
+ view = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc.Name); }"
+ }
+ },
+ "_id" : "_design/spec_db"
+ };
+ db.save(view);
+ end
+
+ it 'should apply the view'
+ var result = db.view('spec_db/people');
+
+ result.rows.should.have_length 3
+ result.rows[0].id.should.eql "123"
+ result.rows[0].key.should.eql "123"
+ result.rows[0].value.should.eql "Felix Gaeta"
+ result.rows[1].id.should.eql "456"
+ result.rows[1].key.should.eql "456"
+ result.rows[1].value.should.eql "Samuel T. Anders"
+ result.rows[2].id.should.eql "789"
+ result.rows[2].key.should.eql "789"
+ result.rows[2].value.should.eql "Cally Tyrol"
+ end
+
+ it 'should pass through the options'
+ var result = db.view('spec_db/people', {"skip":"2"});
+
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "789"
+ result.rows[0].key.should.eql "789"
+ result.rows[0].value.should.eql "Cally Tyrol"
+ end
+
+ it 'should pass through the keys'
+ var result = db.view('spec_db/people', {}, ["456", "123"]);
+
+ result.rows.should.have_length 2
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.should.eql "Samuel T. Anders"
+ result.rows[1].id.should.eql "123"
+ result.rows[1].key.should.eql "123"
+ result.rows[1].value.should.eql "Felix Gaeta"
+ end
+
+ it 'should pass through the options and the keys'
+ var result = db.view('spec_db/people', {"include_docs":"true"}, ["456"]);
+
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.should.eql "Samuel T. Anders"
+ result.rows[0].doc["job"].should.eql "pilot"
+ result.rows[0].doc["_rev"].length.should.be_at_least 30
+ end
+
+ it 'should return null when the view doesnt exist'
+ var result = db.view('spec_db/non_existing_view');
+
+ result.should.be_null
+ end
+ end
+
+ describe '.info'
+ before_each
+ result = db.info();
+ end
+
+ it 'should return the name of the database'
+ result.db_name.should.eql "spec_db"
+ end
+
+ it 'should return the number of documents'
+ result.doc_count.should.eql 0
+ end
+
+ it 'should return the start time of the db instance'
+ result.instance_start_time.should.have_length 16
+ end
+ end
+
+ describe '.designInfo'
+ before_each
+ designDoc = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc); }"
+ }
+ },
+ "_id" : "_design/spec_db"
+ };
+ db.save(designDoc);
+ result = db.designInfo("_design/spec_db");
+ end
+
+ it 'should return the database name'
+ result.name.should.eql "spec_db"
+ end
+
+ it 'should return a views language'
+ result.view_index.language.should.eql "javascript"
+ end
+
+ it 'should return a views update sequence'
+ result.view_index.update_seq.should.eql 0
+ end
+
+ it 'should return a views signature'
+ result.view_index.signature.should.have_length 32
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/couch_js_instance_methods_3_spec.js b/share/www/spec/couch_js_instance_methods_3_spec.js
new file mode 100644
index 00000000..692e08e6
--- /dev/null
+++ b/share/www/spec/couch_js_instance_methods_3_spec.js
@@ -0,0 +1,203 @@
+// Specs for couch.js lines 201-265
+
+describe 'CouchDB instance'
+ before_each
+ db = new CouchDB("spec_db", {"X-Couch-Full-Commit":"false"});
+ db.createDb();
+ end
+
+ after_each
+ db.deleteDb();
+ end
+
+ describe '.allDocs'
+ it 'should return no docs when there arent any'
+ db.allDocs().total_rows.should.eql 0
+ db.allDocs().rows.should.eql []
+ end
+
+ describe 'with docs'
+ before_each
+ db.save({"Name" : "Felix Gaeta", "_id" : "123"});
+ db.save({"Name" : "Samuel T. Anders", "_id" : "456"});
+ end
+
+ it 'should return all docs'
+ var result = db.allDocs();
+
+ result.total_rows.should.eql 2
+ result.rows.should.have_length 2
+ result.rows[0].id.should.eql "123"
+ result.rows[0].key.should.eql "123"
+ result.rows[0].value.rev.length.should.be_at_least 30
+ result.rows[1].id.should.eql "456"
+ end
+
+ it 'should pass through the options'
+ var result = db.allDocs({"startkey": "123", "limit": "1"});
+
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "123"
+ end
+
+ it 'should pass through the keys'
+ var result = db.allDocs({}, ["456"]);
+
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.rev.length.should.be_at_least 30
+ end
+
+ it 'should pass through the options and the keys'
+ var result = db.allDocs({"include_docs":"true"}, ["456"]);
+
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "456"
+ result.rows[0].key.should.eql "456"
+ result.rows[0].value.rev.length.should.be_at_least 30
+ result.rows[0].doc["Name"].should.eql "Samuel T. Anders"
+ result.rows[0].doc["_rev"].length.should.be_at_least 30
+ end
+
+ end
+ end
+
+ describe '.designDocs'
+ it 'should return nothing when there arent any design docs'
+ db.save({"Name" : "Felix Gaeta", "_id" : "123"});
+ db.designDocs().rows.should.eql []
+ end
+
+ it 'should return all design docs'
+ var designDoc = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc); }"
+ }
+ },
+ "_id" : "_design/spec_db"
+ };
+ db.save(designDoc);
+ db.save({"Name" : "Felix Gaeta", "_id" : "123"});
+
+ var result = db.designDocs();
+
+ result.total_rows.should.eql 2
+ result.rows.should.have_length 1
+ result.rows[0].id.should.eql "_design/spec_db"
+ result.rows[0].key.should.eql "_design/spec_db"
+ result.rows[0].value.rev.length.should.be_at_least 30
+ end
+ end
+
+ describe '.changes'
+ it 'should return no changes when there arent any'
+ db.changes().last_seq.should.eql 0
+ db.changes().results.should.eql []
+ end
+
+ describe 'with changes'
+ before_each
+ db.save({"Name" : "Felix Gaeta", "_id" : "123"});
+ db.save({"Name" : "Samuel T. Anders", "_id" : "456"});
+ end
+
+ it 'should return changes'
+ var result = db.changes();
+
+ result.last_seq.should.eql 2
+ result.results[0].id.should.eql "123"
+ result.results[0].seq.should.eql 1
+ result.results[0].changes[0].rev.length.should.be_at_least 30
+ result.results[1].id.should.eql "456"
+ result.results[1].seq.should.eql 2
+ result.results[1].changes[0].rev.length.should.be_at_least 30
+ end
+
+ it 'should pass through the options'
+ var result = db.changes({"since":"1"});
+
+ result.last_seq.should.eql 2
+ result.results[0].id.should.eql "456"
+ end
+ end
+ end
+
+ describe '.compact'
+ it 'should return ok true'
+ db.compact().ok.should.be_true
+ end
+
+ it 'should post _compact to the db'
+ db.should.receive("request", "once").with_args("POST", "/spec_db/_compact")
+ db.compact();
+ end
+ end
+
+ describe '.viewCleanup'
+ it 'should return ok true'
+ db.viewCleanup().ok.should.be_true
+ end
+
+ it 'should post _view_cleanup to the db'
+ db.should.receive("request", "once").with_args("POST", "/spec_db/_view_cleanup")
+ db.viewCleanup();
+ end
+ end
+
+ describe '.setDbProperty'
+ it 'should return ok true'
+ db.setDbProperty("_revs_limit", 1500).ok.should.be_true
+ end
+
+ it 'should set a db property'
+ db.setDbProperty("_revs_limit", 1500);
+ db.getDbProperty("_revs_limit").should.eql 1500
+ db.setDbProperty("_revs_limit", 1200);
+ db.getDbProperty("_revs_limit").should.eql 1200
+ end
+ end
+
+ describe '.getDbProperty'
+ it 'should get a db property'
+ db.setDbProperty("_revs_limit", 1200);
+ db.getDbProperty("_revs_limit").should.eql 1200
+ end
+
+ it 'should throw an error when the property doesnt exist'
+ function(){ db.getDbProperty("_doesnt_exist")}.should.throw_error
+ end
+ end
+
+ describe '.setSecObj'
+ it 'should return ok true'
+ db.setSecObj({"readers":{"names":["laura"],"roles":["president"]}}).ok.should.be_true
+ end
+
+ it 'should save a well formed object into the _security object '
+ db.should.receive("request", "once").with_args("PUT", "/spec_db/_security", {body: '{"readers":{"names":["laura"],"roles":["president"]}}'})
+ db.setSecObj({"readers": {"names" : ["laura"], "roles" : ["president"]}})
+ end
+
+ it 'should throw an error when the readers or admins object is malformed'
+ -{ db.setSecObj({"admins":["cylon"]}) }.should.throw_error
+ end
+
+ it 'should save any other object into the _security object'
+ db.setSecObj({"something" : "anything"})
+ db.getSecObj().should.eql {"something" : "anything"}
+ end
+ end
+
+ describe '.getSecObj'
+ it 'should get the security object'
+ db.setSecObj({"admins" : {"names" : ["bill"], "roles" : ["admiral"]}})
+ db.getSecObj().should.eql {"admins" : {"names": ["bill"], "roles": ["admiral"]}}
+ end
+
+ it 'should return an empty object when there is no security object'
+ db.getSecObj().should.eql {}
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/custom_helpers.js b/share/www/spec/custom_helpers.js
new file mode 100644
index 00000000..35055c54
--- /dev/null
+++ b/share/www/spec/custom_helpers.js
@@ -0,0 +1,39 @@
+function stubAlert(){
+ if(typeof(old_alert) == 'undefined'){
+ old_alert = alert;
+ }
+ alert = function(msg){
+ alert_msg = msg;
+ };
+}
+
+function destubAlert(){
+ alert = old_alert;
+}
+
+function errorCallback(status, error, reason){
+ console.log("Unexpected " + status + " error: " + error + " - " + reason)
+ throw("Unexpected " + status + " error: " + error + " - " + reason);
+}
+
+function successCallback(resp){
+ console.log("No error message here unexpectedly, successful response instead.")
+ throw("No error message here unexpectedly, successful response instead.");
+}
+
+function useTestUserDb(){
+ users_db = new CouchDB("spec_users_db");
+ var xhr = CouchDB.request("PUT", "/_config/couch_httpd_auth/authentication_db", {
+ body: JSON.stringify("spec_users_db")
+ });
+ if(typeof(old_auth_db) == 'undefined'){
+ old_auth_db = xhr.responseText.replace(/\n/,'').replace(/"/g,'');
+ }
+}
+
+function useOldUserDb(){
+ CouchDB.request("PUT", "/_config/couch_httpd_auth/authentication_db", {
+ body: JSON.stringify(old_auth_db)
+ });
+ users_db.deleteDb();
+} \ No newline at end of file
diff --git a/share/www/spec/jquery_couch_js_class_methods_spec.js b/share/www/spec/jquery_couch_js_class_methods_spec.js
new file mode 100644
index 00000000..a8deb2b6
--- /dev/null
+++ b/share/www/spec/jquery_couch_js_class_methods_spec.js
@@ -0,0 +1,511 @@
+// Specs for jquery_couch.js lines 48-156 and 415-448
+
+describe 'jQuery couchdb'
+ before
+ stubAlert();
+ end
+
+ after
+ destubAlert();
+ end
+
+ describe 'activeTasks'
+ before_each
+ db = $.couch.db("spec_db");
+ db.create();
+ end
+
+ after_each
+ db.drop();
+ end
+
+ it 'should return an empty array when there are no active tasks'
+ $.couch.activeTasks({
+ success: function(resp){
+ resp.should.eql []
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return an active task'
+ // doing a bit of stuff here so compaction has something to do and takes a while
+ var battlestar, civillian;
+ db.saveDoc({"type":"Battlestar", "name":"Galactica"}, {
+ success: function(resp){
+ db.openDoc(resp.id, {
+ success: function(resp2){
+ battlestar = resp2;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ battlestar.name = "Pegasus";
+ db.saveDoc(battlestar);
+
+ db.saveDoc({"type":"Civillian", "name":"Cloud 9"}, {
+ success: function(resp){
+ db.openDoc(resp.id, {
+ success: function(resp2){
+ civillian = resp2;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ civillian.name = "Olympic Carrier";
+ db.saveDoc(civillian);
+ db.removeDoc(civillian);
+
+ db.compact({
+ ajaxStart: function(resp){
+ $.couch.activeTasks({
+ success: function(resp2){
+ resp2[0].type.should.eql "Database Compaction"
+ resp2[0].task.should.eql "spec_db"
+ resp2[0].should.have_prop "status"
+ resp2[0].should.include "pid"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ }
+ });
+ end
+ end
+
+ describe 'allDbs'
+ it 'should return an array that includes a created database'
+ temp_db = new CouchDB("temp_spec_db", {"X-Couch-Full-Commit":"false"});
+ temp_db.createDb();
+ $.couch.allDbs({
+ success: function(resp){
+ resp.should.include "temp_spec_db"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ temp_db.deleteDb();
+ end
+
+ it 'should return an array that does not include a database that does not exist'
+ $.couch.allDbs({
+ success: function(resp){
+ resp.should.not.include("not_existing_temp_spec_db");
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'config'
+ it 'should get the config settings'
+ $.couch.config({
+ success: function(resp){
+ resp.httpd.port.should.eql window.location.port
+ resp.stats.samples.should.match /\[.*\]/
+ resp.native_query_servers.should.have_prop "erlang"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should get a specific config setting'
+ $.couch.config({
+ success: function(resp){
+ parseInt(resp.max_document_size).should.be_a Number
+ resp.delayed_commits.should.be_a String
+ resp.database_dir.should.be_a String
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, "couchdb");
+ end
+
+ it 'should update a config setting'
+ $.couch.config({
+ success: function(resp){
+ resp.should.eql ""
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, "test", "colony", "Caprica");
+
+ $.couch.config({
+ success: function(resp){
+ resp.colony.should.eql "Caprica"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, "test");
+
+ $.couch.config({}, "test", "colony", null);
+ end
+
+ it 'should delete a config setting'
+ $.couch.config({}, "test", "colony", "Caprica");
+
+ $.couch.config({
+ success: function(resp){
+ resp.should.eql "Caprica"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, "test", "colony", null);
+
+ $.couch.config({
+ success: function(resp){
+ resp.should.eql {}
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, "test");
+ end
+
+ it 'should alert with an error message prefix'
+ $.couch.config("asdf", "asdf", "asdf");
+ alert_msg.should.match /An error occurred retrieving\/updating the server configuration/
+ end
+ end
+
+ describe 'session'
+ it 'should return information about the session'
+ $.couch.session({
+ success: function(resp){
+ resp.info.should.have_prop 'authentication_db'
+ resp.userCtx.should.include 'name'
+ resp.userCtx.roles.should.be_an Array
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'userDb'
+ it 'should return the userDb'
+ var authentication_db;
+ $.couch.session({
+ success: function(resp){
+ authentication_db = resp.info.authentication_db;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+
+ $.couch.userDb(function(resp){
+ resp.name.should.eql authentication_db
+ });
+ end
+
+ it 'should return a db instance'
+ $.couch.userDb(function(resp){
+ resp.should.respond_to 'allDocs'
+ resp.should.respond_to 'bulkSave'
+ });
+ end
+ end
+
+ describe 'user_db stuff'
+ before
+ useTestUserDb();
+ end
+
+ after
+ useOldUserDb();
+ end
+
+ describe 'signup'
+ it 'should return a saved user'
+ $.couch.signup(
+ {name: "Tom Zarek"}, "secretpass", {
+ success: function(resp){
+ resp.id.should.eql "org.couchdb.user:Tom Zarek"
+ resp.rev.length.should.be_at_least 30
+ resp.ok.should.be_true
+ users_db.deleteDoc({_id : resp.id, _rev : resp.rev})
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should create a userDoc in the user db'
+ $.couch.signup(
+ {name: "Tom Zarek"}, "secretpass", {
+ success: function(resp){
+ var user = users_db.open(resp.id);
+ user.name.should.eql "Tom Zarek"
+ user._id.should.eql "org.couchdb.user:Tom Zarek"
+ user.roles.should.eql []
+ user.password_sha.length.should.be_at_least 30
+ user.password_sha.should.be_a String
+ users_db.deleteDoc({_id : resp.id, _rev : resp.rev})
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should create a userDoc with roles when specified'
+ $.couch.signup(
+ {name: "Tom Zarek", roles: ["vice_president", "activist"]}, "secretpass", {
+ success: function(resp){
+ var user = users_db.open(resp.id);
+ user.roles.should.eql ["vice_president", "activist"]
+ users_db.deleteDoc({_id : resp.id, _rev : resp.rev})
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'login'
+ before_each
+ user = {};
+ $.couch.signup({name: "Tom Zarek", roles: ["vice_president", "activist"]}, "secretpass", {
+ success: function(resp){
+ user.id = resp.id;
+ user.rev = resp.rev;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ after_each
+ users_db.deleteDoc({_id : user.id, _rev : user.rev})
+ end
+
+ it 'should return the logged in user'
+ $.couch.login({
+ name: "Tom Zarek",
+ password: "secretpass",
+ success: function(resp){
+ resp.name.should.eql "Tom Zarek"
+ resp.ok.should.be_true
+ resp.roles.should.eql ["vice_president", "activist"]
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in a session for the logged in user'
+ $.couch.login({
+ name: "Tom Zarek",
+ password: "secretpass"
+ });
+ $.couch.session({
+ success: function(resp){
+ resp.info.authentication_db.should.eql "spec_users_db"
+ resp.userCtx.name.should.eql "Tom Zarek"
+ resp.userCtx.roles.should.eql ["vice_president", "activist"]
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return a 404 when password is wrong'
+ $.couch.login({
+ name: "Tom Zarek",
+ password: "wrongpass",
+ error: function(status, error, reason){
+ status.should.eql 401
+ error.should.eql "unauthorized"
+ reason.should.eql "Name or password is incorrect."
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should return a 404 when the user doesnt exist in the users db'
+ $.couch.login({
+ name: "Number Three",
+ password: "secretpass",
+ error: function(status, error, reason){
+ status.should.eql 401
+ error.should.eql "unauthorized"
+ reason.should.eql "Name or password is incorrect."
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ $.couch.login("asdf");
+ alert_msg.should.match /An error occurred logging in/
+ end
+ end
+
+ describe 'logout'
+ before_each
+ user = {};
+ $.couch.signup({name: "Tom Zarek", roles: ["vice_president", "activist"]}, "secretpass", {
+ success: function(resp){
+ user.id = resp.id;
+ user.rev = resp.rev;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ $.couch.login({name: "Tom Zarek", password: "secretpass"});
+ end
+
+ after_each
+ users_db.deleteDoc({_id : user.id, _rev : user.rev})
+ end
+
+ it 'should return ok true'
+ $.couch.logout({
+ success: function(resp){
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in an empty session'
+ $.couch.logout();
+ $.couch.session({
+ success: function(resp){
+ resp.userCtx.name.should.be_null
+ resp.userCtx.roles.should.not.include ["vice_president"]
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+ end
+
+ describe 'encodeDocId'
+ it 'should return the encoded docID when it is not a design document'
+ $.couch.encodeDocId("viper").should.eql(encodeURIComponent("viper"))
+ end
+
+ it 'should encode only the name of the design document'
+ $.couch.encodeDocId("_design/raptor").should.eql("_design/" + encodeURIComponent("raptor"))
+ end
+
+ it 'should also work when the name of the des'
+ $.couch.encodeDocId("_design/battlestar/_view/crew").should.eql("_design/" + encodeURIComponent("battlestar/_view/crew"))
+ end
+ end
+
+ describe 'info'
+ it 'should return the CouchDB version'
+ $.couch.info({
+ success: function(resp){
+ resp.couchdb.should.eql "Welcome"
+ resp.version.should_match /^\d\d?\.\d\d?\.\d\d?.*/
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'replicate'
+ before_each
+ db = $.couch.db("spec_db");
+ db.create();
+ db2 = $.couch.db("spec_db_2");
+ db2.create();
+ host = window.location.protocol + "//" + window.location.host ;
+ end
+
+ after_each
+ db.drop();
+ db2.drop();
+ end
+
+ it 'should return no_changes true when there are no changes between the dbs'
+ $.couch.replicate(host + db.uri, host + db2.uri, {
+ success: function(resp){
+ resp.ok.should.be_true
+ resp.no_changes.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return the session ID'
+ db.saveDoc({'type':'battlestar', 'name':'galactica'});
+ $.couch.replicate(host + db.uri, host + db2.uri, {
+ success: function(resp){
+ resp.session_id.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return source_last_seq'
+ db.saveDoc({'type':'battlestar', 'name':'galactica'});
+ db.saveDoc({'type':'battlestar', 'name':'pegasus'});
+
+ $.couch.replicate(host + db.uri, host + db2.uri, {
+ success: function(resp){
+ resp.source_last_seq.should.eql 2
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return the replication history'
+ db.saveDoc({'type':'battlestar', 'name':'galactica'});
+ db.saveDoc({'type':'battlestar', 'name':'pegasus'});
+
+ $.couch.replicate(host + db.uri, host + db2.uri, {
+ success: function(resp){
+ resp.history[0].docs_written.should.eql 2
+ resp.history[0].start_last_seq.should.eql 0
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through replication options'
+ db.saveDoc({'type':'battlestar', 'name':'galactica'});
+ db2.drop();
+ $.couch.replicate(host + db.uri, host + db2.uri, {
+ error: function(status, error, reason){
+ status.should.eql 500
+ reason.should.match /db_not_found/
+ },
+ success: function(resp){successCallback(resp)}
+ });
+
+ $.couch.replicate(host + db.uri, host + db2.uri, {
+ success: function(resp){
+ resp.ok.should.eql true
+ resp.history[0].docs_written.should.eql 1
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, {
+ "create_target":true
+ });
+
+ db2.info({
+ success: function(resp){
+ resp.db_name.should.eql "spec_db_2"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ $.couch.replicate("asdf");
+ alert_msg.should.match /Replication failed/
+ end
+ end
+
+ describe 'newUUID'
+ it 'should return a new UUID'
+ var new_uuid = $.couch.newUUID(1);
+ new_uuid.should.be_a String
+ new_uuid.should.have_length 32
+ end
+
+ it 'should fill the uuidCache with the specified number minus 1'
+ // we can't reach the uuidCache from here, so we mock the next request
+ // to test that the next uuid is not coming from the request, but from the cache.
+ $.couch.newUUID(2);
+ mock_request().and_return({'uuids':['a_sample_uuid']})
+ $.couch.newUUID(1).should.not.eql 'a_sample_uuid'
+ $.couch.newUUID(1).should.eql 'a_sample_uuid'
+ end
+
+ it 'should alert with an error message prefix'
+ $.couch.newUUID("asdf");
+ alert_msg.should.match /Failed to retrieve UUID batch/
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/jquery_couch_js_instance_methods_1_spec.js b/share/www/spec/jquery_couch_js_instance_methods_1_spec.js
new file mode 100644
index 00000000..3dc363ba
--- /dev/null
+++ b/share/www/spec/jquery_couch_js_instance_methods_1_spec.js
@@ -0,0 +1,190 @@
+// Specs for jquery_couch.js lines 163-209
+
+describe 'jQuery couchdb db'
+ before
+ stubAlert();
+ end
+
+ after
+ destubAlert();
+ end
+
+ before_each
+ db = $.couch.db('spec_db');
+ end
+
+ describe 'constructor'
+ it 'should set the name'
+ db.name.should.eql 'spec_db'
+ end
+
+ it 'should set the uri'
+ db.uri.should.eql '/spec_db/'
+ end
+ end
+
+ describe 'triggering db functions'
+ before_each
+ db.create();
+ end
+
+ after_each
+ db.drop();
+ end
+
+ describe 'compact'
+ it 'should return ok true'
+ db.compact({
+ success: function(resp) {
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should trigger _compact'
+ db.compact({
+ success: function(resp, obj) {
+ obj.url.should.eql "/spec_db/_compact"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'viewCleanup'
+ it 'should return ok true'
+ db.viewCleanup({
+ success: function(resp) {
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should trigger _view_cleanup'
+ db.viewCleanup({
+ success: function(resp, obj) {
+ obj.url.should.eql "/spec_db/_view_cleanup"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'compactView'
+ before_each
+ var designDoc = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc); }"
+ }
+ },
+ "_id" : "_design/myview"
+ };
+ db.saveDoc(designDoc);
+ db.saveDoc({"Name" : "Felix Gaeta", "_id" : "123"});
+ end
+
+ it 'should return ok true'
+ db.compactView("myview", {
+ success: function(resp) {
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should trigger _compact_view with the groupname'
+ db.compactView("myview", {
+ success: function(resp, obj) {
+ obj.url.should.eql "/spec_db/_compact/myview"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return raise a 404 error when the design name doesnt exist'
+ db.compactView("non_existing_design_name", {
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "missing"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.compactView("asdf");
+ alert_msg.should.match /The view could not be compacted/
+ end
+ end
+ end
+
+ describe 'create'
+ after_each
+ db.drop();
+ end
+
+ it 'should return ok true'
+ db.create({
+ success: function(resp) {
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in a created db'
+ db.create();
+ db.create({
+ error: function(status, error, reason){
+ status.should.eql 412
+ error.should.eql "file_exists"
+ reason.should.eql "The database could not be created, the file already exists."
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.create();
+ db.create();
+ alert_msg.should.match /The database could not be created/
+ end
+ end
+
+ describe 'drop'
+ before_each
+ db.create();
+ end
+
+ it 'should return ok true'
+ db.drop({
+ success: function(resp) {
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in a deleted db'
+ db.drop();
+ db.drop({
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "missing"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.drop();
+ db.drop();
+ alert_msg.should.match /The database could not be deleted/
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/jquery_couch_js_instance_methods_2_spec.js b/share/www/spec/jquery_couch_js_instance_methods_2_spec.js
new file mode 100644
index 00000000..fbcdacc4
--- /dev/null
+++ b/share/www/spec/jquery_couch_js_instance_methods_2_spec.js
@@ -0,0 +1,421 @@
+// Specs for jquery_couch.js lines 210-299
+
+describe 'jQuery couchdb db'
+ before
+ stubAlert();
+ end
+
+ after
+ destubAlert();
+ end
+
+ before_each
+ db = $.couch.db('spec_db');
+ db.create();
+ end
+
+ after_each
+ db.drop();
+ end
+
+ describe 'info'
+ before_each
+ result = {};
+ db.info({
+ success: function(resp) { result = resp; },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return the name of the database'
+ result.db_name.should.eql "spec_db"
+ end
+
+ it 'should return the number of documents'
+ result.doc_count.should.eql 0
+ end
+
+ it 'should return the start time of the db instance'
+ result.instance_start_time.should.have_length 16
+ end
+ end
+
+ describe 'allDocs'
+ it 'should return no docs when there arent any'
+ db.allDocs({
+ success: function(resp) {
+ resp.total_rows.should.eql 0
+ resp.rows.should.eql []
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ describe 'with docs'
+ before_each
+ db.saveDoc({"Name" : "Felix Gaeta", "_id" : "123"});
+ db.saveDoc({"Name" : "Samuel T. Anders", "_id" : "456"});
+ end
+
+ it 'should return all docs'
+ db.allDocs({
+ success: function(resp) {
+ resp.total_rows.should.eql 2
+ resp.rows.should.have_length 2
+ resp.rows[0].id.should.eql "123"
+ resp.rows[0].key.should.eql "123"
+ resp.rows[0].value.rev.length.should.be_at_least 30
+ resp.rows[1].id.should.eql "456"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options'
+ db.allDocs({
+ "startkey": "123",
+ "limit": "1",
+ success: function(resp) {
+ resp.rows.should.have_length 1
+ resp.rows[0].id.should.eql "123"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+ end
+
+ describe 'allDesignDocs'
+ it 'should return nothing when there arent any design docs'
+ db.saveDoc({"Name" : "Felix Gaeta", "_id" : "123"});
+ db.allDesignDocs({
+ success: function(resp) {
+ resp.rows.should.eql []
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return all design docs'
+ var designDoc = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc); }"
+ }
+ },
+ "_id" : "_design/spec_db"
+ };
+ db.saveDoc(designDoc);
+ db.saveDoc({"Name" : "Felix Gaeta", "_id" : "123"});
+
+ db.allDesignDocs({
+ success: function(resp) {
+ resp.total_rows.should.eql 2
+ resp.rows.should.have_length 1
+ resp.rows[0].id.should.eql "_design/spec_db"
+ resp.rows[0].key.should.eql "_design/spec_db"
+ resp.rows[0].value.rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+ end
+
+ describe 'allApps'
+ it 'should provide a custom function with appName, appPath and design document when there is an attachment with index.html'
+ var designDoc = {"_id" : "_design/with_attachments"};
+
+ designDoc._attachments = {
+ "index.html" : {
+ "content_type": "text\/html",
+ // this is "<html><p>Hi, here is index!</p></html>", base64 encoded
+ "data": "PGh0bWw+PHA+SGksIGhlcmUgaXMgaW5kZXghPC9wPjwvaHRtbD4="
+ }
+ };
+ db.saveDoc(designDoc);
+
+ db.allApps({
+ eachApp: function(appName, appPath, ddoc) {
+ appName.should.eql "with_attachments"
+ appPath.should.eql "/spec_db/_design/with_attachments/index.html"
+ ddoc._id.should.eql "_design/with_attachments"
+ ddoc._attachments["index.html"].content_type.should.eql "text/html"
+ ddoc._attachments["index.html"].length.should.eql "<html><p>Hi, here is index!</p></html>".length
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should provide a custom function with appName, appPath and design document when there is a couchapp with index file'
+ var designDoc = {"_id" : "_design/with_index"};
+ designDoc.couchapp = {
+ "index" : "cylon"
+ };
+ db.saveDoc(designDoc);
+
+ db.allApps({
+ eachApp: function(appName, appPath, ddoc) {
+ appName.should.eql "with_index"
+ appPath.should.eql "/spec_db/_design/with_index/cylon"
+ ddoc._id.should.eql "_design/with_index"
+ ddoc.couchapp.index.should.eql "cylon"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should not call the eachApp function when there is neither index.html in _attachments nor a couchapp index file'
+ var designDoc = {"_id" : "_design/nothing"};
+ db.saveDoc(designDoc);
+
+ var eachApp_called = false;
+ db.allApps({
+ eachApp: function(appName, appPath, ddoc) {
+ eachApp_called = true;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+
+ eachApp_called.should.be_false
+ end
+
+ it 'should alert with an error message prefix'
+ db.allApps();
+ alert_msg.should.match /Please provide an eachApp function for allApps()/
+ end
+ end
+
+ describe 'openDoc'
+ before_each
+ doc = {"Name" : "Louanne Katraine", "Callsign" : "Kat", "_id" : "123"};
+ db.saveDoc(doc);
+ end
+
+ it 'should open the document'
+ db.openDoc("123", {
+ success: function(resp){
+ resp.should.eql doc
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should raise a 404 error when there is no document with the given ID'
+ db.openDoc("non_existing", {
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "missing"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should pass through the options'
+ doc.Name = "Sasha";
+ db.saveDoc(doc);
+ db.openDoc("123", {
+ revs: true,
+ success: function(resp){
+ resp._revisions.start.should.eql 2
+ resp._revisions.ids.should.have_length 2
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.openDoc("asdf");
+ alert_msg.should.match /The document could not be retrieved/
+ end
+ end
+
+ describe 'saveDoc'
+ before_each
+ doc = {"Name" : "Kara Thrace", "Callsign" : "Starbuck"};
+ end
+
+ it 'should save the document'
+ db.saveDoc(doc, {
+ success: function(resp, status){
+ status.should.eql 201
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return ok true'
+ db.saveDoc(doc, {
+ success: function(resp, status){
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return ID and revision of the document'
+ db.saveDoc(doc, {
+ success: function(resp, status){
+ resp.id.should.be_a String
+ resp.id.should.have_length 32
+ resp.rev.should.be_a String
+ resp.rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in a saved document with generated ID'
+ db.saveDoc(doc, {
+ success: function(resp, status){
+ db.openDoc(resp.id, {
+ success: function(resp2){
+ resp2.Name.should.eql "Kara Thrace"
+ resp2.Callsign.should.eql "Starbuck"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should save the document with the specified ID'
+ doc._id = "123";
+ db.saveDoc(doc, {
+ success: function(resp, status){
+ resp.id.should.eql "123"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options'
+ db.saveDoc(doc, {
+ "batch" : "ok",
+ success: function(resp, status){
+ // when using batch ok, couch sends a 202 status immediately
+ status.should.eql 202
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.saveDoc("asdf");
+ alert_msg.should.match /The document could not be saved/
+ end
+ end
+
+ describe 'bulkSave'
+ before_each
+ doc = {"Name" : "Kara Thrace", "Callsign" : "Starbuck"};
+ doc2 = {"Name" : "Karl C. Agathon", "Callsign" : "Helo"};
+ doc3 = {"Name" : "Sharon Valerii", "Callsign" : "Boomer"};
+ docs = [doc, doc2, doc3];
+ end
+
+ it 'should save all documents'
+ db.bulkSave({"docs": docs});
+ db.allDocs({
+ success: function(resp) {
+ resp.total_rows.should.eql 3
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in saved documents'
+ doc3._id = "789";
+ db.bulkSave({"docs": [doc3]});
+
+ db.openDoc("789", {
+ success: function(resp){
+ resp.Name.should.eql "Sharon Valerii"
+ resp.Callsign.should.eql "Boomer"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return ID and revision of the documents'
+ db.bulkSave({"docs": docs},{
+ success: function(resp){
+ resp[0].id.should.be_a String
+ resp[0].id.should.have_length 32
+ resp[0].rev.should.be_a String
+ resp[0].rev.length.should.be_at_least 30
+ resp[1].id.should.be_a String
+ resp[1].id.should.have_length 32
+ resp[1].rev.should.be_a String
+ resp[1].rev.length.should.be_at_least 30
+ resp[2].id.should.be_a String
+ resp[2].id.should.have_length 32
+ resp[2].rev.should.be_a String
+ resp[2].rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should save the document with specified IDs'
+ doc._id = "123";
+ doc2._id = "456";
+ docs = [doc, doc2, doc3];
+
+ db.bulkSave({"docs": docs},{
+ success: function(resp){
+ resp[0].id.should.eql "123"
+ resp[1].id.should.eql "456"
+ resp[2].id.should.have_length 32
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options'
+ // a lengthy way to test that a conflict can't be created with the
+ // all_or_nothing option set to false, but can be when it's true.
+
+ var old_doc = {"Name" : "Louanne Katraine", "Callsign" : "Kat", "_id" : "123"};
+ db.saveDoc(old_doc, {
+ success: function(resp){
+ old_doc._rev = resp.rev;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+
+ var new_doc = {"Name" : "Sasha", "Callsign" : "Kat", "_id" : "123"};
+
+ db.bulkSave({"docs": [new_doc], "all_or_nothing": false}, {
+ success: function(resp){
+ resp[0].id.should.eql "123"
+ resp[0].error.should.eql "conflict"
+ resp[0].reason.should.eql "Document update conflict."
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+
+ db.bulkSave({"docs": [new_doc], "all_or_nothing": true}, {
+ success: function(resp){
+ resp[0].id.should.eql "123"
+ resp[0].rev.should.not.eql old_doc._rev
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+
+ db.openDoc("123", {
+ "conflicts": true,
+ success: function(resp){
+ resp._conflicts[0].should.eql old_doc._rev
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.bulkSave("asdf");
+ alert_msg.should.match /The documents could not be saved/
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/jquery_couch_js_instance_methods_3_spec.js b/share/www/spec/jquery_couch_js_instance_methods_3_spec.js
new file mode 100644
index 00000000..4d21c16c
--- /dev/null
+++ b/share/www/spec/jquery_couch_js_instance_methods_3_spec.js
@@ -0,0 +1,528 @@
+// Specs for jquery_couch.js lines 300-411
+
+describe 'jQuery couchdb db'
+ before
+ stubAlert();
+ end
+
+ after
+ destubAlert();
+ end
+
+ before_each
+ db = $.couch.db('spec_db');
+ db.create();
+ end
+
+ after_each
+ db.drop();
+ end
+
+ describe 'removeDoc'
+ before_each
+ doc = {"Name" : "Louanne Katraine", "Callsign" : "Kat", "_id" : "345"};
+ saved_doc = {};
+ db.saveDoc(doc, {
+ success: function(resp){
+ saved_doc = resp;
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in a deleted document'
+ db.removeDoc({_id : "345", _rev : saved_doc.rev}, {
+ success: function(resp){
+ db.openDoc("345", {
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "deleted"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return ok true, the ID and the revision of the deleted document'
+ db.removeDoc({_id : "345", _rev : saved_doc.rev}, {
+ success: function(resp){
+ resp.ok.should.be_true
+ resp.id.should.eql "345"
+ resp.rev.should.be_a String
+ resp.rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should record the revision in the deleted document'
+ db.removeDoc({_id : "345", _rev : saved_doc.rev}, {
+ success: function(resp){
+ db.openDoc("345", {
+ rev: resp.rev,
+ success: function(resp2){
+ resp2._rev.should.eql resp.rev
+ resp2._id.should.eql resp.id
+ resp2._deleted.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.removeDoc({_id: "asdf"});
+ alert_msg.should.match /The document could not be deleted/
+ end
+ end
+
+ describe 'bulkRemove'
+ before_each
+ doc = {"Name" : "Kara Thrace", "Callsign" : "Starbuck", "_id" : "123"};
+ doc2 = {"Name" : "Karl C. Agathon", "Callsign" : "Helo", "_id" : "456"};
+ doc3 = {"Name" : "Sharon Valerii", "Callsign" : "Boomer", "_id" : "789"};
+ docs = [doc, doc2, doc3];
+
+ db.bulkSave({"docs": docs}, {
+ success: function(resp){
+ for (var i = 0; i < docs.length; i++) {
+ docs[i]._rev = resp[i].rev;
+ }
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should remove all documents specified'
+ db.bulkRemove({"docs": docs});
+ db.allDocs({
+ success: function(resp) {
+ resp.total_rows.should.eql 0
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should not remove documents that should not have been deleted'
+ db.bulkRemove({"docs": [doc3]});
+ db.allDocs({
+ success: function(resp) {
+ resp.total_rows.should.eql 2
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should result in deleted documents'
+ db.bulkRemove({"docs": docs}, {
+ success: function(resp){
+ db.openDoc("123", {
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "deleted"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should return the ID and the revision of the deleted documents'
+ db.bulkRemove({"docs": docs}, {
+ success: function(resp){
+ resp[0].id.should.eql "123"
+ resp[0].rev.should.be_a String
+ resp[0].rev.length.should.be_at_least 30
+ resp[1].id.should.eql "456"
+ resp[1].rev.should.be_a String
+ resp[1].rev.length.should.be_at_least 30
+ resp[2].id.should.eql "789"
+ resp[2].rev.should.be_a String
+ resp[2].rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should record the revision in the deleted documents'
+ db.bulkRemove({"docs": docs}, {
+ success: function(resp){
+ db.openDoc("123", {
+ rev: resp[0].rev,
+ success: function(resp2){
+ resp2._rev.should.eql resp[0].rev
+ resp2._id.should.eql resp[0].id
+ resp2._deleted.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.bulkRemove({docs: ["asdf"]});
+ alert_msg.should.match /The documents could not be deleted/
+ end
+ end
+
+ describe 'copyDoc'
+ before_each
+ doc = {"Name" : "Sharon Agathon", "Callsign" : "Athena", "_id" : "123"};
+ db.saveDoc(doc);
+ end
+
+ it 'should result in another document with same data and new id'
+ db.copyDoc("123", {
+ success: function(resp){
+ resp.id.should.eql "456"
+ resp.rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, {
+ headers: {"Destination":"456"}
+ });
+
+ db.openDoc("456", {
+ success: function(resp){
+ resp.Name.should.eql "Sharon Agathon"
+ resp.Callsign.should.eql "Athena"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should throw an error when trying to overwrite a document without providing a revision'
+ doc2 = {"Name" : "Louanne Katraine", "Callsign" : "Kat", "_id" : "456"};
+ db.saveDoc(doc2);
+
+ db.copyDoc("123", {
+ error: function(status, error, reason){
+ status.should.eql 409
+ error.should.eql "conflict"
+ reason.should.eql "Document update conflict."
+ },
+ success: function(resp){successCallback(resp)}
+ }, {
+ headers: {"Destination":"456"}
+ });
+ end
+
+ it 'should overwrite a document with the correct revision'
+ doc2 = {"Name" : "Louanne Katraine", "Callsign" : "Kat", "_id" : "456"};
+ var doc2_rev;
+ db.saveDoc(doc2, {
+ success: function(resp){
+ doc2_rev = resp.rev;
+ }
+ });
+
+ db.copyDoc("123", {
+ success: function(resp){
+ resp.id.should.eql "456"
+ resp.rev.length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ }, {
+ headers: {"Destination":"456?rev=" + doc2_rev}
+ });
+
+ db.openDoc("456", {
+ success: function(resp){
+ resp.Name.should.eql "Sharon Agathon"
+ resp.Callsign.should.eql "Athena"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.copyDoc("asdf", {}, {});
+ alert_msg.should.match /The document could not be copied/
+ end
+ end
+
+ describe 'query'
+ before_each
+ db.saveDoc({"Name" : "Cally Tyrol", "job" : "deckhand", "_id" : "789"});
+ db.saveDoc({"Name" : "Felix Gaeta", "job" : "officer", "_id" : "123"});
+ db.saveDoc({"Name" : "Samuel T. Anders", "job" : "pilot", "_id" : "456"});
+ map_function = "function(doc) { emit(doc._id, 1); }";
+ reduce_function = "function(key, values, rereduce) { return sum(values); }";
+ end
+
+ it 'should apply the map function'
+ db.query(map_function, null, null, {
+ success: function(resp){
+ resp.rows.should.have_length 3
+ resp.rows[0].id.should.eql "123"
+ resp.rows[0].key.should.eql "123"
+ resp.rows[0].value.should.eql 1
+ resp.rows[1].id.should.eql "456"
+ resp.rows[1].key.should.eql "456"
+ resp.rows[1].value.should.eql 1
+ resp.rows[2].id.should.eql "789"
+ resp.rows[2].key.should.eql "789"
+ resp.rows[2].value.should.eql 1
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should apply the reduce function'
+ db.query(map_function, reduce_function, null, {
+ success: function(resp){
+ resp.rows.should.have_length 1
+ resp.rows[0].key.should.be_null
+ resp.rows[0].value.should_eql 3
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options'
+ db.query(map_function, null, null, {
+ "startkey": "456",
+ success: function(resp){
+ resp.rows.should.have_length 2
+ resp.rows[0].id.should.eql "456"
+ resp.rows[0].key.should.eql "456"
+ resp.rows[0].value.should.eql 1
+ resp.rows[1].id.should.eql "789"
+ resp.rows[1].key.should.eql "789"
+ resp.rows[1].value.should.eql 1
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the keys'
+ //shouldn't this better work? TODO: implement in jquery.couch.js
+ console.log("shouldn't this better work? TODO: implement in jquery.couch.js")
+ db.query(map_function, null, null, {
+ "keys": ["456", "123"],
+ success: function(resp){
+ resp.rows.should.have_length 2
+ resp.rows[0].id.should.eql "456"
+ resp.rows[0].key.should.eql "456"
+ resp.rows[0].value.should.eql 1
+ resp.rows[1].id.should.eql "123"
+ resp.rows[1].key.should.eql "123"
+ resp.rows[1].value.should.eql 1
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options and the keys'
+ //shouldn't this better work? TODO: implement in jquery.couch.js
+ console.log("shouldn't this better work? TODO: implement in jquery.couch.js")
+ db.query(map_function, null, null, {
+ "include_docs":"true",
+ "keys": ["456"],
+ success: function(resp){
+ resp.rows.should.have_length 1
+ resp.rows[0].id.should.eql "456"
+ resp.rows[0].key.should.eql "456"
+ resp.rows[0].value.should.eql 1
+ resp.rows[0].doc["job"].should.eql "pilot"
+ resp.rows[0].doc["_rev"].length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should apply a query in erlang also'
+ // when this test fails, read this: http://wiki.apache.org/couchdb/EnableErlangViews
+ var erlang_map = 'fun({Doc}) -> ' +
+ 'ID = proplists:get_value(<<"_id">>, Doc, null), ' +
+ 'Emit(ID, 1) ' +
+ 'end.';
+ db.query(erlang_map, null, "erlang", {
+ success: function(resp){
+ resp.rows.should.have_length 3
+ resp.rows[0].id.should.eql "123"
+ resp.rows[0].key.should.eql "123"
+ resp.rows[0].value.should.eql 1
+ resp.rows[1].id.should.eql "456"
+ resp.rows[1].key.should.eql "456"
+ resp.rows[1].value.should.eql 1
+ resp.rows[2].id.should.eql "789"
+ resp.rows[2].key.should.eql "789"
+ resp.rows[2].value.should.eql 1
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.query("asdf");
+ alert_msg.should.match /An error occurred querying the database/
+ end
+ end
+
+ describe 'view'
+ before_each
+ db.saveDoc({"Name" : "Cally Tyrol", "job" : "deckhand", "_id" : "789"});
+ db.saveDoc({"Name" : "Felix Gaeta", "job" : "officer", "_id" : "123"});
+ db.saveDoc({"Name" : "Samuel T. Anders", "job" : "pilot", "_id" : "456"});
+ view = {
+ "views" : {
+ "people" : {
+ "map" : "function(doc) { emit(doc._id, doc.Name); }"
+ }
+ },
+ "_id" : "_design/spec_db"
+ };
+ db.saveDoc(view);
+ end
+
+ it 'should apply the view'
+ db.view('spec_db/people', {
+ success: function(resp){
+ resp.rows.should.have_length 3
+ resp.rows[0].id.should.eql "123"
+ resp.rows[0].key.should.eql "123"
+ resp.rows[0].value.should.eql "Felix Gaeta"
+ resp.rows[1].id.should.eql "456"
+ resp.rows[1].key.should.eql "456"
+ resp.rows[1].value.should.eql "Samuel T. Anders"
+ resp.rows[2].id.should.eql "789"
+ resp.rows[2].key.should.eql "789"
+ resp.rows[2].value.should.eql "Cally Tyrol"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options'
+ db.view('spec_db/people', {
+ "skip":"2",
+ success: function(resp){
+ resp.rows.should.have_length 1
+ resp.rows[0].id.should.eql "789"
+ resp.rows[0].key.should.eql "789"
+ resp.rows[0].value.should.eql "Cally Tyrol"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the keys'
+ db.view('spec_db/people', {
+ "keys":["456", "123"],
+ success: function(resp){
+ resp.rows.should.have_length 2
+ resp.rows[0].id.should.eql "456"
+ resp.rows[0].key.should.eql "456"
+ resp.rows[0].value.should.eql "Samuel T. Anders"
+ resp.rows[1].id.should.eql "123"
+ resp.rows[1].key.should.eql "123"
+ resp.rows[1].value.should.eql "Felix Gaeta"
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should pass through the options and the keys'
+ db.view('spec_db/people', {
+ "include_docs":"true",
+ "keys":["456"],
+ success: function(resp){
+ resp.rows.should.have_length 1
+ resp.rows[0].id.should.eql "456"
+ resp.rows[0].key.should.eql "456"
+ resp.rows[0].value.should.eql "Samuel T. Anders"
+ resp.rows[0].doc["job"].should.eql "pilot"
+ resp.rows[0].doc["_rev"].length.should.be_at_least 30
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should throw a 404 when the view doesnt exist'
+ db.view('spec_db/non_existing_view', {
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "missing_named_view"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.view("asdf");
+ alert_msg.should.match /An error occurred accessing the view/
+ end
+ end
+
+ describe 'setDbProperty'
+ it 'should return ok true'
+ db.setDbProperty("_revs_limit", 1500, {
+ success: function(resp){
+ resp.ok.should.be_true
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should set a db property'
+ db.setDbProperty("_revs_limit", 1500);
+ db.getDbProperty("_revs_limit", {
+ success: function(resp){
+ resp.should.eql 1500
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ db.setDbProperty("_revs_limit", 1200);
+ db.getDbProperty("_revs_limit", {
+ success: function(resp){
+ resp.should.eql 1200
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.setDbProperty("asdf");
+ alert_msg.should.match /The property could not be updated/
+ end
+ end
+
+ describe 'getDbProperty'
+ it 'should get a db property'
+ db.setDbProperty("_revs_limit", 1200);
+ db.getDbProperty("_revs_limit", {
+ success: function(resp){
+ resp.should.eql 1200
+ },
+ error: function(status, error, reason){errorCallback(status, error, reason)}
+ });
+ end
+
+ it 'should throw a 404 when the property doesnt exist'
+ db.getDbProperty("_doesnt_exist", {
+ error: function(status, error, reason){
+ status.should.eql 404
+ error.should.eql "not_found"
+ reason.should.eql "missing"
+ },
+ success: function(resp){successCallback(resp)}
+ });
+ end
+
+ it 'should alert with an error message prefix'
+ db.getDbProperty("asdf");
+ alert_msg.should.match /The property could not be retrieved/
+ end
+ end
+end \ No newline at end of file
diff --git a/share/www/spec/run.html b/share/www/spec/run.html
new file mode 100644
index 00000000..3e9e943a
--- /dev/null
+++ b/share/www/spec/run.html
@@ -0,0 +1,33 @@
+<html>
+ <head>
+ <link type="text/css" rel="stylesheet" href="../script/jspec/jspec.css" />
+ <script src="../script/jquery.js"></script>
+ <script src="../script/sha1.js"></script>
+ <script src="../script/jspec/jspec.js"></script>
+ <script src="../script/jspec/jspec.jquery.js"></script>
+ <script src="../script/jspec/jspec.xhr.js"></script>
+ <script src="./custom_helpers.js"></script>
+ <script src="../script/couch.js"></script>
+ <script src="../script/jquery.couch.js"></script>
+ <script>
+ function runSuites() {
+ JSpec
+ .exec('couch_js_class_methods_spec.js')
+ .exec('couch_js_instance_methods_1_spec.js')
+ .exec('couch_js_instance_methods_2_spec.js')
+ .exec('couch_js_instance_methods_3_spec.js')
+ .exec('jquery_couch_js_class_methods_spec.js')
+ .exec('jquery_couch_js_instance_methods_1_spec.js')
+ .exec('jquery_couch_js_instance_methods_2_spec.js')
+ .exec('jquery_couch_js_instance_methods_3_spec.js')
+ .run({failuresOnly: true})
+ .report()
+ }
+ </script>
+ </head>
+ <body class="jspec" onLoad="runSuites();">
+ <div id="jspec-top"><h2 id="jspec-title">JSpec <em><script>document.write(JSpec.version)</script></em></h2></div>
+ <div id="jspec"></div>
+ <div id="jspec-bottom"></div>
+ </body>
+</html>