<commit_before>var promises = require('q'),
	assert = require('assert');


/**@class	A Feature models a sequence of actions to be executed through Widgets.
* 
* A feature description file contains a simple descriptive array listing widget methods to execute and widget state descriptors to assert.
* More formally, such an array is ordered and its members may be:
* - a closure;
* - an object whose keys are some widgets' attributes identifiers (ex: "MyWidget.myAttr"), pointing at a string that contains the expected text content of the HTML element represented by the `myAttr` hook in `MyWidget`.
*
* Upon instanciation, a Feature translates this array into an array of promises:
* - closures are executed directly, either as promises if they are so themselves, or as basic functions;
* - a widget state descripting hash maps each of its members to an assertion inside a promise, evaluating all of them completely asynchronously.
* All those promises are then evaluated sequentially upon calling the `test` method of a Feature.
*/
var Feature = new Class({
	/** A sequence of promises to be executed in order, constructed after the scenario for this feature.
	*@private
	*/
	steps: [],
	
	/**
	*
	*@param	{String}	description	A plaintext description of the feature, advised to be written in a BDD fashion.
	*@param	{Array}		scenario	An array that describes states and transitions. See class documentation for formatting.
	*/
	initialize: function init(description, scenario) {
		this.description = description;
		
		this.steps = this.loadScenario(scenario);
	},
	
	/** Parses an array that describes states and transitions and transforms it into a sequence of promises to be evaluated.
	*
	*@param		{Array}	scenario	An array that describes states and transitions. See class documentation for formatting.
	*@returns	{Array.<function>}	An array of promises representing the given scenario.
	*@private
	*/
	loadScenario: function loadScenario(scenario) {
		var result = [];
		
		for (var stepIndex = 0; stepIndex < scenario.length; stepIndex++) {
			var step = scenario[stepIndex]; // takes all values listed in a scenario
			var promiseCreator; // a function that will return a promise
			
			switch (typeOf(step)) {
				case 'function':
					promiseCreator = step;
					break;
				case 'object':
					promiseCreator = this.buildAssertionPromise(step);
					break;
				default:	// any other value type will be considered a parameter to the previous function
							// if you need to pass objects or functions as parameters, just embed them in arrays
							// arrays will be unwrapped as a list of parameters
					promiseCreator = this.buildFunctionalPromise(result.pop(),
																 typeOf(step) == 'array' ? step : [ step ]);
			}
			
			result.push(promiseCreator);
		}
		
		return result;
	},
	
	/** Normalizes an operational closure (i.e. a function that modifies a widget's state) to a format compatible with scenario steps execution.
	*
	*@param	{Function}	func	The raw function to execute.
	*@param	{Array}		params	Parameters to bind to this function.
	*@returns	{Function}	A bound function, ready for execution as a step.
	*@private
	*/
	buildFunctionalPromise: function buildFunctionalPromise(func, params) {
		return func.apply.bind(func, null, params);	//TODO: handle errors like `func` not being a function
	},
	
	/** Parses a widget state description and creates an assertive closure returning the promise for assertions results upon evaluation.
	*
	*@private
	*@param		{Object}	hooksVals	A hash whose keys match some widgets' attributes, pointing at values that are expected values for those attributes.
	*@returns	{function}	A parameter-less closure asserting the described state and returning a promise that will be either:
	*	- rejected if any assertion fails, passing a string parameter that describes the first failed match;
	*	- resolved if all assertions pass, with no parameter.
	*/
	buildAssertionPromise: function buildAssertionPromise(hooksVals) {
		return function() {
			var evaluator = promises.defer(),
				matchesLeft = Object.getLength(hooksVals);

			Object.each(hooksVals, function(expected, attribute) {
				eval(attribute).getText().then(function(actual) { //TODO: replace eval with an object walker
					if (expected != actual)
						evaluator.reject(attribute + ' was "' + actual + '" instead of "' + expected + '"');
						
					if (--matchesLeft == 0)
						evaluator.resolve();
				});
			});
			
			return evaluator.promise;
		}
	},
	
	/** Asynchronously evaluates the scenario given to this feature.
	*
	*@returns	{Promise}	A promise that will be either:
	*	- rejected if any assertion or action fails, passing an array of strings that describe reason(s) for failure(s) (one reason per item in the array);
	*	- resolved if all assertions pass, with no parameter.
	*/
	test: function evaluate() {
		var deferred = promises.defer(),
			stepIndex = 0;
		
		var evaluateNext,
			failureReasons = [];
		
		var handleFailure = function handleFailure(message) {
			failureReasons.push(message);
			evaluateNext();
		}
		
		evaluateNext = (function(value) {
			if (stepIndex == this.steps.length) {
				if (failureReasons.length == 0)
					return deferred.resolve();
				else
					return deferred.reject(failureReasons);
			}
			
			try {
				promises.when(this.steps[stepIndex](),
							  evaluateNext,
							  handleFailure);
			} catch (error) {
				handleFailure(error);
			}
			
			stepIndex++;
		}).bind(this);
		
		evaluateNext();
		
		return deferred.promise;
	}
});


module.exports = Feature;	// CommonJS export
<commit_msg>Corrected feature loading for compatibility with sandboxing.<commit_after>var promises = require('q'),
	assert = require('assert');


/**@class	A Feature models a sequence of actions to be executed through Widgets.
* 
* A feature description file contains a simple descriptive array listing widget methods to execute and widget state descriptors to assert.
* More formally, such an array is ordered and its members may be:
* - a closure;
* - an object whose keys are some widgets' attributes identifiers (ex: "MyWidget.myAttr"), pointing at a string that contains the expected text content of the HTML element represented by the `myAttr` hook in `MyWidget`.
*
* Upon instanciation, a Feature translates this array into an array of promises:
* - closures are executed directly, either as promises if they are so themselves, or as basic functions;
* - a widget state descripting hash maps each of its members to an assertion inside a promise, evaluating all of them completely asynchronously.
* All those promises are then evaluated sequentially upon calling the `test` method of a Feature.
*/
var Feature = new Class({
	/** A sequence of promises to be executed in order, constructed after the scenario for this feature.
	*@private
	*/
	steps: [],
	
	/**
	*
	*@param	{String}	description	A plaintext description of the feature, advised to be written in a BDD fashion.
	*@param	{Array}		scenario	An array that describes states and transitions. See class documentation for formatting.
	*/
	initialize: function init(description, scenario) {
		this.description = description;
		
		this.steps = this.loadScenario(scenario);
	},
	
	/** Parses an array that describes states and transitions and transforms it into a sequence of promises to be evaluated.
	*
	*@param		{Array}	scenario	An array that describes states and transitions. See class documentation for formatting.
	*@returns	{Array.<function>}	An array of promises representing the given scenario.
	*@private
	*/
	loadScenario: function loadScenario(scenario) {
		var result = [];
		
		for (var stepIndex = 0; stepIndex < scenario.length; stepIndex++) {
			var step = scenario[stepIndex]; // takes all values listed in a scenario
			
			/* So, this is going to be a bit hard. Stay with me  ;)
			 * Scenarios are loaded in a different context, absolutely clean by default (see SuiteLoader).
			 * Therefore, steps in the scenario are clean of any protoype augmentation.
			 * MooTools, for example, allows proper type introspection through prototype augmentation. This is not usable here.
			 * But we still need to do introspection to offer proper heuristics. Tricks to achieve this are below.
			 */
			result.push(	step instanceof Function ?
							step
						  : typeof step == 'object' && step.length >= 0 ?	// an Array have a length property, not an Object; as a consequence, `length` is a reserved property for state description hashes
						    this.buildFunctionalPromise(result.pop(), step)
						  : typeof step == 'object' ?
						    this.buildAssertionPromise(step) // if this is a Hash, it is a description value
						  : this.buildFunctionalPromise(result.pop(), [ step ])	// default: this is a primitive value, we normalize it by wrapping
						);
		}
		
		return result;
	},
	
	/** Normalizes an operational closure (i.e. a function that modifies a widget's state) to a format compatible with scenario steps execution.
	*
	*@param	{Function}	func	The raw function to execute.
	*@param	{Array}		params	Parameters to bind to this function.
	*@returns	{Function}	A bound function, ready for execution as a step.
	*@private
	*/
	buildFunctionalPromise: function buildFunctionalPromise(func, params) {
		return func.apply.bind(func, null, params);	//TODO: handle errors like `func` not being a function
	},
	
	/** Parses a widget state description and creates an assertive closure returning the promise for assertions results upon evaluation.
	*
	*@private
	*@param		{Object}	hooksVals	A hash whose keys match some widgets' attributes, pointing at values that are expected values for those attributes.
	*@returns	{function}	A parameter-less closure asserting the described state and returning a promise that will be either:
	*	- rejected if any assertion fails, passing a string parameter that describes the first failed match;
	*	- resolved if all assertions pass, with no parameter.
	*/
	buildAssertionPromise: function buildAssertionPromise(hooksVals) {
		return function() {
			var evaluator = promises.defer(),
				matchesLeft = Object.getLength(hooksVals);

			Object.each(hooksVals, function(expected, attribute) {
				eval(attribute).getText().then(function(actual) { //TODO: replace eval with an object walker
					if (expected != actual)
						evaluator.reject(attribute + ' was "' + actual + '" instead of "' + expected + '"');
						
					if (--matchesLeft == 0)
						evaluator.resolve();
				});
			});
			
			return evaluator.promise;
		}
	},
	
	/** Asynchronously evaluates the scenario given to this feature.
	*
	*@returns	{Promise}	A promise that will be either:
	*	- rejected if any assertion or action fails, passing an array of strings that describe reason(s) for failure(s) (one reason per item in the array);
	*	- resolved if all assertions pass, with no parameter.
	*/
	test: function evaluate() {
		var deferred = promises.defer(),
			stepIndex = 0;
		
		var evaluateNext,
			failureReasons = [];
		
		var handleFailure = function handleFailure(message) {
			failureReasons.push(message);
			evaluateNext();
		}
		
		evaluateNext = (function(value) {
			if (stepIndex == this.steps.length) {
				if (failureReasons.length == 0)
					return deferred.resolve();
				else
					return deferred.reject(failureReasons);
			}
			
			try {
				promises.when(this.steps[stepIndex](),
							  evaluateNext,
							  handleFailure);
			} catch (error) {
				handleFailure(error);
			}
			
			stepIndex++;
		}).bind(this);
		
		evaluateNext();
		
		return deferred.promise;
	}
});


module.exports = Feature;	// CommonJS export

{'fraction_non_alphanumeric': 0.08891574585635359, 'fraction_numerical': 0.0007769337016574585, 'mean_word_length': 3.6845936109987867, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1162752', 'n_tokens_mistral': 3568, 'n_tokens_neox': 3055, 'n_words': 1312}