summaryrefslogtreecommitdiff
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
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
-rw-r--r--README17
-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
17 files changed, 5081 insertions, 36 deletions
diff --git a/README b/README
index f75a6c4b..540226d3 100644
--- a/README
+++ b/README
@@ -39,6 +39,23 @@ The mailing lists provide a wealth of support and knowledge for you to tap into.
Feel free to drop by with your questions or discussion. See the official CouchDB
website for more information about our community resources.
+
+Running the Testsuite
+---------------------
+
+Run the testsuite for couch.js and jquery.couch.js by browsing to this site: http://127.0.0.1:5984/_utils/spec/run.html
+It should work in at least Firefox >= 3.6 and Safari >= 4.0.4.
+
+Read more about JSpec here: http://jspec.info/
+
+Trouble shooting
+~~~~~~~~~~~~~~~~
+
+ * When you change the specs, but your changes have no effect, manually reload the changed spec file in the browser.
+
+ * When the spec that tests erlang views fails, make sure you have enabled erlang views as described here: <http://wiki.apache.org/couchdb/EnableErlangViews>
+
+
Cryptographic Software Notice
-----------------------------
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>