'use strict'; /** * Maintains an object tree from the runner events. * * @param {Object} runner The scenario Runner instance to connect to. * * TODO(esprehn): Every output type creates one of these, but we probably * want one global shared instance. Need to handle events better too * so the HTML output doesn't need to do spec model.getSpec(spec.id) * silliness. * * TODO(vojta) refactor on, emit methods (from all objects) - use inheritance */ angular.scenario.ObjectModel = function(runner) { var self = this; this.specMap = {}; this.listeners = []; this.value = { name: '', children: {} }; runner.on('SpecBegin', function(spec) { var block = self.value, definitions = []; angular.forEach(self.getDefinitionPath(spec), function(def) { if (!block.children[def.name]) { block.children[def.name] = { id: def.id, name: def.name, children: {}, specs: {} }; } block = block.children[def.name]; definitions.push(def.name); }); var it = self.specMap[spec.id] = block.specs[spec.name] = new angular.scenario.ObjectModel.Spec(spec.id, spec.name, definitions); // forward the event self.emit('SpecBegin', it); }); runner.on('SpecError', function(spec, error) { var it = self.getSpec(spec.id); it.status = 'error'; it.error = error; // forward the event self.emit('SpecError', it, error); }); runner.on('SpecEnd', function(spec) { var it = self.getSpec(spec.id); complete(it); // forward the event self.emit('SpecEnd', it); }); runner.on('StepBegin', function(spec, step) { var it = self.getSpec(spec.id); var step = new angular.scenario.ObjectModel.Step(step.name); it.steps.push(step); // forward the event self.emit('StepBegin', it, step); }); runner.on('StepEnd', function(spec) { var it = self.getSpec(spec.id); var step = it.getLastStep(); if (step.name !== step.name) throw 'Events fired in the wrong order. Step names don\'t match.'; complete(step); // forward the event self.emit('StepEnd', it, step); }); runner.on('StepFailure', function(spec, step, error) { var it = self.getSpec(spec.id), modelStep = it.getLastStep(); modelStep.setErrorStatus('failure', error, step.line()); it.setStatusFromStep(modelStep); // forward the event self.emit('StepFailure', it, modelStep, error); }); runner.on('StepError', function(spec, step, error) { var it = self.getSpec(spec.id), modelStep = it.getLastStep(); modelStep.setErrorStatus('error', error, step.line()); it.setStatusFromStep(modelStep); // forward the event self.emit('StepError', it, modelStep, error); }); runner.on('RunnerBegin', function() { self.emit('RunnerBegin'); }); runner.on('RunnerEnd', function() { self.emit('RunnerEnd'); }); function complete(item) { item.endTime = new Date().getTime(); item.duration = item.endTime - item.startTime; item.status = item.status || 'success'; } }; /** * Adds a listener for an event. * * @param {string} eventName Name of the event to add a handler for * @param {function()} listener Function that will be called when event is fired */ angular.scenario.ObjectModel.prototype.on = function(eventName, listener) { eventName = eventName.toLowerCase(); this.listeners[eventName] = this.listeners[eventName] || []; this.listeners[eventName].push(listener); }; /** * Emits an event which notifies listeners and passes extra * arguments. * * @param {string} eventName Name of the event to fire. */ angular.scenario.ObjectModel.prototype.emit = function(eventName) { var self = this, args = Array.prototype.slice.call(arguments, 1), eventName = eventName.toLowerCase(); if (this.listeners[eventName]) { angular.forEach(this.listeners[eventName], function(listener) { listener.apply(self, args); }); } }; /** * Computes the path of definition describe blocks that wrap around * this spec. * * @param spec Spec to compute the path for. * @return {Array} The describe block path */ angular.scenario.ObjectModel.prototype.getDefinitionPath = function(spec) { var path = []; var currentDefinition = spec.definition; while (currentDefinition && currentDefinition.name) { path.unshift(currentDefinition); currentDefinition = currentDefinition.parent; } return path; }; /** * Gets a spec by id. * * @param {string} The id of the spec to get the object for. * @return {Object} the Spec instance */ angular.scenario.ObjectModel.prototype.getSpec = function(id) { return this.specMap[id]; }; /** * A single it block. * * @param {string} id Id of the spec * @param {string} name Name of the spec * @param {Array=} definitionNames List of all describe block names that wrap this spec */ angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) { this.id = id; this.name = name; this.startTime = new Date().getTime(); this.steps = []; this.fullDefinitionName = (definitionNames || []).join(' '); }; /** * Adds a new step to the Spec. * * @param {string} step Name of the step (really name of the future) * @return {Object} the added step */ angular.scenario.ObjectModel.Spec.prototype.addStep = function(name) { var step = new angular.scenario.ObjectModel.Step(name); this.steps.push(step); return step; }; /** * Gets the most recent step. * * @return {Object} the step */ angular.scenario.ObjectModel.Spec.prototype.getLastStep = function() { return this.steps[this.steps.length-1]; }; /** * Set status of the Spec from given Step * * @param {angular.scenario.ObjectModel.Step} step */ angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) { if (!this.status || step.status == 'error') { this.status = step.status; this.error = step.error; this.line = step.line; } }; /** * A single step inside a Spec. * * @param {string} step Name of the step */ angular.scenario.ObjectModel.Step = function(name) { this.name = name; this.startTime = new Date().getTime(); }; /** * Helper method for setting all error status related properties * * @param {string} status * @param {string} error * @param {string} line */ angular.scenario.ObjectModel.Step.prototype.setErrorStatus = function(status, error, line) { this.status = status; this.error = error; this.line = line; };