Files
angular.js/src/scenario/SpecRunner.js
Elliott Sprehn e7e894a2e3 Significantly clean up the way the scenario DSL works and implement many more DSL statements.
- "this" always means the current chain scope inside a DSL

- addFutureAction callbacks now take ($window, $document, done)

- $document has a special method elements() that uses the currently selected nodes in the document as defined by using() statements.

- $document.elements() allows placeholder insertion into selectors to make them more readable.
  ex. $document.elements('input[name="$1"]', myVar) will substitute the value of myVar for $1 in the selector. Subsequent arguments are $2 and so on.

- $document.elements() results have a special method trigger(event) which should be used to events. This method implements some hacks to make sure browser UI controls update and the correct angular events fire.

- futures now allow custom formatting. By default any chain that results in a future can use toJson() or fromJson() to convert the future value to and from json. A custom parser can be provided with parsedWith(fn) where fn is a callback(value) that must return the parsed result.

Note: The entire widgets.html UI is now able to be controlled and asserted through DSL statements!!! Victory! :)
2010-10-19 00:45:38 -07:00

127 lines
3.7 KiB
JavaScript

/**
* This class is the "this" of the it/beforeEach/afterEach method.
* Responsibilities:
* - "this" for it/beforeEach/afterEach
* - keep state for single it/beforeEach/afterEach execution
* - keep track of all of the futures to execute
* - run single spec (execute each future)
*/
angular.scenario.SpecRunner = function() {
this.futures = [];
};
/**
* Executes a spec which is an it block with associated before/after functions
* based on the describe nesting.
*
* @param {Object} An angular.scenario.UI implementation
* @param {Object} A spec object
* @param {Object} An angular.scenario.Application instance
* @param {Function} Callback function that is called when the spec finshes.
*/
angular.scenario.SpecRunner.prototype.run = function(ui, spec, specDone) {
var specUI = ui.addSpec(spec);
try {
spec.fn.call(this);
} catch (e) {
specUI.error(e);
specDone();
return;
}
asyncForEach(
this.futures,
function(future, futureDone) {
var stepUI = specUI.addStep(future.name);
try {
future.execute(function(error) {
stepUI.finish(error);
futureDone(error);
});
} catch (e) {
stepUI.error(e);
throw e;
}
},
function(e) {
specUI.finish(e);
specDone();
}
);
};
/**
* Adds a new future action.
*
* @param {String} Name of the future
* @param {Function} Behavior of the future
*/
angular.scenario.SpecRunner.prototype.addFuture = function(name, behavior) {
var future = new angular.scenario.Future(name, angular.bind(this, behavior));
this.futures.push(future);
return future;
};
/**
* Adds a new future action to be executed on the application window.
*
* @param {String} Name of the future
* @param {Function} Behavior of the future
*/
angular.scenario.SpecRunner.prototype.addFutureAction = function(name, behavior) {
return this.addFuture(name, function(done) {
this.application.executeAction(angular.bind(this, function($window, $document) {
$document.elements = angular.bind(this, function(selector) {
var args = Array.prototype.slice.call(arguments, 1);
if (this.selector) {
selector = this.selector + ' ' + (selector || '');
}
angular.foreach(args, function(value, index) {
selector = selector.replace('$' + (index + 1), value);
});
var result = $document.find(selector);
if (!result.length) {
throw {
type: 'selector',
message: 'Selector ' + selector + ' did not match any elements.'
};
}
result.trigger = function(type) {
result.each(function(index, node) {
var element = $window.angular.element(node);
//TODO(esprehn): HACK!!! Something is broken in angular event dispatching
// and if the real jQuery is used we need to set the attribtue after too
if (angular.isDefined(element.selector)) {
if (type === 'click' && node.nodeName.toLowerCase() === 'input') {
element.attr('checked', !element.attr('checked'));
}
}
//TODO(esprehn): HACK!! See above comment.
element.trigger(type);
if (angular.isDefined(element.selector)) {
if (type === 'click' && node.nodeName.toLowerCase() === 'input') {
element.attr('checked', !element.attr('checked'));
}
}
});
};
return result;
});
try {
behavior.call(this, $window, $document, done);
} catch(e) {
if (e.type && e.type === 'selector') {
done(e.message);
} else {
throw e;
}
}
}));
});
};