aboutsummaryrefslogtreecommitdiffstats
path: root/src/scenario/HtmlUI.js
diff options
context:
space:
mode:
authorElliott Sprehn2010-10-19 13:17:49 -0700
committerElliott Sprehn2010-10-20 14:38:00 -0700
commit2115db69035c5993533fe7a3825e64cf6e9068ad (patch)
tree796a502b28cd2bda8108a672eac0bf28c8bc21d4 /src/scenario/HtmlUI.js
parent9c8b1800b90e14b643bab6ada8e96f8f850e84a6 (diff)
downloadangular.js-2115db69035c5993533fe7a3825e64cf6e9068ad.tar.bz2
Lots of stability and performance updates and UI polish too.
Polish the Scenario Runner UI to include: - a scroll pane that steps appear in since the list can be very long - Collapse successful tests - Show the line where the DSL statements were when there's an error (Chrome, Firefox) Also: - Remove lots angular.bind calls to reduce the amount of stack space used. - Use setTimeout(...,0) to schedule the next future to let the browser breathe and have it repaint the steps. Also prevents overflowing the stack when an it() creates many futures. - Run afterEach() handlers even if the it() block fails. - Make navigateTo() take a function as the second argument so you can compute a URL in the future. - Add wait() DSL statement to allow interactive debugging of tests. - Allow custom jQuery selectors with element(...).query(fn) DSL statement. Known Issues: - All afterEach() handlers run even if a beforeEach() handler fails. Only after handlers for the same level as the failure and above should run.
Diffstat (limited to 'src/scenario/HtmlUI.js')
-rw-r--r--src/scenario/HtmlUI.js102
1 files changed, 71 insertions, 31 deletions
diff --git a/src/scenario/HtmlUI.js b/src/scenario/HtmlUI.js
index a93ed1e3..78fe8c33 100644
--- a/src/scenario/HtmlUI.js
+++ b/src/scenario/HtmlUI.js
@@ -21,23 +21,29 @@ angular.scenario.ui.Html = function(context) {
};
/**
+ * The severity order of an error.
+ */
+angular.scenario.ui.Html.SEVERITY = ['pending', 'success', 'failure', 'error'];
+
+/**
* Adds a new spec to the UI.
*
* @param {Object} The spec object created by the Describe object.
*/
angular.scenario.ui.Html.prototype.addSpec = function(spec) {
+ var self = this;
var specContext = this.findContext(spec.definition);
specContext.find('> .tests').append(
'<li class="status-pending test-it"></li>'
);
specContext = specContext.find('> .tests li:last');
- return new angular.scenario.ui.Html.Spec(specContext, spec.name,
- angular.bind(this, function(status) {
- status = this.context.find('#status-legend .status-' + status);
+ return new angular.scenario.ui.Html.Spec(specContext, spec.name,
+ function(status) {
+ status = self.context.find('#status-legend .status-' + status);
var parts = status.text().split(' ');
var value = (parts[0] * 1) + 1;
status.text(value + ' ' + parts[1]);
- })
+ }
);
};
@@ -47,6 +53,7 @@ angular.scenario.ui.Html.prototype.addSpec = function(spec) {
* @param {Object} The definition created by the Describe object.
*/
angular.scenario.ui.Html.prototype.findContext = function(definition) {
+ var self = this;
var path = [];
var currentContext = this.context.find('#specs');
var currentDefinition = definition;
@@ -54,9 +61,9 @@ angular.scenario.ui.Html.prototype.findContext = function(definition) {
path.unshift(currentDefinition);
currentDefinition = currentDefinition.parent;
}
- angular.foreach(path, angular.bind(this, function(defn) {
+ angular.foreach(path, function(defn) {
var id = 'describe-' + defn.id;
- if (!this.context.find('#' + id).length) {
+ if (!self.context.find('#' + id).length) {
currentContext.find('> .test-children').append(
'<div class="test-describe" id="' + id + '">' +
' <h2></h2>' +
@@ -64,10 +71,10 @@ angular.scenario.ui.Html.prototype.findContext = function(definition) {
' <ul class="tests"></ul>' +
'</div>'
);
- this.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
+ self.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
}
- currentContext = this.context.find('#' + id);
- }));
+ currentContext = self.context.find('#' + id);
+ });
return this.context.find('#describe-' + definition.id);
};
@@ -90,9 +97,24 @@ angular.scenario.ui.Html.Spec = function(context, name, doneFn) {
' <span class="test-name"></span>' +
' </p>' +
'</div>' +
- '<ol class="test-actions">' +
- '</ol>'
+ '<div class="scrollpane">' +
+ ' <ol class="test-actions">' +
+ ' </ol>' +
+ '</div>'
);
+ context.find('> .test-info').click(function() {
+ var scrollpane = context.find('> .scrollpane');
+ var actions = scrollpane.find('> .test-actions');
+ var name = context.find('> .test-info .test-name');
+ if (actions.find(':visible').length) {
+ actions.hide();
+ name.removeClass('open').addClass('closed');
+ } else {
+ actions.show();
+ scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
+ name.removeClass('closed').addClass('open');
+ }
+ });
context.find('> .test-info .test-name').text('it ' + name);
};
@@ -100,13 +122,20 @@ angular.scenario.ui.Html.Spec = function(context, name, doneFn) {
* Adds a new Step to this spec and returns it.
*
* @param {String} The name of the step.
+ * @param {Function} function() that returns a string with the file/line number
+ * where the step was added from.
*/
-angular.scenario.ui.Html.Spec.prototype.addStep = function(name) {
- this.context.find('> .test-actions').append('<li class="status-pending"></li>');
- var stepContext = this.context.find('> .test-actions li:last');
+angular.scenario.ui.Html.Spec.prototype.addStep = function(name, location) {
+ this.context.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
+ var stepContext = this.context.find('> .scrollpane .test-actions li:last');
var self = this;
- return new angular.scenario.ui.Html.Step(stepContext, name, function(status) {
- self.status = status;
+ return new angular.scenario.ui.Html.Step(stepContext, name, location, function(status) {
+ if (indexOf(angular.scenario.ui.Html.SEVERITY, status) >
+ indexOf(angular.scenario.ui.Html.SEVERITY, self.status)) {
+ self.status = status;
+ }
+ var scrollpane = self.context.find('> .scrollpane');
+ scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
});
};
@@ -118,6 +147,10 @@ angular.scenario.ui.Html.Spec.prototype.complete = function() {
var endTime = new Date().getTime();
this.context.find("> .test-info .timer-result").
text((endTime - this.startTime) + "ms");
+ if (this.status === 'success') {
+ this.context.find('> .test-info .test-name').addClass('closed');
+ this.context.find('> .scrollpane .test-actions').hide();
+ }
};
/**
@@ -125,15 +158,8 @@ angular.scenario.ui.Html.Spec.prototype.complete = function() {
*
* @param {Object} An optional error
*/
-angular.scenario.ui.Html.Spec.prototype.finish = function(error) {
+angular.scenario.ui.Html.Spec.prototype.finish = function() {
this.complete();
- if (error) {
- if (this.status !== 'failure') {
- this.status = 'error';
- }
- this.context.append('<pre></pre>');
- this.context.find('pre:first').text(error.stack || error.toString());
- }
this.context.addClass('status-' + this.status);
this.doneFn(this.status);
};
@@ -144,7 +170,10 @@ angular.scenario.ui.Html.Spec.prototype.finish = function(error) {
* @param {Object} Required error
*/
angular.scenario.ui.Html.Spec.prototype.error = function(error) {
- this.finish(error);
+ this.status = 'error';
+ this.context.append('<pre></pre>');
+ this.context.find('> pre').text(formatException(error));
+ this.finish();
};
/**
@@ -152,28 +181,39 @@ angular.scenario.ui.Html.Spec.prototype.error = function(error) {
*
* @param {Object} The jQuery object for the context of the step.
* @param {String} The name of the step.
+ * @param {Function} function() that returns file/line number of step.
* @param {Function} Callback function(status) to call when complete.
*/
-angular.scenario.ui.Html.Step = function(context, name, doneFn) {
+angular.scenario.ui.Html.Step = function(context, name, location, doneFn) {
this.context = context;
this.name = name;
+ this.location = location;
this.startTime = new Date().getTime();
this.doneFn = doneFn;
context.append(
- '<span class="timer-result"></span>' +
- '<span class="test-title"></span>'
+ '<div class="timer-result"></div>' +
+ '<div class="test-title"></div>'
);
context.find('> .test-title').text(name);
+ var scrollpane = context.parents('.scrollpane');
+ scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
};
/**
* Completes the step and sets the timer value.
*/
-angular.scenario.ui.Html.Step.prototype.complete = function() {
+angular.scenario.ui.Html.Step.prototype.complete = function(error) {
this.context.removeClass('status-pending');
var endTime = new Date().getTime();
this.context.find(".timer-result").
text((endTime - this.startTime) + "ms");
+ if (error) {
+ if (!this.context.find('.test-title pre').length) {
+ this.context.find('.test-title').append('<pre></pre>');
+ }
+ var message = _jQuery.trim(this.location() + '\n\n' + formatException(error));
+ this.context.find('.test-title pre').text(message);
+ }
};
/**
@@ -182,7 +222,7 @@ angular.scenario.ui.Html.Step.prototype.complete = function() {
* @param {Object} An optional error
*/
angular.scenario.ui.Html.Step.prototype.finish = function(error) {
- this.complete();
+ this.complete(error);
if (error) {
this.context.addClass('status-failure');
this.doneFn('failure');
@@ -198,7 +238,7 @@ angular.scenario.ui.Html.Step.prototype.finish = function(error) {
* @param {Object} Required error
*/
angular.scenario.ui.Html.Step.prototype.error = function(error) {
- this.complete();
+ this.complete(error);
this.context.addClass('status-error');
this.doneFn('error');
};