diff options
| author | Elliott Sprehn | 2010-10-19 13:17:49 -0700 |
|---|---|---|
| committer | Elliott Sprehn | 2010-10-20 14:38:00 -0700 |
| commit | 2115db69035c5993533fe7a3825e64cf6e9068ad (patch) | |
| tree | 796a502b28cd2bda8108a672eac0bf28c8bc21d4 /src/scenario/HtmlUI.js | |
| parent | 9c8b1800b90e14b643bab6ada8e96f8f850e84a6 (diff) | |
| download | angular.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.js | 102 |
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'); }; |
