API Docs for: 0.9.1
Show:

File: lib/container.js

// Copyright 2014, Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

var Base = require('preceptor-core').Base;
var utils = require('preceptor-core').utils;
var _ = require('underscore');

/**
 * This class manages the messages received and is used as data-source for the reporter instances.
 *
 * @class ReportContainer
 * @extends Base
 *
 * @property {object[]} _sequence Sequence of items in the report
 * @property {object} _actions Actions for each item keyed by the id - used for direct access into the tree
 * @property {object} _tree Root object of reporting tree - uses references from _actions to form tree
 */
var ReportContainer = Base.extend(

	{
		/**
		 * Called when reporting starts
		 *
		 * @method start
		 */
		start: function () {
			this._sequence = [];
			this._actions = {};
			this._tree = {
				startTime: +(new Date()),
				pending: true,
				type: 'root',
				name: 'Root',
				level: 0,
				data: {},
				messages: [],
				children: [],
				output: {}
			};
		},

		/**
		 * Called when reporting stops
		 *
		 * @method stop
		 */
		stop: function () {

			this._tree.endTime = +(new Date());
			this._tree.duration = this._tree.endTime - this._tree.startTime;
			this._tree.pending = false;

			_.each(_.keys(this._actions), function (id) {

				var action = this._actions[id];

				if (action.pending) {
					throw new Error("Reporter action '" + action.type + "' for '" + action.name + "' is pending when end was reached.");
				}
			}, this);
		},

		/**
		 * Reporting is completed
		 *
		 * @method complete
		 */
		complete: function () {
			// Nothing here
		},

		/**
		 * Called when suite starts
		 *
		 * @method suiteStart
		 * @param {string} id
		 * @param {string} parentId
		 * @param {string} suiteName
		 * @param {object} [options]
		 * @param {int} [options.startTime]
		 */
		suiteStart: function (id, parentId, suiteName, options) {
			var parent;

			options = options || {};

			this._actions[id] = {
				id: id,
				startTime: options.startTime || +(new Date()),
				pending: true,
				type: 'suite',
				name: suiteName,
				data: {},
				messages: [],
				parentId: parentId,
				children: [],
				output: {}
			};

			parent = this.getAction(parentId);
			this._actions[id].level = parent.level + 1;

			this._sequence.push(this._actions[id]);
			parent.children.push(this._actions[id]);
		},

		/**
		 * Called when suite ends
		 *
		 * @method suiteEnd
		 * @param {string} id
		 * @param {object} [options]
		 * @param {int} [options.endTime]
		 */
		suiteEnd: function (id, options) {
			var action = this.getAction(id);

			options = options || {};

			if (action.type !== 'suite') {
				throw new Error("Type of reporter action was expected to be 'suite' but was '" + action.type + "'.")
			}
			if (!action.pending) {
				throw new Error("Reporter action for suite was already closed.");
			}

			action.endTime = options.startTime || +(new Date());
			action.duration = action.endTime - action.startTime;
			action.pending = false;
		},


		/**
		 * Called when any item has custom data
		 *
		 * @method itemData
		 * @param {string} id
		 * @param {string} json JSON-data
		 */
		itemData: function (id, json) {
			var action = this.getAction(id);

			action.data = utils.deepExtend(action.data, [JSON.parse(json)]);
		},

		/**
		 * Called when any item has a custom message
		 *
		 * @method itemMessage
		 * @param {string} id
		 * @param {string} message
		 */
		itemMessage: function (id, message) {
			this.getAction(id).messages.push(message);
		},


		/**
		 * Called when test starts
		 *
		 * @method testStart
		 * @param {string} id
		 * @param {string} parentId
		 * @param {string} testName
		 * @param {object} [options]
		 * @param {int} [options.startTime]
		 */
		testStart: function (id, parentId, testName, options) {
			var parent;

			options = options || {};

			this._actions[id] = {
				id: id,
				startTime: options.startTime || +(new Date()),
				pending: true,
				type: 'test',
				name: testName,
				data: {},
				messages: [],
				parentId: parentId,
				output: {}
			};

			parent = this.getAction(parentId);
			this._actions[id].level = parent.level + 1;

			this._sequence.push(this._actions[id]);
			parent.children.push(this._actions[id]);
		},


		/**
		 * Called when test fails
		 *
		 * @method testFailed
		 * @param {string} id
		 * @param {string} [message]
		 * @param {string} [reason]
		 * @param {object} [options]
		 * @param {int} [options.endTime]
		 */
		testFailed: function (id, message, reason, options) {
			var action = this._completeTestAction(id, options);

			action.outcome = 'failed';
			action.message = message || 'FAILED';
			action.reason = reason || action.message;
		},

		/**
		 * Called when test has an error
		 *
		 * @method testError
		 * @param {string} id
		 * @param {string} [message]
		 * @param {string} [reason]
		 * @param {object} [options]
		 */
		testError: function (id, message, reason, options) {
			var action = this._completeTestAction(id, options);

			action.outcome = 'error';
			action.message = message || 'ERROR';
			action.reason = reason || action.message;
		},

		/**
		 * Called when test has passed
		 *
		 * @method testPassed
		 * @param {string} id
		 * @param {object} [options]
		 */
		testPassed: function (id, options) {
			var action = this._completeTestAction(id, options);

			action.outcome = 'passed';
		},

		/**
		 * Called when test is undefined
		 *
		 * @method testUndefined
		 * @param {string} id
		 * @param {object} [options]
		 */
		testUndefined: function (id, options) {
			var action = this._completeTestAction(id, options);

			action.outcome = 'undefined';
		},

		/**
		 * Called when test is skipped
		 *
		 * @method testSkipped
		 * @param {string} id
		 * @param {string} [reason]
		 * @param {object} [options]
		 */
		testSkipped: function (id, reason, options) {
			var action = this._completeTestAction(id, options);

			action.outcome = 'skipped';
			action.reason = reason || 'SKIPPED';
		},

		/**
		 * Called when test is incomplete
		 *
		 * @method testIncomplete
		 * @param {string} id
		 * @param {object} [options]
		 */
		testIncomplete: function (id, options) {
			var action = this._completeTestAction(id, options);

			action.outcome = 'incomplete';
		},


		/**
		 * Completes a test
		 *
		 * @method _completeTestAction
		 * @param {string} id
		 * @param {object} [options]
		 * @param {int} [options.endTime]
		 * @return {object} Action
		 * @private
		 */
		_completeTestAction: function (id, options) {
			var action = this.getAction(id);

			options = options || {};

			if (action.type !== 'test') {
				throw new Error("Type of reporter action was expected to be 'test' but was '" + action.type + "'.")
			}
			if (!action.pending) {
				throw new Error("Reporter action for test was already closed.");
			}

			action.endTime = options.endTime || +(new Date());
			action.duration = action.endTime - action.startTime;
			action.pending = false;

			return action;
		},


		/**
		 * Gets action by id
		 *
		 * @method getAction
		 * @param {string} id
		 * @return {object}
		 */
		getAction: function (id) {
			var action;

			if (id === "undefined") {
				id = undefined;
			}
			if (id === "null") {
				id = null;
			}

			if ((id === undefined) || (id === null)) {
				action = this.getTree();
			} else {
				action = this._actions[id];

				if (!action) {
					throw new Error("Id for reporter action doesn't exist " + id + ".");
				}
			}

			return action;
		},

		/**
		 * Gets the sequence of actions by the sequential list of ids
		 *
		 * @method getSequence
		 * @return {string[]}
		 */
		getSequence: function () {
			return this._sequence;
		},

		/**
		 * Gets the action tree
		 *
		 * @method getTree
		 * @return {object}
		 */
		getTree: function () {
			return this._tree;
		},


		/**
		 * Gathers all test outcomes for a node
		 *
		 * @method gatherTestOutcomes
		 * @param {object} treeNode
		 * @return {object} Of `{tests: int, failed: int, disabled: int, error: int}`
		 */
		gatherTestOutcomes: function (treeNode) {
			var result = {
				tests: 0,
				failed: 0,
				incomplete: 0,
				skipped: 0,
				error: 0,
				passed: 0,
				undef: 0
			};

			this._countOutcomes(treeNode, result);

			return result;
		},

		/**
		 * Counts a specific outcome downwards from the current tree-point
		 *
		 * @method _countOutcomes
		 * @param {object} treeNode
		 * @param {object} sumObj
		 * @private
		 */
		_countOutcomes: function (treeNode, sumObj) {
			if (treeNode.type === 'test') {
				sumObj.tests++;

				if (treeNode.outcome === 'undefined') {
					sumObj['undef']++;
				} else {
					sumObj[treeNode.outcome]++;
				}
			} else {
				_.each(treeNode.children || [], function (node) {
					this._countOutcomes(node, sumObj);
				}, this);
			}
		},

		/**
		 * Gets the full name of an object
		 *
		 * @method getFullName
		 * @param {string} id
		 * @return {string}
		 */
		getFullName: function (id) {
			var action = this.getAction(id),
				parentId = action.parentId,
				name = action.name;

			if (parentId !== undefined) {
				return [this.getFullName(parentId), name].join(" ");
			} else {
				return name;
			}
		}
	},

	{
		/**
		 * @property TYPE
		 * @type {string}
		 * @static
		 */
		TYPE: 'ReportContainer'
	});

module.exports = ReportContainer;