<commit_before>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 * Views controller using deepjs tools and methods
 */
if (typeof define !== 'function') {
	var define = require('amdefine')(module);
}
define(["require", "deepjs/deep", "deepjs/lib/errors", "./directives"], function(require, deep, errors) {
"use strict";

	/**
	 * view refresh sequence : arg = route params
	 * 	if(!initialised)
	 * 		init()
	 * 	then
	 * 		load(params);
	 * 			deepLoad what, how, where
	 * 		render(loaded);
	 * 			how(context);
	 * 		 	where(context);
	 * 		  	enhance(context);
	 * 		   	done(context);
	 *
	 * View remove sequence :
	 * 		remove each subs
	 * 		remove each dp-controlled
	 * 		remove html node
	 * 		clean();
	 */

	deep.errors.View = function(msg, report, fileName, lineNum){
		if(typeof msg === 'object')
			report = msg;
		if(!msg)
			msg = "UIError";
		return errors.Error(1500, msg, report, fileName, lineNum);
	};

	deep.View = deep.Classes(function(layer) {
		if(layer)
			deep.aup(layer, this);
	}, {
		_deep_view_: true,
		how: undefined,
		done: undefined,
		fail: function(e) {
			deep.utils.dumpError(e);
			if (!this.where)
				return e;
			if (typeof this.where === 'string')
				return deep.nodes(this.where).done(function(s) {
					if (typeof s === 'function')
						s(e.toString());
					else
						$(s).replaceWith(e.toString());
					return e;
				});
			else if (typeof this.where === 'function')
				this.where(e.toString());
			else if (typeof jQuery !== 'undefined' && this.where instanceof jQuery) // WARNING : cheerio case not managed
				$(this.where).html(e.toString());
		},
		addSubControlled:function(ctrl){
			var label = "view" + Date.now().valueOf();
			this._deep_controlled_ = this._deep_controlled_ || {};
			this._deep_controlled_[label] = ctrl;
		},
		remove: function() {
			//console.log("VIEW REMOVE : ", this.placed);
			if (this.subs)
				for (var i in this.subs)
					this.subs[i].remove();
			if (this._deep_controlled_) {
				for (var i in this._deep_controlled_)
					this._deep_controlled_[i].remove();
				this._deep_controlled_ = null;
			}
			if (this.placed)
			{
				// console.log("remove node : ", this, this.placed())
				deep.$(this.placed()).remove();
			}
			if (this.clean)
				this.clean();
			this.initialised = false;
			this.placed = null;
		},
		load: function(opt) {
			opt = opt || {};
			var params = opt.params;
			var config = this.config || {};
			//if (params && Object.keys(params).length === 0)
			//	params = null;
			if (config.scope && config.scope != "both")
				if (!deep.isBrowser) {
					if (config.scope != "server")
						return;
				} else
			if (config.scope != "browser")
				return;
			var self = this;
			if (this.init && (deep.Promise.context.concurrency || !this.initialised))
				return deep.when(this.init())
					.done(function(success) {
						self.initialised = true;
						return self.load({
							params: params
						});
					});
			if (this.condition === false)
				return null;
			if (this.condition)
				if (typeof this.condition === "function" && !this.condition.apply(params))
					return null;
			var promises = [];
			if (this.what)
				promises.push(deep(this).query("./what").deepLoad(params, false));
			else
				promises.push(null);

			if (typeof this.how === 'string')
				promises.push(deep(this).query("./how").deepLoad(params, false, true));
			else
				promises.push(this.how);

			if (this.where)
				if (typeof this.where === 'string')
					promises.push(deep(this).query("./where").deepLoad(params, false, true));
				else
					promises.push(this.where);

				// console.log("launch deep load");
			return deep.all(promises)
				.done(function(res) {
					//console.log("view deep load : res  : ", res);
					var result = {
						_deep_render_node_: true,
						what: res[0],
						how: res[1],
						where: res[2],
						params: params
					};
					// console.log("view deep load done : ", self.how);
					return result;
				})
				.fail(function(e) {
					if (typeof self.fail === 'function')
						return self.fail(e) || e;
					return e;
				});
		},
		refresh: function(arg /* params, loaded, routeMatch */ ) {
			var config = this.config || {};
			if (config.scope && config.scope != "both")
				if (deep.isBrowser) {
					if (config.scope != "browser")
						return;
				} else
			if (config.scope != "server")
				return;
			// console.log("view refresh : params : ", params, " - loaded : ", loaded);
			var self = this;
			if (this.init && (deep.Promise.context.concurrency || !this.initialised))
				return deep.when(this.init())
					.done(function(success) {
						self.initialised = true;
						return self.refresh(arg);
					});
			var loaded, params, routeMatch;
			if (arg)
				if (arg._deep_render_node_) {
					loaded = arg;
					params = loaded.params;
				} else
			if (arg._deep_matched_node_) {
				loaded = arg.loaded;
				params = arg.output;

				routeMatch = arg;
			} else
				params = arg;
			params = params || this.params;
			if (params && deep.isBrowser)
				this.params = params;

			var render = function(loaded) {
				// console.log("view render : ", loaded)
				if (!loaded)
					return;
				if (self._deep_controlled_) {
					for (var i in self._deep_controlled_)
						self._deep_controlled_[i].remove();
					self._deep_controlled_ = null;
				}
				var what = null,
					how = null,
					where = null,
					rendered = null,
					placed = null;
				if (loaded.what)
					what = loaded.what;
				if (loaded.how)
					how = loaded.how;
				if (loaded.where)
					where = loaded.where;
				what = (what && what._deep_query_node_) ? what.value : what;
				how = (how && how._deep_query_node_) ? how.value : how;
				where = (where && where._deep_query_node_) ? where.value : where;

				if (what && what._deep_ocm_)
					what = what();
				if (how && how._deep_ocm_)
					how = how();
				if (where && where._deep_ocm_)
					where = where();

				loaded.what = what;
				loaded.how = how;
				loaded.where = where;
				loaded.params = params;

				placed = self.placed ? self.placed() : null;

				loaded.placed = placed;
				loaded.ctrl = self;
				// console.log("render with : how : ", how, what)
				try {
					// console.log("XHAT : ", what);
					if (routeMatch) {
						loaded.route = routeMatch;
					}
					if (typeof how === 'function')
						rendered = loaded.rendered = how.call(self, loaded);
					else
						rendered = loaded.rendered = how;
					if (where && typeof where === 'function')
						placed = loaded.placed = where.call(self, loaded.rendered, loaded.placed);
					else if (typeof jQuery !== 'undefined' && where instanceof jQuery)
						placed = loaded.placed = where;
					else if (typeof where === 'string' && deep.Promise.context.$)
						placed = loaded.placed = deep.Promise.context.$(where);
					/*else
						placed = loaded.placed = null;*/
				} catch (e) {
					// console.log("error while rendering :  ",e);
					if (typeof self.fail === 'function')
						return self.fail(e) || e;
					return e;
				}

				if (placed && !deep.Promise.context.concurrency)
					self.placed = function() {
						return placed;
					};

				var promise = null;
				if (config.enhance !== false && placed && deep.Promise.context.$) {
					//console.log("view placed : placed = ", placed);
					if (placed._deep_html_merged_) {
						var subprom = [];
						for (var i in placed) {
							if (i === '_deep_html_merged_')
								continue;
							subprom.push(deep.ui.enhance(self, placed[i], loaded))
						}
						promise = deep.all(subprom);
					} else
						promise = deep.ui.enhance(self, placed, loaded);
				}

				//console.log("rendered : ", loaded);
				if (promise)
					return promise
						.done(function() {
							if(config.relink !== false && deep.isBrowser && placed && deep.ui.relink)
								deep.ui.relink(placed);
							if (typeof self.done === "function") {
								var done = self.done;
								if (done._deep_ocm_)
									done = done();
								return done.call(self, loaded) || loaded;
							}
							return loaded;
						})
						.fail(function(e) {
							if (typeof self.fail === 'function')
								return self.fail(e) || e;
							return e;
						});

				//if(config.relink == false)
				//	console.log("skip relink on : ", self);
				if(config.relink !== false && deep.isBrowser && placed && deep.ui.relink)
					deep.ui.relink(placed);
				if (typeof self.done === "function") {
					var done = self.done;
					if (done._deep_ocm_)
						done = done();
					return done.call(self, loaded) || loaded;
				}
				return loaded;
			};
			var p;
			if (loaded)
				p = deep.when(render(loaded))
					.fail(function(e) {
						if (typeof self.fail === 'function') {
							var fail = self.fail;
							if (fail._deep_ocm_)
								fail = fail();
							return fail.call(self, e) || e;
						}
					});
			else
				p = deep.when(this.load({
					params: params
				}))
					.done(function() {
						this.done(render)
							.fail(function(e) {
								if (typeof self.fail === 'function') {
									var fail = self.fail;
									if (fail._deep_ocm_)
										fail = fail();
									return fail.call(self, e) || e;
								}
							});
					});
			return p;
		}
	});
	deep.View = function(layer) {
		return new deep.ui.View(layer);
	};
	deep.coreUnits = deep.coreUnits || [];
	deep.coreUnits.push("js::deepjs/units/views");
	return deep.ui.View;
});<commit_msg>clean deep view unit test inclusion<commit_after>/**
 * @author Gilles Coomans <gilles.coomans@gmail.com>
 * Views controller using deepjs tools and methods
 */
if (typeof define !== 'function') {
	var define = require('amdefine')(module);
}
define(["require", "deepjs/deep", "deepjs/lib/errors", "./directives"], function(require, deep, errors) {
"use strict";

	/**
	 * view refresh sequence : arg = route params
	 * 	if(!initialised)
	 * 		init()
	 * 	then
	 * 		load(params);
	 * 			deepLoad what, how, where
	 * 		render(loaded);
	 * 			how(context);
	 * 		 	where(context);
	 * 		  	enhance(context);
	 * 		   	done(context);
	 *
	 * View remove sequence :
	 * 		remove each subs
	 * 		remove each dp-controlled
	 * 		remove html node
	 * 		clean();
	 */

	deep.errors.View = function(msg, report, fileName, lineNum){
		if(typeof msg === 'object')
			report = msg;
		if(!msg)
			msg = "UIError";
		return errors.Error(1500, msg, report, fileName, lineNum);
	};

	deep.View = deep.Classes(function(layer) {
		if(layer)
			deep.aup(layer, this);
	}, {
		_deep_view_: true,
		how: undefined,
		done: undefined,
		fail: function(e) {
			deep.utils.dumpError(e);
			if (!this.where)
				return e;
			if (typeof this.where === 'string')
				return deep.nodes(this.where).done(function(s) {
					if (typeof s === 'function')
						s(e.toString());
					else
						$(s).replaceWith(e.toString());
					return e;
				});
			else if (typeof this.where === 'function')
				this.where(e.toString());
			else if (typeof jQuery !== 'undefined' && this.where instanceof jQuery) // WARNING : cheerio case not managed
				$(this.where).html(e.toString());
		},
		addSubControlled:function(ctrl){
			var label = "view" + Date.now().valueOf();
			this._deep_controlled_ = this._deep_controlled_ || {};
			this._deep_controlled_[label] = ctrl;
		},
		remove: function() {
			//console.log("VIEW REMOVE : ", this.placed);
			if (this.subs)
				for (var i in this.subs)
					this.subs[i].remove();
			if (this._deep_controlled_) {
				for (var i in this._deep_controlled_)
					this._deep_controlled_[i].remove();
				this._deep_controlled_ = null;
			}
			if (this.placed)
			{
				// console.log("remove node : ", this, this.placed())
				deep.$(this.placed()).remove();
			}
			if (this.clean)
				this.clean();
			this.initialised = false;
			this.placed = null;
		},
		load: function(opt) {
			opt = opt || {};
			var params = opt.params;
			var config = this.config || {};
			//if (params && Object.keys(params).length === 0)
			//	params = null;
			if (config.scope && config.scope != "both")
				if (!deep.isBrowser) {
					if (config.scope != "server")
						return;
				} else
			if (config.scope != "browser")
				return;
			var self = this;
			if (this.init && (deep.Promise.context.concurrency || !this.initialised))
				return deep.when(this.init())
					.done(function(success) {
						self.initialised = true;
						return self.load({
							params: params
						});
					});
			if (this.condition === false)
				return null;
			if (this.condition)
				if (typeof this.condition === "function" && !this.condition.apply(params))
					return null;
			var promises = [];
			if (this.what)
				promises.push(deep(this).query("./what").deepLoad(params, false));
			else
				promises.push(null);

			if (typeof this.how === 'string')
				promises.push(deep(this).query("./how").deepLoad(params, false, true));
			else
				promises.push(this.how);

			if (this.where)
				if (typeof this.where === 'string')
					promises.push(deep(this).query("./where").deepLoad(params, false, true));
				else
					promises.push(this.where);

				// console.log("launch deep load");
			return deep.all(promises)
				.done(function(res) {
					//console.log("view deep load : res  : ", res);
					var result = {
						_deep_render_node_: true,
						what: res[0],
						how: res[1],
						where: res[2],
						params: params
					};
					// console.log("view deep load done : ", self.how);
					return result;
				})
				.fail(function(e) {
					if (typeof self.fail === 'function')
						return self.fail(e) || e;
					return e;
				});
		},
		refresh: function(arg /* params, loaded, routeMatch */ ) {
			var config = this.config || {};
			if (config.scope && config.scope != "both")
				if (deep.isBrowser) {
					if (config.scope != "browser")
						return;
				} else
			if (config.scope != "server")
				return;
			// console.log("view refresh : params : ", params, " - loaded : ", loaded);
			var self = this;
			if (this.init && (deep.Promise.context.concurrency || !this.initialised))
				return deep.when(this.init())
					.done(function(success) {
						self.initialised = true;
						return self.refresh(arg);
					});
			var loaded, params, routeMatch;
			if (arg)
				if (arg._deep_render_node_) {
					loaded = arg;
					params = loaded.params;
				} else
			if (arg._deep_matched_node_) {
				loaded = arg.loaded;
				params = arg.output;

				routeMatch = arg;
			} else
				params = arg;
			params = params || this.params;
			if (params && deep.isBrowser)
				this.params = params;

			var render = function(loaded) {
				// console.log("view render : ", loaded)
				if (!loaded)
					return;
				if (self._deep_controlled_) {
					for (var i in self._deep_controlled_)
						self._deep_controlled_[i].remove();
					self._deep_controlled_ = null;
				}
				var what = null,
					how = null,
					where = null,
					rendered = null,
					placed = null;
				if (loaded.what)
					what = loaded.what;
				if (loaded.how)
					how = loaded.how;
				if (loaded.where)
					where = loaded.where;
				what = (what && what._deep_query_node_) ? what.value : what;
				how = (how && how._deep_query_node_) ? how.value : how;
				where = (where && where._deep_query_node_) ? where.value : where;

				if (what && what._deep_ocm_)
					what = what();
				if (how && how._deep_ocm_)
					how = how();
				if (where && where._deep_ocm_)
					where = where();

				loaded.what = what;
				loaded.how = how;
				loaded.where = where;
				loaded.params = params;

				placed = self.placed ? self.placed() : null;

				loaded.placed = placed;
				loaded.ctrl = self;
				// console.log("render with : how : ", how, what)
				try {
					// console.log("XHAT : ", what);
					if (routeMatch) {
						loaded.route = routeMatch;
					}
					if (typeof how === 'function')
						rendered = loaded.rendered = how.call(self, loaded);
					else
						rendered = loaded.rendered = how;
					if (where && typeof where === 'function')
						placed = loaded.placed = where.call(self, loaded.rendered, loaded.placed);
					else if (typeof jQuery !== 'undefined' && where instanceof jQuery)
						placed = loaded.placed = where;
					else if (typeof where === 'string' && deep.Promise.context.$)
						placed = loaded.placed = deep.Promise.context.$(where);
					/*else
						placed = loaded.placed = null;*/
				} catch (e) {
					// console.log("error while rendering :  ",e);
					if (typeof self.fail === 'function')
						return self.fail(e) || e;
					return e;
				}

				if (placed && !deep.Promise.context.concurrency)
					self.placed = function() {
						return placed;
					};

				var promise = null;
				if (config.enhance !== false && placed && deep.Promise.context.$) {
					//console.log("view placed : placed = ", placed);
					if (placed._deep_html_merged_) {
						var subprom = [];
						for (var i in placed) {
							if (i === '_deep_html_merged_')
								continue;
							subprom.push(deep.ui.enhance(self, placed[i], loaded))
						}
						promise = deep.all(subprom);
					} else
						promise = deep.ui.enhance(self, placed, loaded);
				}

				//console.log("rendered : ", loaded);
				if (promise)
					return promise
						.done(function() {
							if(config.relink !== false && deep.isBrowser && placed && deep.ui.relink)
								deep.ui.relink(placed);
							if (typeof self.done === "function") {
								var done = self.done;
								if (done._deep_ocm_)
									done = done();
								return done.call(self, loaded) || loaded;
							}
							return loaded;
						})
						.fail(function(e) {
							if (typeof self.fail === 'function')
								return self.fail(e) || e;
							return e;
						});

				//if(config.relink == false)
				//	console.log("skip relink on : ", self);
				if(config.relink !== false && deep.isBrowser && placed && deep.ui.relink)
					deep.ui.relink(placed);
				if (typeof self.done === "function") {
					var done = self.done;
					if (done._deep_ocm_)
						done = done();
					return done.call(self, loaded) || loaded;
				}
				return loaded;
			};
			var p;
			if (loaded)
				p = deep.when(render(loaded))
					.fail(function(e) {
						if (typeof self.fail === 'function') {
							var fail = self.fail;
							if (fail._deep_ocm_)
								fail = fail();
							return fail.call(self, e) || e;
						}
					});
			else
				p = deep.when(this.load({
					params: params
				}))
					.done(function() {
						this.done(render)
							.fail(function(e) {
								if (typeof self.fail === 'function') {
									var fail = self.fail;
									if (fail._deep_ocm_)
										fail = fail();
									return fail.call(self, e) || e;
								}
							});
					});
			return p;
		}
	});
	deep.View = function(layer) {
		return new deep.ui.View(layer);
	};
	deep.coreUnits = deep.coreUnits || [];
	deep.coreUnits.push("js::deep-views/units/views");
	return deep.View;
});<|endoftext|>


<commit_before>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	keystone = require('../'),
	async = require('async'),
	utils = require('keystone-utils');

/**
 * View Constructor
 * =================
 *
 * Helper to simplify view logic in a Keystone application
 *
 * @api public
 */

function View(req, res) {

	if (!req || req.constructor.name != 'IncomingMessage') {
		throw new Error('Keystone.View Error: Express request object is required.');
	}

	if (!res || res.constructor.name != 'ServerResponse') {
		throw new Error('Keystone.View Error: Express response object is required.');
	}

	this.req = req;
	this.res = res;

	this.initQueue = [];	// executed first in series
	this.actionQueue = [];	// executed second in parallel, if optional conditions are met
	this.queryQueue = [];	// executed third in parallel
	this.renderQueue = [];	// executed fourth in parallel

}

module.exports = exports = View;

/**
 * Adds a method (or array of methods to be executed in parallel
 * to the `init` or `action` queue.
 *
 * @api public
 */

View.prototype.on = function(on) {

	var req = this.req,
		callback = arguments[1],
		values;

	if ('function' == typeof on) {
		if (on()) {
			this.actionQueue.push(callback);
		}
	} else if (utils.isObject(on)) {
		var check = function(value, path) {

			var ctx = req,
				parts = path.split('.');

			for (var i = 0; i < parts.length - 1; i++) {
				if (!ctx[parts[i]]) {
					return false;
				}
				ctx = ctx[parts[i]];
			}

			return (ctx[parts[i]] == value);

		}
		if (_.every(on, check)) {
			this.actionQueue.push(callback);
		}
	} else if (on == 'post' || on == 'get') {

		if (req.method != on.toUpperCase()) {
			return;
		}

		if (utils.isObject(callback)) {
			values = callback;
			callback = arguments[2];
		}

		var ctx = (on == 'post') ? req.body : req.query;

		if (_.every(values || {}, function(value, path) {
			return (ctx[path] == value);
		})) {
			this.actionQueue.push(callback);
		}

	} else if (on == 'init') {
		this.initQueue.push(callback);
	} else if (on == 'render') {
		this.renderQueue.push(callback);
	}

	return this;

}

/**
 * Queues a mongoose query for execution before the view is rendered.
 * The results of the query are set in `locals[key]`.
 *
 * Keys can be nested paths, containing objects will be created as required.
 *
 * The third argument `then` can be a method to call after the query is completed
 * like function(err, results, callback), or a `populatedRelated` definition
 * (string or array).
 *
 * @api public
 */

View.prototype.query = function(key, query, then) {

	var locals = this.res.locals,
		parts = key.split('.'),
		key = parts.pop();

	for (var i = 0; i < parts.length; i++) {
		if (!locals[parts[i]]) {
			locals[parts[i]] = {};
		}
		locals = locals[parts[i]];
	}

	this.queryQueue.push(function(next) {
		query.exec(function(err, results) {
			locals[key] = results;
			if ('function' == typeof then) {
				then(err, results, next);
			} else if (!err && then) {
				keystone.populateRelated(results, then, next);
			} else {
				next(err);
			}
		});
	});
}

/**
 * Executes the current queue of init and action methods in series, and
 * then executes the callback. If callback is a string, it is provided
 * to `res.render`.
 *
 * It is expected that *most* init stacks require processing in series,
 * but it is safe to execute actions in parallel.
 *
 * If there are several init methods that should be run in parallel, queue
 * them as an array, e.g. `view.on('init', [first, second])`.
 *
 * @api public
 */

View.prototype.render = function(callback, locals) {

	var req = this.req,
		res = this.res;

	if ('string' == typeof callback) {
		var viewPath = callback;
		callback = (function() {
			if ('function' == typeof locals) {
				locals = locals();
			}
			this.res.render(viewPath, locals);
		}).bind(this);
	}

	// Add actions, queries & renderQueue to the end of the initQueue
	this.initQueue.push(this.actionQueue);
	this.initQueue.push(this.queryQueue);

	var preRenderQueue = [];

	// Add Keystone's global pre('render') queue
	keystone._pre.render.forEach(function(fn) {
		preRenderQueue.push(function(next) {
			fn(req, res, next);
		});
	});

	this.initQueue.push(preRenderQueue);
	this.initQueue.push(this.renderQueue);

	async.eachSeries(this.initQueue, function(i, next) {
		if (Array.isArray(i)) {
			// process nested arrays in parallel
			async.parallel(i, next);
		} else if ('function' == typeof i) {
			// process single methods in series
			i(next);
		} else {
			throw new Error('Keystone.View.render() Error: events must be functions.');
		}
	}, function(err) {
		// assume error handling is individually implemented in each queue fn, and bail.
		if (!err) {
			callback();
		}
	});

}

<commit_msg>Making View.on more intelligent about GET and POST parameter detection, fixes #64<commit_after>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	keystone = require('../'),
	async = require('async'),
	utils = require('keystone-utils');

/**
 * View Constructor
 * =================
 *
 * Helper to simplify view logic in a Keystone application
 *
 * @api public
 */

function View(req, res) {

	if (!req || req.constructor.name != 'IncomingMessage') {
		throw new Error('Keystone.View Error: Express request object is required.');
	}

	if (!res || res.constructor.name != 'ServerResponse') {
		throw new Error('Keystone.View Error: Express response object is required.');
	}

	this.req = req;
	this.res = res;

	this.initQueue = [];	// executed first in series
	this.actionQueue = [];	// executed second in parallel, if optional conditions are met
	this.queryQueue = [];	// executed third in parallel
	this.renderQueue = [];	// executed fourth in parallel

}

module.exports = exports = View;

/**
 * Adds a method (or array of methods) to be executed in parallel
 * to the `init`, `action` or `render` queue.
 *
 * @api public
 */

View.prototype.on = function(on) {

	var req = this.req,
		callback = arguments[1],
		values;

	if ('function' == typeof on) {

		if (on()) {
			this.actionQueue.push(callback);
		}

	} else if (utils.isObject(on)) {

		var check = function(value, path) {

			var ctx = req,
				parts = path.split('.');

			for (var i = 0; i < parts.length - 1; i++) {
				if (!ctx[parts[i]]) {
					return false;
				}
				ctx = ctx[parts[i]];
			}

			return (value === true && path in ctx) ? true : (ctx[path] == value);

		}

		if (_.every(on, check)) {
			this.actionQueue.push(callback);
		}

	} else if (on == 'post' || on == 'get') {

		if (req.method != on.toUpperCase()) {
			return;
		}

		if (arguments.length == 3) {

			if (utils.isString(callback)) {
				values = {};
				values[callback] = true;
			} else {
				values = callback;
			}

			callback = arguments[2];

			var ctx = (on == 'post') ? req.body : req.query;

			if (_.every(values || {}, function(value, path) {
				return (value === true && path in ctx) ? true : (ctx[path] == value);
			})) {
				this.actionQueue.push(callback);
			}

		} else {
			this.actionQueue.push(callback);
		}

	} else if (on == 'init') {
		this.initQueue.push(callback);
	} else if (on == 'render') {
		this.renderQueue.push(callback);
	}

	return this;

}

/**
 * Queues a mongoose query for execution before the view is rendered.
 * The results of the query are set in `locals[key]`.
 *
 * Keys can be nested paths, containing objects will be created as required.
 *
 * The third argument `then` can be a method to call after the query is completed
 * like function(err, results, callback), or a `populatedRelated` definition
 * (string or array).
 *
 * @api public
 */

View.prototype.query = function(key, query, then) {

	var locals = this.res.locals,
		parts = key.split('.'),
		key = parts.pop();

	for (var i = 0; i < parts.length; i++) {
		if (!locals[parts[i]]) {
			locals[parts[i]] = {};
		}
		locals = locals[parts[i]];
	}

	this.queryQueue.push(function(next) {
		query.exec(function(err, results) {
			locals[key] = results;
			if ('function' == typeof then) {
				then(err, results, next);
			} else if (!err && then) {
				keystone.populateRelated(results, then, next);
			} else {
				next(err);
			}
		});
	});
}

/**
 * Executes the current queue of init and action methods in series, and
 * then executes the callback. If callback is a string, it is provided
 * to `res.render`.
 *
 * It is expected that *most* init stacks require processing in series,
 * but it is safe to execute actions in parallel.
 *
 * If there are several init methods that should be run in parallel, queue
 * them as an array, e.g. `view.on('init', [first, second])`.
 *
 * @api public
 */

View.prototype.render = function(callback, locals) {

	var req = this.req,
		res = this.res;

	if ('string' == typeof callback) {
		var viewPath = callback;
		callback = (function() {
			if ('function' == typeof locals) {
				locals = locals();
			}
			this.res.render(viewPath, locals);
		}).bind(this);
	}

	// Add actions, queries & renderQueue to the end of the initQueue
	this.initQueue.push(this.actionQueue);
	this.initQueue.push(this.queryQueue);

	var preRenderQueue = [];

	// Add Keystone's global pre('render') queue
	keystone._pre.render.forEach(function(fn) {
		preRenderQueue.push(function(next) {
			fn(req, res, next);
		});
	});

	this.initQueue.push(preRenderQueue);
	this.initQueue.push(this.renderQueue);

	async.eachSeries(this.initQueue, function(i, next) {
		if (Array.isArray(i)) {
			// process nested arrays in parallel
			async.parallel(i, next);
		} else if ('function' == typeof i) {
			// process single methods in series
			i(next);
		} else {
			throw new Error('Keystone.View.render() Error: events must be functions.');
		}
	}, function(err) {
		// assume error handling is individually implemented in each queue fn, and bail.
		if (!err) {
			callback();
		}
	});

}

<|endoftext|>


<commit_before>/* Base view */

var dust = require('dustjs-linkedin'),
	Promise = require('./util/promise');

/* Extract wrapping element, attributes and contents */

    tagPattern = /<(\w+)[^>]*>/,
    attrPattern = /\s+(\S+)\s*=\s*("[^"]*"|'[^']*')/g;

function parseElement(html, callback) {
	var m = html.match(tagPattern);    // match first start tag

	if (m) {
		var tag = m[0], tagName = m[1],
			endTag = '</' + tagName + '>',
			endIndex = html.lastIndexOf('</');

		// check that the last end matches the start tag
		// and that there is no content after it

		if (html.slice(endIndex).replace(/\s+/g, '') == endTag)
			return callback(html.slice(m.index + tag.length, endIndex), tag, endTag, tagName);
	}

	return callback(html);
}

function attributes(tag) {    // extract attributes from tag string
	var attrs = {}, m;

	while (m = attrPattern.exec(tag))
		attrs[m[1]] = m[2].slice(1, -1);

	return attrs;
}

function updateElement(html, tag, endTag, tagName) {
	if (tag && !this.rendered) {
		if (this.$el.is(tagName)) {
			this.$el.attr(attributes(tag));
		} else
			this.setElement(tag + endTag);
	}
	this.el.innerHTML = html;
	this.rendered = true;
	if (this.attach) this.attach();
}

module.exports = Backbone.View.extend({
	// Promise factory function using view as the context with callbacks
	Promise: function(resolver) {
		return Promise(resolver, this);
	},

	constructor: function(options) {
		_.extend(this, _.pick(options, 'template', 'data'));

		Backbone.View.apply(this, arguments);

		this.rendered = (this.el && this.el.firstChild) != null;
		if (!this.rendered) this.render();
		else if (this.attach) this.attach();

		// this.ready(callbacks)

		this.ready = this.ready || $.Callbacks('once memory').fireWith(this).add;

	},

	convertElements: function() {
		var view = this;
		_.each(this.elements, function(element, key) {
			if(/^\$/.test(key)) view.elements[key.slice(1)] = element[0];
		});
	},

	render: function() {
		var data = _.result(this, 'data');

		if (this.model)
			data = _.defaults(this.model.toJSON(), data);
		else if(this.collection)
			data = _.defaults(this.collection.toJSON(), data);

		this.renderTemplate(data).ready(arguments);

		// TODO attach
		return this;
	},

	// Render Dust template and update element, using the fx queue

	renderTemplate: function(data) {

		var templateName = this.template,
			rendering = this.Promise(function(resolve, reject) {
			this.$el.queue(function(next, hooks) {
				// hooks.stop() is called if queue is stopped using $el.stop() or $el.finish()
				hooks.stop = function() {
					reject();
					next = null;
				};

				dust.render(templateName, data || {}, function(err, out) {
					if (err)
						reject(err);
					else {
						parseElement(out, resolve);
						if (next) next();
					}
				});
			});
		}).done(updateElement);

		this.ready = rendering.done;
		this.rendering = rendering;

		return this;
	},

	// Animate view

	enter: function() {
		this.$el.enter.apply(this.$el, arguments);
	},

	remove: function() {
		this.$el.leave();
		return this.stopListening();
	}
});
<commit_msg>Fix element queue when rendering<commit_after>/* Base view */

var dust = require('dustjs-linkedin'),
	Promise = require('./util/promise');

/* Extract wrapping element, attributes and contents */

    tagPattern = /<(\w+)[^>]*>/,
    attrPattern = /\s+(\S+)\s*=\s*("[^"]*"|'[^']*')/g;

function parseElement(html, callback) {
	var m = html.match(tagPattern);    // match first start tag

	if (m) {
		var tag = m[0], tagName = m[1],
			endTag = '</' + tagName + '>',
			endIndex = html.lastIndexOf('</');

		// check that the last end matches the start tag
		// and that there is no content after it

		if (html.slice(endIndex).replace(/\s+/g, '') == endTag)
			return callback(html.slice(m.index + tag.length, endIndex), tag, endTag, tagName);
	}

	return callback(html);
}

function attributes(tag) {    // extract attributes from tag string
	var attrs = {}, m;

	while (m = attrPattern.exec(tag))
		attrs[m[1]] = m[2].slice(1, -1);

	return attrs;
}

function updateElement(html, tag, endTag, tagName) {
	if (tag && !this.rendered) {
		if (this.$el.is(tagName)) {
			this.$el.attr(attributes(tag));
		} else {
			var queue = this.$el.queue();
			this.$el.clearQueue();
			this.setElement(tag + endTag);
			this.$el.queue(queue);
		}
	}
	this.el.innerHTML = html;
	this.rendered = true;
}

module.exports = Backbone.View.extend({
	// Promise factory function using view as the context with callbacks
	Promise: function(resolver) {
		return Promise(resolver, this);
	},

	constructor: function(options) {
		_.extend(this, _.pick(options, 'template', 'data'));

		Backbone.View.apply(this, arguments);

		this.rendered = (this.el && this.el.firstChild) != null;
		if (!this.rendered) this.render();
		else if (this.attach) this.attach();

		// this.ready(callbacks)

		this.ready = this.ready || $.Callbacks('once memory').fireWith(this).add;

	},

	convertElements: function() {
		var view = this;
		_.each(this.elements, function(element, key) {
			if(/^\$/.test(key)) view.elements[key.slice(1)] = element[0];
		});
	},

	render: function() {
		var data = _.result(this, 'data');

		if (this.model)
			data = _.defaults(this.model.toJSON(), data);
		else if(this.collection)
			data = _.defaults(this.collection.toJSON(), data);

		this.renderTemplate(data).ready(arguments);

		// TODO attach
		return this;
	},

	// Render Dust template and update element, using the fx queue

	renderTemplate: function(data) {

		var templateName = this.template,
			rendering = this.Promise(function(resolve, reject) {
			this.$el.queue(function(next, hooks) {
				// hooks.stop() is called if queue is stopped using $el.stop() or $el.finish()
				hooks.stop = function() {
					reject();
				};

				dust.render(templateName, data || {}, function(err, out) {
					if (err)
						reject(err);
					else
						parseElement(out, resolve);
				});
			});
		}).done(updateElement, this.attach, function() { this.$el.dequeue(); });

		this.ready = rendering.done;
		this.rendering = rendering;

		return this;
	},

	// Animate view

	enter: function() {
		this.$el.enter.apply(this.$el, arguments);
	},

	remove: function() {
		this.$el.leave();
		return this.stopListening();
	}
});
<|endoftext|>


<commit_before>/*jslint forin:true sub:true anon:true, sloppy:true, stupid:true nomen:true, node:true continue:true*/

/*
* Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/

var log4js = require("log4js");
var path = require("path");
var fs = require("fs");

function Arrow(config, args) {
    this.logger = log4js.getLogger("Arrow");
    this.config = config;
    this.args = args;

    Arrow.instance = this;
}

Arrow.instance = null;
Arrow.getInstance = function () {
    return Arrow.instance;
};

Arrow.prototype.run = function () {
    var SessionFactory = require("../session/sessionfactory"),
        sf;

    sf = new SessionFactory(this.config, this.args);
    sf.runAllTestSessions();
};

Arrow.prototype.runController = function (controllerName, testConfig, testParams, driver, callback) {
    var self = this,
        ControllerClass,
        controller;

    if (!testConfig) { testConfig = {}; }
    if (!testParams) { testParams = {}; }

    // Setting Default value of testParams.customController
    testParams.customController = false;

    if (controllerName) {
        if (controllerName.indexOf(".js") !== -1) {
            // its a user provided custom controller, marking the flag true.
            testParams.customController = true;
            controllerName = path.resolve(process.cwd(), controllerName);
        } else {
            var ctrName = controllerName;
            controllerName = path.join(this.config["arrowModuleRoot"] ,"lib/controller/" , ctrName);
            var getControllerFromShareLib=function(){
                var customControllerJson = path.join(self.config["arrowModuleRoot"] ,"tmp","custom_controller.json");
                try{
                    if (fs.statSync(customControllerJson).isFile()) {
                        var controllerJson = require(customControllerJson);
                        if (controllerJson && controllerJson[ctrName] && controllerJson[ctrName].path) {
                            return controllerJson[ctrName].path;
                        }
                    }
                }catch(e){}
                return ctrName;  //can't find in share lib
            }
            try{
                if(!fs.statSync(path.normalize(controllerName+".js")).isFile()){ // cant find in arrow/lib
                    controllerName=getControllerFromShareLib();                 // find it in share lib
                }
            }catch(e){
                controllerName=getControllerFromShareLib();
            }
        }
    } else {
        controllerName = this.config["arrowModuleRoot"] + "lib/controller/default"; // default controller
    }

    this.logger.info("Loading controller: " + controllerName);
    ControllerClass = require(controllerName);
    controller = new ControllerClass(testConfig, testParams, driver);

    this.logger.info("Executing controller: " + controllerName);

    if (testParams.scenario) {
        driver.reports.startScenarioReport();
    } else {
        driver.reports.startReport();
    }

    controller.execute(function (error, data) {
        var errorMsg;
        if (error) {
            if (testParams.testName) {
                errorMsg = "Test failed: " + testParams.testName +
                    ", Detailed error: " + error +
                    ", Controller: " + controllerName;
            } else {
                errorMsg = error + ", Controller: " + controllerName;
            }
            self.logger.error(errorMsg);
            if (!testParams.test && !testParams.scenario) {
                driver.reports.addReport({"error" : errorMsg, "controller" : controllerName, "params" : testParams});
            } else if (testParams.test && !testParams.scenario) {
                driver.reports.addReport({"error" : errorMsg, "controller" : controllerName, "params" : testParams});
            }
        } else {

            if (!testParams.test && !testParams.scenario) {
                driver.reports.addReport({"controller" : controllerName, "params" : testParams});
            }
            driver.reports.popReportAtom();
        }

        // error, data from previous controller, controller instance
        callback(error, data, controller);
    });
};

module.exports = Arrow;

<commit_msg>some jslint fixes, added uncaught exception for custom controller<commit_after>/*jslint forin:true sub:true anon:true, sloppy:true, stupid:true nomen:true, node:true continue:true*/

/*
* Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/

var log4js = require("log4js");
var path = require("path");
var fs = require("fs");

function Arrow(config, args) {
    this.logger = log4js.getLogger("Arrow");
    this.config = config;
    this.args = args;

    Arrow.instance = this;
}

Arrow.instance = null;
Arrow.getInstance = function () {
    return Arrow.instance;
};

Arrow.prototype.run = function () {
    var SessionFactory = require("../session/sessionfactory"),
        sf;

    sf = new SessionFactory(this.config, this.args);
    sf.runAllTestSessions();
};

Arrow.prototype.runController = function (controllerName, testConfig, testParams, driver, callback) {
    var self = this,
        ControllerClass,
        controller,
        ctrName,
        getControllerFromShareLib,
        errorMsg;

    if (!testConfig) {
        testConfig = {};
    }
    if (!testParams) {
        testParams = {};
    }

    if (driver.webdriver) {
        driver.webdriver.listener.on("uncaughtException", function (e) {
            errorMsg =  "Uncaught exception: " + e.message;
            self.logger.error(errorMsg);
            callback(errorMsg);
        });
    }

    // Setting Default value of testParams.customController
    testParams.customController = false;

    if (controllerName) {
        if (controllerName.indexOf(".js") !== -1) {
            // its a user provided custom controller, marking the flag true.
            testParams.customController = true;
            controllerName = path.resolve(process.cwd(), controllerName);
        } else {
            ctrName = controllerName;
            controllerName = path.join(this.config["arrowModuleRoot"], "lib/controller/", ctrName);
            getControllerFromShareLib = function () {
                var customControllerJson = path.join(self.config["arrowModuleRoot"], "tmp", "custom_controller.json"),
                    controllerJson;
                try {
                    if (fs.statSync(customControllerJson).isFile()) {
                        controllerJson = require(customControllerJson);
                        if (controllerJson && controllerJson[ctrName] && controllerJson[ctrName].path) {
                            return controllerJson[ctrName].path;
                        }
                    }
                } catch (e) {

                }
                return ctrName;  //can't find in share lib
            };
            try {
                if (!fs.statSync(path.normalize(controllerName + ".js")).isFile()) { // cant find in arrow/lib
                    controllerName = getControllerFromShareLib();                 // find it in share lib
                }
            } catch (e) {
                controllerName = getControllerFromShareLib();
            }
        }
    } else {
        controllerName = this.config["arrowModuleRoot"] + "lib/controller/default"; // default controller
    }

    this.logger.info("Loading controller: " + controllerName);
    ControllerClass = require(controllerName);
    controller = new ControllerClass(testConfig, testParams, driver);

    this.logger.info("Executing controller: " + controllerName);

    if (testParams.scenario) {
        driver.reports.startScenarioReport();
    } else {
        driver.reports.startReport();
    }

    controller.execute(function (error, data) {
        var errorMsg;
        if (error) {
            if (testParams.testName) {
                errorMsg = "Test failed: " + testParams.testName +
                    ", Detailed error: " + error +
                    ", Controller: " + controllerName;
            } else {
                errorMsg = error + ", Controller: " + controllerName;
            }
            self.logger.error(errorMsg);
            if (!testParams.test && !testParams.scenario) {
                driver.reports.addReport({"error" : errorMsg, "controller" : controllerName, "params" : testParams});
            } else if (testParams.test && !testParams.scenario) {
                driver.reports.addReport({"error" : errorMsg, "controller" : controllerName, "params" : testParams});
            }
        } else {

            if (!testParams.test && !testParams.scenario) {
                driver.reports.addReport({"controller" : controllerName, "params" : testParams});
            }
            driver.reports.popReportAtom();
        }

        // error, data from previous controller, controller instance
        callback(error, data, controller);
    });
};

module.exports = Arrow;

<|endoftext|>


<commit_before>//
// Vows.js - asynchronous event-based BDD for node.js
//
//   usage:
//
//       var vows = require('vows');
//
//       vows.describe('Deep Thought').addVows(function () {
//           question('what is the answer to the universe?').addVow(function (answer) {
//               assert.equals(answer, 42);
//           }, 'it should know the answer to the ultimate question of life');
//       });
//
var path = require('path');

require.paths.unshift(path.join(path.dirname(__filename), 'vendor'),
                      path.dirname(__filename));

var sys = require('sys'),
    assert = require('assert'),
    events = require('events'),
    eyes = require('eyes').inspector({ writer: null }),
    vows = exports;

// Keeps track of the outcome of vows.
var total  = 0, honored = 0,
    broken = 0, errored = 0,

    start,  // Start time
    end,    // End time
    suites; // Number of test suites added by `addVows()`

// Context stack, used in addVow() to keep track
var lastContext;

// Output buffer
var buffer;

//
// Assertion Macros
//
vows.macros = require('vows/macros');

assert.AssertionError.prototype.toString = function () {
    var that = this,
        source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/);

    function parse(str) {
        return str.replace(/{actual}/g,   inspect(that.actual)).
                   replace(/{expected}/g, inspect(that.expected)).
                   replace(/{operator}/g, stylize(that.operator, 'bold'));
    }

    if (this.message) {
        return stylize(parse(this.message), 'yellow') +
               stylize(' // ' + source[1] + source[2], 'grey');
    } else {
        return stylize([
            this.expected,
            this.operator,
            this.actual
        ].join(' '), 'yellow');
    }
}

//
// This function gets added to events.EventEmitter.prototype, by default.
// It's essentially a wrapper around `addCallback`, which adds all the specification
// goodness.
//
function addVow(/* description & callback */) {
    var desc, callback, context = '', vow = {}, args = arguments;

    total++;

    if (args.length > 1) {
        // Sometimes it might be nicer to pass the proof first,
        // and the description second, so we let the user choose
        // the order.
        if (typeof(args[0]) === "function") {
            vow.callback = args[0], vow.description = args[1];
        } else {
            vow.callback = args[1], vow.description = args[0];
        }
    } else if (args[0].callback && args[0].context) {
        vow = args[0];
    } else {
        throw new(Error)("wrong argument type for addVow()");
    }

    return this.addListener("success", function () {
        var args = Array.prototype.slice.call(arguments);
        // If the callback is expecting two or more arguments,
        // pass the error as the first (null) and the result after.
        if (vow.callback.length >= 2) {
            args.unshift(null);
        }
        runTest(args);

    }).addListener("error", function (err) {
        var exception;

        if (vow.callback.length >= 2) {
            runTest([err]);
        } else {
            exception = "  * " + stylize('The promise returned an error: ' +
                                 stylize(err, 'bold'), 'red');
            errored++;
            output('- ' + stylize(vow.description, 'red'), exception + "\n");
        }
    });

    function runTest(args) {
        var title = ' - ', exception, topic, msg;

        // Run the test, and try to catch `AssertionError`s and other exceptions;
        // increment counters accordingly.
        try {
            vow.callback.apply(vow.binding || null, args);
            title += stylize(vow.description, 'green');
            honored++;
        } catch (e) {
            if (e.name && e.name.match(/AssertionError/)) {
                title += stylize(vow.description, 'yellow');
                exception = '   ~ ' + e.toString();
                broken++;
            } else {
                title += stylize(vow.description, 'red');
                msg = e.stack || e.message || e.toString() || e;
                exception = '   ! ' + stylize(msg, 'red');
                errored++;
            }
        }
        output(title, exception);
    }

    function output(title, exception) {
        if (exception || !vows.options.brief) {
            if (vow.context && lastContext !== vow.context) {
                lastContext = vow.context;
                puts(vow.context);
            }
            puts(title);
            if (exception) puts(exception);
        }
        tryFinish(vows.remaining, vow.promise);
    }
};

function Context(vow, ctx, env) {
    this.tests = vow.callback;
    this.topics = (ctx.topics || []).slice(0);
    this.env = env || {};
    this.env.context = this;
    this.name = (ctx.name ? ctx.name + ' ' : '') +
                (vow.description || '');
}

function addVows(tests) {
    var promise = new(events.EventEmitter);
    var remaining = 0;

    suites++;

    vows.promises.push(promise);

    if (typeof(tests) === 'object') {
        // Count the number of vows/promises expected to fire,
        // so we know when the tests are over.
        // We match the keys against `matcher`, to decide
        // whether or not they should be included in the test.
        (function count(tests) {
            var match = false;
            remaining++;
            Object.keys(tests).forEach(function (key) {
                if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) {
                    if (! (match = count(tests[key]) ||
                           match || vows.options.matcher.test(key))) {
                        delete tests[key];
                        remaining--;
                    }
                }
            });
            return match;
        })(tests);
    }

    this.addListener("end", function (honored, broken, errored) {
        var topic, vow, env;

        vows.remaining += remaining;

        if (typeof(tests) === 'function') {
            return tests.call(null);
        } else {
            // The test runner, it calls itself recursively, passing the
            // previous context to the inner contexts. This is so the `topic`
            // functions have access to all the previous context topics in their
            // arguments list.
            // It is defined and invoked at the same time.
            // If it encounters a `topic` function, it waits for the returned
            // promise to emit (the topic), at which point it runs the functions under it,
            // passing the topic as an argument.
            (function run(ctx, lastTopic) {
                var ctxAdded = false;

                topic = ('topic' in ctx.tests) ? ctx.tests.topic
                                               : lastTopic;
                // Topic isn't a function, wrap it into one.
                if (typeof(topic) !== 'function') {
                    topic = (function (topic) {
                        return function () { return topic };
                    })(topic);
                }

                // Run the topic, passing the previous context topics
                topic = lastTopic = topic.apply(ctx.env, ctx.topics);

                // If the topic doesn't return an event emitter (such as a promise),
                // we create it ourselves, and emit the value on the next tick.
                if (! (topic instanceof vows.options.Emitter)) {
                    var emitter = new(vows.options.Emitter);

                    process.nextTick(function (val) {
                        return function () { emitter.emit("success", val) };
                    }(topic)); topic = emitter;
                }

                topic.addListener('success', function (val) {
                    // Once the topic fires, add the return value
                    // to the beginning of the topics list, so it
                    // becomes the first argument for the next topic.
                    ctx.topics.unshift(val);
                });

                // Now run the tests, or sub-contexts
                Object.keys(ctx.tests).filter(function (k) {
                    return ctx.tests[k] && k !== 'topic';
                }).forEach(function (item) {
                    // Holds the current test or context
                    vow = Object.create({
                        callback: ctx.tests[item],
                        context: ctx.name,
                        description: item,
                        binding: ctx.env,
                        promise: promise
                    });
                    env = Object.create(ctx.env);

                    // If we encounter a function, add it to the callbacks
                    // of the `topic` function, so it'll get called once the
                    // topic fires.
                    // If we encounter an object literal, we recurse, sending it
                    // our current context.
                    if (typeof(vow.callback) === 'function') {
                        topic.addVow(vow);
                    } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
                        // If there's a setup stage, we have to wait for it to fire,
                        // before calling the inner context. Else, just run the inner context
                        // synchronously.
                        if (topic) {
                            topic.addListener("success", function (vow, ctx) {
                                return function (val) {
                                    return run(new(Context)(vow, ctx, env), lastTopic);
                                };
                            }(vow, ctx));
                        } else {
                            run(new(Context)(vow, ctx, env), lastTopic);
                        }
                    }
                });
                // Check if we're done running the tests
                tryFinish(--vows.remaining, promise);
            // This is our initial, empty context
            })(new(Context)({ callback: tests, context: null, description: null }, {}));
        }
    });
    return promise;
}

function puts() {
    var args = Array.prototype.slice.call(arguments);
    if (vows.promises[suites - 1].listeners('finish').length > 0) {
        buffer.push(args.join('\n'));
    } else {
      sys.puts.apply(null, args);
    }
}

//
// Checks if all the required tests have been run,
// and either triggers the next test suite, if any,
// or exits the process.
//
function tryFinish(remaining, promise) {
    var result, style;

    // Output results once all the vows have been checked
    if (honored + broken + errored === total && remaining === 0) {
        result = honored + " honored, " +
                 broken  + " broken, "  +
                 errored + " errored",
        style  = honored === total ? ('green')
                                   : (errored === 0 ? 'yellow' : 'red');

        // If this isn't the last test suite in the chain,
        // emit 'end', to trigger the next test suite.
        if (promise && promise.listeners('end').length > 0) {
            sys.print('\n');
            promise.emit('end', honored, broken, errored);
        } else {
            if (!vows.options.brief) {
                puts("\nVerified " + total + " vows in " +
                     (((new(Date)) - start) / 1000) + " seconds.");
                puts("\n" + stylize(result, style));
            }

            // The 'finish' event is triggered once all the tests have been run.
            // It's used by bin/vows
            vows.promises[suites - 1].emit("finish", honored, broken, errored);

            if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') }

            // Don't exit until stdout is empty
            process.stdout.addListener('drain', function () {
                process.exit(broken || errored ? 1 : 0);
            });
        }
    }
}

//
// On exit, check that all promises have been fired.
// If not, print an error message.
//
process.addListener('exit', function () {
    if (honored + broken + errored < total) {
        sys.puts('\n* ' + stylize("An EventEmitter has failed to fire.", 'red'));
    }
});

//
// Wrap a Node.js style async function into an EventEmmitter
//
vows.prepare = function (obj, targets) {
    targets.forEach(function (target) {
        if (target in obj) {
            obj[target] = (function (fun) {
                return function () {
                    var args = Array.prototype.slice.call(arguments);
                    var ee = new(events.EventEmitter);

                    args.push(function (err /* [, data] */) {
                        var args = Array.prototype.slice.call(arguments, 1);

                        if (err) { ee.emit('error', err) }
                        else     { ee.emit.apply(ee, ['success'].concat(args)) }
                    });
                    fun.apply(obj, args);

                    return ee;
                };
            })(obj[target]);
        }
    });
    return obj;
};

// Options
vows.options = {
    Emitter: events.EventEmitter,
    brief: false,
    matcher: /.*/
};

// Run all vows/tests.
// It can take either a function as `tests`,
// or an object literal.
vows.describe = function (subject) {
    this.options.Emitter.prototype.addVow = addVow;
    this.options.Emitter.prototype.addVows = addVows;
    this.remaining = 0;
    this.promises = [];

    // Reset values
    total  = 0, honored = 0,
    broken = 0, errored = 0;
    buffer = [], suites = 0;

    process.nextTick(function () {
        if (!vows.options.brief) {
            puts('\n' + stylize(subject, 'underline') + '\n');
        }
    });
    return new(events.EventEmitter)().addListener('newListener', function (e, listener) {
        if (e === 'end') {
            this.removeListener(e, listener);
            start = new(Date);
            listener.call(this);
        }
    });
};

vows.tell = vows.describe;

// Return the `vows` object after setting some options
vows.config = function (opts) {
    for (var k in opts) { this.options[k] = opts[k] }
    return this;
};

//
// Utility functions
//

function inspect(val) {
    return '\033[1m' + eyes(val) + '\033[22m';
}

// Stylize a string
function stylize(str, style) {
    var styles = {
        'bold'      : [1,  22],
        'underline' : [4,  24],
        'yellow'    : [33, 39],
        'green'     : [32, 39],
        'red'       : [31, 39],
        'grey'      : [90, 39]
    };
    return '\033[' + styles[style][0] + 'm' + str +
           '\033[' + styles[style][1] + 'm';
}

<commit_msg>make sure result doesn't precede title<commit_after>//
// Vows.js - asynchronous event-based BDD for node.js
//
//   usage:
//
//       var vows = require('vows');
//
//       vows.describe('Deep Thought').addVows(function () {
//           question('what is the answer to the universe?').addVow(function (answer) {
//               assert.equals(answer, 42);
//           }, 'it should know the answer to the ultimate question of life');
//       });
//
var path = require('path');

require.paths.unshift(path.join(path.dirname(__filename), 'vendor'),
                      path.dirname(__filename));

var sys = require('sys'),
    assert = require('assert'),
    events = require('events'),
    eyes = require('eyes').inspector({ writer: null }),
    vows = exports;

// Keeps track of the outcome of vows.
var total  = 0, honored = 0,
    broken = 0, errored = 0,

    start,  // Start time
    end,    // End time
    suites; // Number of test suites added by `addVows()`

// Context stack, used in addVow() to keep track
var lastContext;

// Output buffer
var buffer;

//
// Assertion Macros
//
vows.macros = require('vows/macros');

assert.AssertionError.prototype.toString = function () {
    var that = this,
        source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/);

    function parse(str) {
        return str.replace(/{actual}/g,   inspect(that.actual)).
                   replace(/{expected}/g, inspect(that.expected)).
                   replace(/{operator}/g, stylize(that.operator, 'bold'));
    }

    if (this.message) {
        return stylize(parse(this.message), 'yellow') +
               stylize(' // ' + source[1] + source[2], 'grey');
    } else {
        return stylize([
            this.expected,
            this.operator,
            this.actual
        ].join(' '), 'yellow');
    }
}

//
// This function gets added to events.EventEmitter.prototype, by default.
// It's essentially a wrapper around `addCallback`, which adds all the specification
// goodness.
//
function addVow(/* description & callback */) {
    var desc, callback, context = '', vow = {}, args = arguments;

    total++;

    if (args.length > 1) {
        // Sometimes it might be nicer to pass the proof first,
        // and the description second, so we let the user choose
        // the order.
        if (typeof(args[0]) === "function") {
            vow.callback = args[0], vow.description = args[1];
        } else {
            vow.callback = args[1], vow.description = args[0];
        }
    } else if (args[0].callback && args[0].context) {
        vow = args[0];
    } else {
        throw new(Error)("wrong argument type for addVow()");
    }

    return this.addListener("success", function () {
        var args = Array.prototype.slice.call(arguments);
        // If the callback is expecting two or more arguments,
        // pass the error as the first (null) and the result after.
        if (vow.callback.length >= 2) {
            args.unshift(null);
        }
        runTest(args);

    }).addListener("error", function (err) {
        var exception;

        if (vow.callback.length >= 2) {
            runTest([err]);
        } else {
            exception = "  * " + stylize('The promise returned an error: ' +
                                 stylize(err, 'bold'), 'red');
            errored++;
            output('- ' + stylize(vow.description, 'red'), exception + "\n");
        }
    });

    function runTest(args) {
        var title = ' - ', exception, topic, msg;

        // Run the test, and try to catch `AssertionError`s and other exceptions;
        // increment counters accordingly.
        try {
            vow.callback.apply(vow.binding || null, args);
            title += stylize(vow.description, 'green');
            honored++;
        } catch (e) {
            if (e.name && e.name.match(/AssertionError/)) {
                title += stylize(vow.description, 'yellow');
                exception = '   ~ ' + e.toString();
                broken++;
            } else {
                title += stylize(vow.description, 'red');
                msg = e.stack || e.message || e.toString() || e;
                exception = '   ! ' + stylize(msg, 'red');
                errored++;
            }
        }
        output(title, exception);
    }

    function output(title, exception) {
        if (exception || !vows.options.brief) {
            if (vow.context && lastContext !== vow.context) {
                lastContext = vow.context;
                puts(vow.context);
            }
            puts(title);
            if (exception) puts(exception);
        }
        tryFinish(vows.remaining, vow.promise);
    }
};

function Context(vow, ctx, env) {
    this.tests = vow.callback;
    this.topics = (ctx.topics || []).slice(0);
    this.env = env || {};
    this.env.context = this;
    this.name = (ctx.name ? ctx.name + ' ' : '') +
                (vow.description || '');
}

function addVows(tests) {
    var promise = new(events.EventEmitter);
    var remaining = 0;

    suites++;

    vows.promises.push(promise);

    if (typeof(tests) === 'object') {
        // Count the number of vows/promises expected to fire,
        // so we know when the tests are over.
        // We match the keys against `matcher`, to decide
        // whether or not they should be included in the test.
        (function count(tests) {
            var match = false;
            remaining++;
            Object.keys(tests).forEach(function (key) {
                if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) {
                    if (! (match = count(tests[key]) ||
                           match || vows.options.matcher.test(key))) {
                        delete tests[key];
                        remaining--;
                    }
                }
            });
            return match;
        })(tests);
    }

    this.addListener("end", function (honored, broken, errored) {
        var topic, vow, env;

        vows.remaining += remaining;

        if (typeof(tests) === 'function') {
            return tests.call(null);
        } else {
            // The test runner, it calls itself recursively, passing the
            // previous context to the inner contexts. This is so the `topic`
            // functions have access to all the previous context topics in their
            // arguments list.
            // It is defined and invoked at the same time.
            // If it encounters a `topic` function, it waits for the returned
            // promise to emit (the topic), at which point it runs the functions under it,
            // passing the topic as an argument.
            (function run(ctx, lastTopic) {
                var ctxAdded = false;

                topic = ('topic' in ctx.tests) ? ctx.tests.topic
                                               : lastTopic;
                // Topic isn't a function, wrap it into one.
                if (typeof(topic) !== 'function') {
                    topic = (function (topic) {
                        return function () { return topic };
                    })(topic);
                }

                // Run the topic, passing the previous context topics
                topic = lastTopic = topic.apply(ctx.env, ctx.topics);

                // If the topic doesn't return an event emitter (such as a promise),
                // we create it ourselves, and emit the value on the next tick.
                if (! (topic instanceof vows.options.Emitter)) {
                    var emitter = new(vows.options.Emitter);

                    process.nextTick(function (val) {
                        return function () { emitter.emit("success", val) };
                    }(topic)); topic = emitter;
                }

                topic.addListener('success', function (val) {
                    // Once the topic fires, add the return value
                    // to the beginning of the topics list, so it
                    // becomes the first argument for the next topic.
                    ctx.topics.unshift(val);
                });

                // Now run the tests, or sub-contexts
                Object.keys(ctx.tests).filter(function (k) {
                    return ctx.tests[k] && k !== 'topic';
                }).forEach(function (item) {
                    // Holds the current test or context
                    vow = Object.create({
                        callback: ctx.tests[item],
                        context: ctx.name,
                        description: item,
                        binding: ctx.env,
                        promise: promise
                    });
                    env = Object.create(ctx.env);

                    // If we encounter a function, add it to the callbacks
                    // of the `topic` function, so it'll get called once the
                    // topic fires.
                    // If we encounter an object literal, we recurse, sending it
                    // our current context.
                    if (typeof(vow.callback) === 'function') {
                        topic.addVow(vow);
                    } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) {
                        // If there's a setup stage, we have to wait for it to fire,
                        // before calling the inner context. Else, just run the inner context
                        // synchronously.
                        if (topic) {
                            topic.addListener("success", function (vow, ctx) {
                                return function (val) {
                                    return run(new(Context)(vow, ctx, env), lastTopic);
                                };
                            }(vow, ctx));
                        } else {
                            run(new(Context)(vow, ctx, env), lastTopic);
                        }
                    }
                });
                // Check if we're done running the tests
                tryFinish(--vows.remaining, promise);
            // This is our initial, empty context
            })(new(Context)({ callback: tests, context: null, description: null }, {}));
        }
    });
    return promise;
}

function puts() {
    var args = Array.prototype.slice.call(arguments);
    if (vows.promises[suites - 1].listeners('finish').length > 0) {
        buffer.push(args.join('\n'));
    } else {
      sys.puts.apply(null, args);
    }
}

//
// Checks if all the required tests have been run,
// and either triggers the next test suite, if any,
// or exits the process.
//
function tryFinish(remaining, promise) {
    var result, style;

    // Output results once all the vows have been checked
    if (honored + broken + errored === total && remaining === 0) {
        result = honored + " honored, " +
                 broken  + " broken, "  +
                 errored + " errored",
        style  = honored === total ? ('green')
                                   : (errored === 0 ? 'yellow' : 'red');

        // If this isn't the last test suite in the chain,
        // emit 'end', to trigger the next test suite.
        if (promise && promise.listeners('end').length > 0) {
            sys.print('\n');
            promise.emit('end', honored, broken, errored);
        } else {
            if (!vows.options.brief) {
                puts("\nVerified " + total + " vows in " +
                     (((new(Date)) - start) / 1000) + " seconds.");
                puts("\n" + stylize(result, style));
            }

            // The 'finish' event is triggered once all the tests have been run.
            // It's used by bin/vows
            vows.promises[suites - 1].emit("finish", honored, broken, errored);

            if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') }

            // Don't exit until stdout is empty
            process.stdout.addListener('drain', function () {
                process.exit(broken || errored ? 1 : 0);
            });
        }
    }
}

//
// On exit, check that all promises have been fired.
// If not, print an error message.
//
process.addListener('exit', function () {
    if (honored + broken + errored < total) {
        sys.puts('\n* ' + stylize("An EventEmitter has failed to fire.", 'red'));
    }
});

//
// Wrap a Node.js style async function into an EventEmmitter
//
vows.prepare = function (obj, targets) {
    targets.forEach(function (target) {
        if (target in obj) {
            obj[target] = (function (fun) {
                return function () {
                    var args = Array.prototype.slice.call(arguments);
                    var ee = new(events.EventEmitter);

                    args.push(function (err /* [, data] */) {
                        var args = Array.prototype.slice.call(arguments, 1);

                        if (err) { ee.emit('error', err) }
                        else     { ee.emit.apply(ee, ['success'].concat(args)) }
                    });
                    fun.apply(obj, args);

                    return ee;
                };
            })(obj[target]);
        }
    });
    return obj;
};

// Options
vows.options = {
    Emitter: events.EventEmitter,
    brief: false,
    matcher: /.*/
};

// Run all vows/tests.
// It can take either a function as `tests`,
// or an object literal.
vows.describe = function (subject) {
    this.options.Emitter.prototype.addVow = addVow;
    this.options.Emitter.prototype.addVows = addVows;
    this.remaining = 0;
    this.promises = [];

    // Reset values
    total  = 0, honored = 0,
    broken = 0, errored = 0;
    buffer = [], suites = 0;

    process.nextTick(function () {
        if (!vows.options.brief) {
            puts('\n' + stylize(subject, 'underline') + '\n');
        }
    });
    return new(events.EventEmitter)().addListener('newListener', function (e, listener) {
        var that = this;
        if (e === 'end') {
            this.removeListener(e, listener);
            process.nextTick(function () {
                start = new(Date);
                listener.call(that);
            });
        }
    });
};

vows.tell = vows.describe;

// Return the `vows` object after setting some options
vows.config = function (opts) {
    for (var k in opts) { this.options[k] = opts[k] }
    return this;
};

//
// Utility functions
//

function inspect(val) {
    return '\033[1m' + eyes(val) + '\033[22m';
}

// Stylize a string
function stylize(str, style) {
    var styles = {
        'bold'      : [1,  22],
        'underline' : [4,  24],
        'yellow'    : [33, 39],
        'green'     : [32, 39],
        'red'       : [31, 39],
        'grey'      : [90, 39]
    };
    return '\033[' + styles[style][0] + 'm' + str +
           '\033[' + styles[style][1] + 'm';
}

<|endoftext|>