vows-is.js

var fluent = require("vows-fluent"),
	request = require("request"),
	should = require("should"),
	AssertionConstructor = should.Assertion,
	Assertion = AssertionConstructor.prototype,
	Trait = require("traits").Trait,
	Topic = fluent.Topic,
	Vow = fluent.Vow,
	Context = fluent.Context;

Remove should from the prototype.

delete Object.prototype.should;

Redefine should in a friendly manner.

Object.defineProperty(Object.prototype, "should", {
	get: function() {
		return this._should || new AssertionConstructor(this); 
	},
	set: function(val) {
		this._should = val;
	},
	configurable: true
});

defaults objects overwritten through config.

var defaults = {},

test whether an url is an url or has a method in it.

	test_uri = /^(GET|POST|PUT|HEAD|DELETE)(.*)/;

utility create function

var create = function _create(trait) {
	return Object.create(Object.prototype, trait)
};

Is object used in topic().is

exports.Is = {

dummy getter

	get a() {
		return this;
	},

dummy getter

	get an() {
		return this;
	},

invocation. This will invoke the previous topic with values and return it

	"invocation": function _invocation() {
		var args = arguments;
		this._parent.set(function _topic(topic) {
			return topic.apply(null, args);
		});
		return this.end();
	},

request. This sets the value of the topic to be a request function. It goes off and asks for a server from the server factory, Then generates the url to request. It makes a request to your server then uses this.callback to set the response as the topic

	"request": function _request(url) {
		this._parent.set(function _topic() {
			var cb = this.callback;
			defaults.server.factory(function _getServer(server) {
				var options = {};
				var match = url && url.match(test_uri);
				if (match) {
					options.method = match[1];
					options.uri = defaults.server.uri + match[2].substr(1);
				} else {
					options.uri = defaults.server.uri + uri	
				}
				request(options, function _handleRequest(err, res, body) {
					cb(err, res);
				});
			});
		});
		return this.end()
	},

Set the topic to be a property of the current topic

	"property": function _property(name) {
		this._parent.set(function _topic(topic) {
			return topic[name];
		});
		return this.end()
	},

end the is abstraction returning the context owning the topic.

	"end": function _end() {
		return this._parent.parent()
	}
};

Property descriptor for property is. Creates a Is, sets the parent and returns it. Typeof function check supports topic.is and topic().is The function is used to allow topic.is(value)

var propertyDescriptorIs = {
	"set": function _set() {},
	"get": function _get() {
		var i = create(Trait(exports.Is));
		if (typeof this === "function") {
			i._parent = this();
		} else {
			i._parent = this;
		}
		var f = (function _is(val) {
			this._parent.set(val);
			return this.end();
		}).bind(i);
		var keys = Object.getOwnPropertyNames(i);
		keys.forEach(function(key) {
			var pd = Object.getOwnPropertyDescriptor(i, key);
			Object.defineProperty(f, key, pd);
		});
		return f;
	}
};

Add is as a getter to the topic to support topic().is

Object.defineProperty(Topic, "is", propertyDescriptorIs);

replace topic method with a getter that returns the function make sure to bind this and add the is property to the function

var old_topic = Context.topic;
Object.defineProperty(Context, "topic", {
	"set": function _set() {},
	"get": function _get() {
		var f = old_topic.bind(this);
		Object.defineProperty(f, "is", propertyDescriptorIs);
		return f;
	}
});

It object used in vow().it

exports.It = {

Creates a should wrapper and returns it. Also sets the vow value to unwrap the should wrapper.

	get should() {
		var s = create(Trait(exports.Should));
		s._stack = [];
		s._parent = this._parent;
		return s;
	}
}

Property descriptor for property it. Creates a It, sets the parent and returns it. Typeof function check supports vow.it and vow().it

var propertyDescriptorIt = {
	"set": function _set() {},
	"get": function _get() {
		var i = create(Trait(exports.It));
		if (typeof this === "function") {
			i._parent = this();
		} else {
			i._parent = this;	
		}
		return i;
	}
};

Add is as a getter on Vow to support vow().it

Object.defineProperty(Vow, "it", propertyDescriptorIt);

Replace vow method with a getter that returns the function make sure to bind the scope and add it to the vow value

var old_vow = Context.vow
Object.defineProperty(Context, "vow", {
	"set": function _set() {},
	"get": function _get() {
		var f = old_vow.bind(this);
		Object.defineProperty(f, "it", propertyDescriptorIt);
		return f;
	}
});

Extension of the should.js Assertion object

exports.Assertion = create(Trait.override(Trait({

header. Assert it has property headers and that the header name has value val

	"header": function _header(name, val) {
		return this.property("headers").with.property(name, val);
	},

error getter. Set's the object to the error and calls ok.

	get error() {
		this.obj = this._error
		return this.ok;
	},
}), Trait(Assertion)));

a Should object which is a wrapper around should.js

exports.Should = {

transforms the stack into a nice assertion string

	"prettyPrint": function _prettyPrint() {
		var arr = this._stack.map(function _map(val) {
			var str = val.key;
			if (exports.prettyPrint[val.key]) {
				str += " " + exports.prettyPrint[val.key](val.args);
			} else if (val.args.length) {
				str += " " + val.args.join(" ");
			}
			return str;
		});
		arr.unshift("should");
		return arr.join(" ");
	},

end the abstraction. This creates a should.js assertion and unravels the stack running every assertion. it then returns context that the vow belongs to.

	"end": function _end() {
		var s = create(Trait(exports.Assertion));
		s.obj = this._obj;
		s._error = this._error;
		this._stack.forEach(function _each(val) {
			if (val.type === 'get') {
				s = s[val.key];
			} else {
				s = s[val.key].apply(s, val.args);
			}
		});
		return this._parent.parent();
	}
};

A stack of prettyPrint methods to make the assertions look pretty

exports.prettyPrint = {
	"header": function _property(args) {
		return args[0] + " with value " + args[1];
	}
};

For each of the methods that can be called on a context add them to should. This tells the Should wrappers to end itself and return back to the context.

["vow", "context", "parent", "batch", "suite", "partial"].forEach(function _each(key) {
	exports.Should[key] = function _parent() {
		var parent = this._parent.parent();
		if (!this._parent._name) {
			this._parent.name(this.prettyPrint());
		}
		var that = this;
		this._parent.set(function _set(err, value) {
			that._obj = value;
			that._error = err;
			that.end();
		});
		return parent[key].apply(parent, arguments);
	};
});

Replace the vow method of Should with a getter so chaining doesn't break

var should_vow = exports.Should.vow
Object.defineProperty(exports.Should, "vow", {
	"set": function _set() {},
	"get": function _get() {
		var f = should_vow.bind(this);
		Object.defineProperty(f, "it", propertyDescriptorIt);
		return f;
	}
});

For each of the methods of exports.Assersion add a wrapper to Should. The wrapper simply saves the command on a stack. The stack is unravelled when the should wrapper ends.

Object.keys(exports.Assertion).forEach(function _each(key) {
	if (!exports.Should.hasOwnProperty(key)) {
		var descriptor = Object.getOwnPropertyDescriptor(exports.Assertion, key);
		var name = descriptor.get ? "get" : "value";
		descriptor[name] = function _descriptor() {
			this._stack.push({
				"type": name,
				"key": key,
				"args": Array.prototype.slice.call(arguments)
			});
			return this;
		}
		Object.defineProperty(exports.Should, key, descriptor);
	}
});

Config method sets defaults

exports.config = function _init(obj) {
	Object.keys(obj).forEach(function _each(key) {
		defaults[key] = obj[key];
	});
	if (defaults.server.defaults) {
		request = request.defaults(defaults.server.defaults);
	}
	return this;
};

partial method that runs a predefined partial function on the context

Context.partial = function _partial(name, data) {
	var p = exports._partial[name];
	return p(this, data);
};

Store hashes of partials

exports._partial = {};

Store a partial by name

exports.partial = function _partial(name, f) {
	this._partial[name]	= f;
	return this;
};

Expose methods of vows-fluent on exports.

Object.keys(fluent).forEach(function _each(key) {
	exports[key] = fluent[key];
});

Expose the custom vows-is reporter

exports.reporter = require("./reporter.js");

Expose vows console

exports.console = require("../lib/console.js");

End function will various clean up functions default.server.kill is your clean up function to kill the server

exports.end = function _end() {
	if (defaults.server && 
		defaults.server.kill && 
		typeof defaults.server.kill === "function"
	) {
		defaults.server.kill();
	}
};

expose should object from should.js

exports.should = should;