From 2430f52bb97fa9d682e5f028c977c5bf94c5ec38 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 23 Mar 2012 14:03:24 -0700 Subject: chore(module): move files around in preparation for more modules --- src/ngScenario/Scenario.js | 417 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 src/ngScenario/Scenario.js (limited to 'src/ngScenario/Scenario.js') diff --git a/src/ngScenario/Scenario.js b/src/ngScenario/Scenario.js new file mode 100644 index 00000000..cd3c335f --- /dev/null +++ b/src/ngScenario/Scenario.js @@ -0,0 +1,417 @@ +'use strict'; + + +/** + * Setup file for the Scenario. + * Must be first in the compilation/bootstrap list. + */ + +// Public namespace +angular.scenario = angular.scenario || {}; + +/** + * Defines a new output format. + * + * @param {string} name the name of the new output format + * @param {function()} fn function(context, runner) that generates the output + */ +angular.scenario.output = angular.scenario.output || function(name, fn) { + angular.scenario.output[name] = fn; +}; + +/** + * Defines a new DSL statement. If your factory function returns a Future + * it's returned, otherwise the result is assumed to be a map of functions + * for chaining. Chained functions are subject to the same rules. + * + * Note: All functions on the chain are bound to the chain scope so values + * set on "this" in your statement function are available in the chained + * functions. + * + * @param {string} name The name of the statement + * @param {function()} fn Factory function(), return a function for + * the statement. + */ +angular.scenario.dsl = angular.scenario.dsl || function(name, fn) { + angular.scenario.dsl[name] = function() { + function executeStatement(statement, args) { + var result = statement.apply(this, args); + if (angular.isFunction(result) || result instanceof angular.scenario.Future) + return result; + var self = this; + var chain = angular.extend({}, result); + angular.forEach(chain, function(value, name) { + if (angular.isFunction(value)) { + chain[name] = function() { + return executeStatement.call(self, value, arguments); + }; + } else { + chain[name] = value; + } + }); + return chain; + } + var statement = fn.apply(this, arguments); + return function() { + return executeStatement.call(this, statement, arguments); + }; + }; +}; + +/** + * Defines a new matcher for use with the expects() statement. The value + * this.actual (like in Jasmine) is available in your matcher to compare + * against. Your function should return a boolean. The future is automatically + * created for you. + * + * @param {string} name The name of the matcher + * @param {function()} fn The matching function(expected). + */ +angular.scenario.matcher = angular.scenario.matcher || function(name, fn) { + angular.scenario.matcher[name] = function(expected) { + var prefix = 'expect ' + this.future.name + ' '; + if (this.inverse) { + prefix += 'not '; + } + var self = this; + this.addFuture(prefix + name + ' ' + angular.toJson(expected), + function(done) { + var error; + self.actual = self.future.value; + if ((self.inverse && fn.call(self, expected)) || + (!self.inverse && !fn.call(self, expected))) { + error = 'expected ' + angular.toJson(expected) + + ' but was ' + angular.toJson(self.actual); + } + done(error); + }); + }; +}; + +/** + * Initialize the scenario runner and run ! + * + * Access global window and document object + * Access $runner through closure + * + * @param {Object=} config Config options + */ +angular.scenario.setUpAndRun = function(config) { + var href = window.location.href; + var body = _jQuery(document.body); + var output = []; + var objModel = new angular.scenario.ObjectModel($runner); + + if (config && config.scenario_output) { + output = config.scenario_output.split(','); + } + + angular.forEach(angular.scenario.output, function(fn, name) { + if (!output.length || indexOf(output,name) != -1) { + var context = body.append('
').find('div:last'); + context.attr('id', name); + fn.call({}, context, $runner, objModel); + } + }); + + if (!/^http/.test(href) && !/^https/.test(href)) { + body.append(''); + body.find('#system-error').text( + 'Scenario runner must be run using http or https. The protocol ' + + href.split(':')[0] + ':// is not supported.' + ); + return; + } + + var appFrame = body.append('').find('#application'); + var application = new angular.scenario.Application(appFrame); + + $runner.on('RunnerEnd', function() { + appFrame.css('display', 'none'); + appFrame.find('iframe').attr('src', 'about:blank'); + }); + + $runner.on('RunnerError', function(error) { + if (window.console) { + console.log(formatException(error)); + } else { + // Do something for IE + alert(error); + } + }); + + $runner.run(application); +}; + +/** + * Iterates through list with iterator function that must call the + * continueFunction to continute iterating. + * + * @param {Array} list list to iterate over + * @param {function()} iterator Callback function(value, continueFunction) + * @param {function()} done Callback function(error, result) called when + * iteration finishes or an error occurs. + */ +function asyncForEach(list, iterator, done) { + var i = 0; + function loop(error, index) { + if (index && index > i) { + i = index; + } + if (error || i >= list.length) { + done(error); + } else { + try { + iterator(list[i++], loop); + } catch (e) { + done(e); + } + } + } + loop(); +} + +/** + * Formats an exception into a string with the stack trace, but limits + * to a specific line length. + * + * @param {Object} error The exception to format, can be anything throwable + * @param {Number} maxStackLines Optional. max lines of the stack trace to include + * default is 5. + */ +function formatException(error, maxStackLines) { + maxStackLines = maxStackLines || 5; + var message = error.toString(); + if (error.stack) { + var stack = error.stack.split('\n'); + if (stack[0].indexOf(message) === -1) { + maxStackLines++; + stack.unshift(error.message); + } + message = stack.slice(0, maxStackLines).join('\n'); + } + return message; +} + +/** + * Returns a function that gets the file name and line number from a + * location in the stack if available based on the call site. + * + * Note: this returns another function because accessing .stack is very + * expensive in Chrome. + * + * @param {Number} offset Number of stack lines to skip + */ +function callerFile(offset) { + var error = new Error(); + + return function() { + var line = (error.stack || '').split('\n')[offset]; + + // Clean up the stack trace line + if (line) { + if (line.indexOf('@') !== -1) { + // Firefox + line = line.substring(line.indexOf('@')+1); + } else { + // Chrome + line = line.substring(line.indexOf('(')+1).replace(')', ''); + } + } + + return line || ''; + }; +} + +/** + * Triggers a browser event. Attempts to choose the right event if one is + * not specified. + * + * @param {Object} element Either a wrapped jQuery/jqLite node or a DOMElement + * @param {string} type Optional event type. + * @param {Array.