diff options
Diffstat (limited to 'src/ngScenario/Runner.js')
| -rw-r--r-- | src/ngScenario/Runner.js | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/src/ngScenario/Runner.js b/src/ngScenario/Runner.js new file mode 100644 index 00000000..06ad3aa1 --- /dev/null +++ b/src/ngScenario/Runner.js @@ -0,0 +1,227 @@ +'use strict'; + +/** + * Runner for scenarios + * + * Has to be initialized before any test is loaded, + * because it publishes the API into window (global space). + */ +angular.scenario.Runner = function($window) { + this.listeners = []; + this.$window = $window; + this.rootDescribe = new angular.scenario.Describe(); + this.currentDescribe = this.rootDescribe; + this.api = { + it: this.it, + iit: this.iit, + xit: angular.noop, + describe: this.describe, + ddescribe: this.ddescribe, + xdescribe: angular.noop, + beforeEach: this.beforeEach, + afterEach: this.afterEach + }; + angular.forEach(this.api, angular.bind(this, function(fn, key) { + this.$window[key] = angular.bind(this, fn); + })); +}; + +/** + * Emits an event which notifies listeners and passes extra + * arguments. + * + * @param {string} eventName Name of the event to fire. + */ +angular.scenario.Runner.prototype.emit = function(eventName) { + var self = this; + var args = Array.prototype.slice.call(arguments, 1); + eventName = eventName.toLowerCase(); + if (!this.listeners[eventName]) + return; + angular.forEach(this.listeners[eventName], function(listener) { + listener.apply(self, args); + }); +}; + +/** + * Adds a listener for an event. + * + * @param {string} eventName The name of the event to add a handler for + * @param {string} listener The fn(...) that takes the extra arguments from emit() + */ +angular.scenario.Runner.prototype.on = function(eventName, listener) { + eventName = eventName.toLowerCase(); + this.listeners[eventName] = this.listeners[eventName] || []; + this.listeners[eventName].push(listener); +}; + +/** + * Defines a describe block of a spec. + * + * @see Describe.js + * + * @param {string} name Name of the block + * @param {function()} body Body of the block + */ +angular.scenario.Runner.prototype.describe = function(name, body) { + var self = this; + this.currentDescribe.describe(name, function() { + var parentDescribe = self.currentDescribe; + self.currentDescribe = this; + try { + body.call(this); + } finally { + self.currentDescribe = parentDescribe; + } + }); +}; + +/** + * Same as describe, but makes ddescribe the only blocks to run. + * + * @see Describe.js + * + * @param {string} name Name of the block + * @param {function()} body Body of the block + */ +angular.scenario.Runner.prototype.ddescribe = function(name, body) { + var self = this; + this.currentDescribe.ddescribe(name, function() { + var parentDescribe = self.currentDescribe; + self.currentDescribe = this; + try { + body.call(this); + } finally { + self.currentDescribe = parentDescribe; + } + }); +}; + +/** + * Defines a test in a describe block of a spec. + * + * @see Describe.js + * + * @param {string} name Name of the block + * @param {function()} body Body of the block + */ +angular.scenario.Runner.prototype.it = function(name, body) { + this.currentDescribe.it(name, body); +}; + +/** + * Same as it, but makes iit tests the only tests to run. + * + * @see Describe.js + * + * @param {string} name Name of the block + * @param {function()} body Body of the block + */ +angular.scenario.Runner.prototype.iit = function(name, body) { + this.currentDescribe.iit(name, body); +}; + +/** + * Defines a function to be called before each it block in the describe + * (and before all nested describes). + * + * @see Describe.js + * + * @param {function()} Callback to execute + */ +angular.scenario.Runner.prototype.beforeEach = function(body) { + this.currentDescribe.beforeEach(body); +}; + +/** + * Defines a function to be called after each it block in the describe + * (and before all nested describes). + * + * @see Describe.js + * + * @param {function()} Callback to execute + */ +angular.scenario.Runner.prototype.afterEach = function(body) { + this.currentDescribe.afterEach(body); +}; + +/** + * Creates a new spec runner. + * + * @private + * @param {Object} scope parent scope + */ +angular.scenario.Runner.prototype.createSpecRunner_ = function(scope) { + var child = scope.$new(); + var Cls = angular.scenario.SpecRunner; + + // Export all the methods to child scope manually as now we don't mess controllers with scopes + // TODO(vojta): refactor scenario runner so that these objects are not tightly coupled as current + for (var name in Cls.prototype) + child[name] = angular.bind(child, Cls.prototype[name]); + + Cls.call(child); + return child; +}; + +/** + * Runs all the loaded tests with the specified runner class on the + * provided application. + * + * @param {angular.scenario.Application} application App to remote control. + */ +angular.scenario.Runner.prototype.run = function(application) { + var self = this; + var $root = angular.injector(['ng']).get('$rootScope'); + angular.extend($root, this); + angular.forEach(angular.scenario.Runner.prototype, function(fn, name) { + $root[name] = angular.bind(self, fn); + }); + $root.application = application; + $root.emit('RunnerBegin'); + asyncForEach(this.rootDescribe.getSpecs(), function(spec, specDone) { + var dslCache = {}; + var runner = self.createSpecRunner_($root); + angular.forEach(angular.scenario.dsl, function(fn, key) { + dslCache[key] = fn.call($root); + }); + angular.forEach(angular.scenario.dsl, function(fn, key) { + self.$window[key] = function() { + var line = callerFile(3); + var scope = runner.$new(); + + // Make the dsl accessible on the current chain + scope.dsl = {}; + angular.forEach(dslCache, function(fn, key) { + scope.dsl[key] = function() { + return dslCache[key].apply(scope, arguments); + }; + }); + + // Make these methods work on the current chain + scope.addFuture = function() { + Array.prototype.push.call(arguments, line); + return angular.scenario.SpecRunner. + prototype.addFuture.apply(scope, arguments); + }; + scope.addFutureAction = function() { + Array.prototype.push.call(arguments, line); + return angular.scenario.SpecRunner. + prototype.addFutureAction.apply(scope, arguments); + }; + + return scope.dsl[key].apply(scope, arguments); + }; + }); + runner.run(spec, function() { + runner.$destroy(); + specDone.apply(this, arguments); + }); + }, + function(error) { + if (error) { + self.emit('RunnerError', error); + } + self.emit('RunnerEnd'); + }); +}; |
