aboutsummaryrefslogtreecommitdiffstats
path: root/src/scenario/Scenario.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/scenario/Scenario.js')
-rw-r--r--src/scenario/Scenario.js103
1 files changed, 103 insertions, 0 deletions
diff --git a/src/scenario/Scenario.js b/src/scenario/Scenario.js
new file mode 100644
index 00000000..e93f6b2e
--- /dev/null
+++ b/src/scenario/Scenario.js
@@ -0,0 +1,103 @@
+/**
+ * Setup file for the Scenario.
+ * Must be first in the compilation/bootstrap list.
+ */
+
+// Public namespace
+angular.scenario = {};
+
+// Namespace for the UI
+angular.scenario.ui = {};
+
+/**
+ * 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} The name of the statement
+ * @param {Function} Factory function(application), return a function for
+ * the statement.
+ */
+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] = angular.bind(self, 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} The name of the matcher
+ * @param {Function} The matching function(expected).
+ */
+angular.scenario.matcher = function(name, fn) {
+ angular.scenario.matcher[name] = function(expected) {
+ var prefix = 'expect ' + this.future.name + ' ';
+ if (this.inverse) {
+ prefix += 'not ';
+ }
+ this.addFuture(prefix + name + ' ' + angular.toJson(expected),
+ angular.bind(this, function(done) {
+ this.actual = this.future.value;
+ if ((this.inverse && fn.call(this, expected)) ||
+ (!this.inverse && !fn.call(this, expected))) {
+ this.error = 'expected ' + angular.toJson(expected) +
+ ' but was ' + angular.toJson(this.actual);
+ }
+ done(this.error);
+ })
+ );
+ };
+};
+
+/**
+ * Iterates through list with iterator function that must call the
+ * continueFunction to continute iterating.
+ *
+ * @param {Array} list to iterate over
+ * @param {Function} Callback function(value, continueFunction)
+ * @param {Function} Callback function(error, result) called when iteration
+ * finishes or an error occurs.
+ */
+function asyncForEach(list, iterator, done) {
+ var i = 0;
+ function loop(error) {
+ if (error || i >= list.length) {
+ done(error);
+ } else {
+ try {
+ iterator(list[i++], loop);
+ } catch (e) {
+ done(e);
+ }
+ }
+ }
+ loop();
+}