X Tutup
Skip to content

Commit 2115db6

Browse files
committed
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.
1 parent 9c8b180 commit 2115db6

File tree

15 files changed

+429
-138
lines changed

15 files changed

+429
-138
lines changed

css/angular-scenario.css

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ body {
8989
border-radius: 8px 0 0 8px;
9090
-webkit-border-radius: 8px 0 0 8px;
9191
-moz-border-radius: 8px 0 0 8px;
92+
cursor: pointer;
93+
}
94+
95+
.test-info:hover .test-name {
96+
text-decoration: underline;
97+
}
98+
99+
.test-info .closed:before {
100+
content: '\25b8\00A0';
101+
}
102+
103+
.test-info .open:before {
104+
content: '\25be\00A0';
105+
font-weight: bold;
92106
}
93107

94108
.test-it ol {
@@ -111,6 +125,21 @@ body {
111125
padding: 4px;
112126
}
113127

128+
.test-actions .test-title,
129+
.test-actions .test-result {
130+
display: table-cell;
131+
padding-left: 0.5em;
132+
padding-right: 0.5em;
133+
}
134+
135+
.test-actions {
136+
display: table;
137+
}
138+
139+
.test-actions li {
140+
display: table-row;
141+
}
142+
114143
.timer-result {
115144
width: 4em;
116145
padding: 0 10px;
@@ -121,6 +150,7 @@ body {
121150
.test-it pre,
122151
.test-actions pre {
123152
clear: left;
153+
color: black;
124154
margin-left: 6em;
125155
}
126156

@@ -132,6 +162,11 @@ body {
132162
content: '\00bb\00A0';
133163
}
134164

165+
.scrollpane {
166+
max-height: 20em;
167+
overflow: auto;
168+
}
169+
135170
/** Colors */
136171

137172
#header {

scenario/widgets-scenario.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ describe('widgets', function() {
2323
select('select').option('B');
2424
expect(binding('select')).toEqual('B');
2525

26-
2726
select('multiselect').options('A', 'C');
2827
expect(binding('multiselect').fromJson()).toEqual(['A', 'C']);
2928

src/scenario/Describe.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,10 @@ angular.scenario.Describe.prototype.it = function(name, body) {
7878
var self = this;
7979
this.its.push({
8080
definition: this,
81-
name: name,
82-
fn: function() {
83-
self.setupBefore.call(this);
84-
body.call(this);
85-
self.setupAfter.call(this);
86-
}
81+
name: name,
82+
before: self.setupBefore,
83+
body: body,
84+
after: self.setupAfter
8785
});
8886
};
8987

src/scenario/Future.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
/**
22
* A future action in a spec.
3+
*
4+
* @param {String} name of the future action
5+
* @param {Function} future callback(error, result)
6+
* @param {String} Optional. function that returns the file/line number.
37
*/
4-
angular.scenario.Future = function(name, behavior) {
8+
angular.scenario.Future = function(name, behavior, line) {
59
this.name = name;
610
this.behavior = behavior;
711
this.fulfilled = false;
812
this.value = undefined;
913
this.parser = angular.identity;
14+
this.line = line || function() { return ''; };
1015
};
1116

1217
/**
@@ -15,18 +20,19 @@ angular.scenario.Future = function(name, behavior) {
1520
* @param {Function} Callback function(error, result)
1621
*/
1722
angular.scenario.Future.prototype.execute = function(doneFn) {
18-
this.behavior(angular.bind(this, function(error, result) {
19-
this.fulfilled = true;
23+
var self = this;
24+
this.behavior(function(error, result) {
25+
self.fulfilled = true;
2026
if (result) {
2127
try {
22-
result = this.parser(result);
28+
result = self.parser(result);
2329
} catch(e) {
2430
error = e;
2531
}
2632
}
27-
this.value = error || result;
33+
self.value = error || result;
2834
doneFn(error, result);
29-
}));
35+
});
3036
};
3137

3238
/**

src/scenario/HtmlUI.js

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,30 @@ angular.scenario.ui.Html = function(context) {
2020
);
2121
};
2222

23+
/**
24+
* The severity order of an error.
25+
*/
26+
angular.scenario.ui.Html.SEVERITY = ['pending', 'success', 'failure', 'error'];
27+
2328
/**
2429
* Adds a new spec to the UI.
2530
*
2631
* @param {Object} The spec object created by the Describe object.
2732
*/
2833
angular.scenario.ui.Html.prototype.addSpec = function(spec) {
34+
var self = this;
2935
var specContext = this.findContext(spec.definition);
3036
specContext.find('> .tests').append(
3137
'<li class="status-pending test-it"></li>'
3238
);
3339
specContext = specContext.find('> .tests li:last');
34-
return new angular.scenario.ui.Html.Spec(specContext, spec.name,
35-
angular.bind(this, function(status) {
36-
status = this.context.find('#status-legend .status-' + status);
40+
return new angular.scenario.ui.Html.Spec(specContext, spec.name,
41+
function(status) {
42+
status = self.context.find('#status-legend .status-' + status);
3743
var parts = status.text().split(' ');
3844
var value = (parts[0] * 1) + 1;
3945
status.text(value + ' ' + parts[1]);
40-
})
46+
}
4147
);
4248
};
4349

@@ -47,27 +53,28 @@ angular.scenario.ui.Html.prototype.addSpec = function(spec) {
4753
* @param {Object} The definition created by the Describe object.
4854
*/
4955
angular.scenario.ui.Html.prototype.findContext = function(definition) {
56+
var self = this;
5057
var path = [];
5158
var currentContext = this.context.find('#specs');
5259
var currentDefinition = definition;
5360
while (currentDefinition && currentDefinition.name) {
5461
path.unshift(currentDefinition);
5562
currentDefinition = currentDefinition.parent;
5663
}
57-
angular.foreach(path, angular.bind(this, function(defn) {
64+
angular.foreach(path, function(defn) {
5865
var id = 'describe-' + defn.id;
59-
if (!this.context.find('#' + id).length) {
66+
if (!self.context.find('#' + id).length) {
6067
currentContext.find('> .test-children').append(
6168
'<div class="test-describe" id="' + id + '">' +
6269
' <h2></h2>' +
6370
' <div class="test-children"></div>' +
6471
' <ul class="tests"></ul>' +
6572
'</div>'
6673
);
67-
this.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
74+
self.context.find('#' + id).find('> h2').text('describe: ' + defn.name);
6875
}
69-
currentContext = this.context.find('#' + id);
70-
}));
76+
currentContext = self.context.find('#' + id);
77+
});
7178
return this.context.find('#describe-' + definition.id);
7279
};
7380

@@ -90,23 +97,45 @@ angular.scenario.ui.Html.Spec = function(context, name, doneFn) {
9097
' <span class="test-name"></span>' +
9198
' </p>' +
9299
'</div>' +
93-
'<ol class="test-actions">' +
94-
'</ol>'
100+
'<div class="scrollpane">' +
101+
' <ol class="test-actions">' +
102+
' </ol>' +
103+
'</div>'
95104
);
105+
context.find('> .test-info').click(function() {
106+
var scrollpane = context.find('> .scrollpane');
107+
var actions = scrollpane.find('> .test-actions');
108+
var name = context.find('> .test-info .test-name');
109+
if (actions.find(':visible').length) {
110+
actions.hide();
111+
name.removeClass('open').addClass('closed');
112+
} else {
113+
actions.show();
114+
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
115+
name.removeClass('closed').addClass('open');
116+
}
117+
});
96118
context.find('> .test-info .test-name').text('it ' + name);
97119
};
98120

99121
/**
100122
* Adds a new Step to this spec and returns it.
101123
*
102124
* @param {String} The name of the step.
125+
* @param {Function} function() that returns a string with the file/line number
126+
* where the step was added from.
103127
*/
104-
angular.scenario.ui.Html.Spec.prototype.addStep = function(name) {
105-
this.context.find('> .test-actions').append('<li class="status-pending"></li>');
106-
var stepContext = this.context.find('> .test-actions li:last');
128+
angular.scenario.ui.Html.Spec.prototype.addStep = function(name, location) {
129+
this.context.find('> .scrollpane .test-actions').append('<li class="status-pending"></li>');
130+
var stepContext = this.context.find('> .scrollpane .test-actions li:last');
107131
var self = this;
108-
return new angular.scenario.ui.Html.Step(stepContext, name, function(status) {
109-
self.status = status;
132+
return new angular.scenario.ui.Html.Step(stepContext, name, location, function(status) {
133+
if (indexOf(angular.scenario.ui.Html.SEVERITY, status) >
134+
indexOf(angular.scenario.ui.Html.SEVERITY, self.status)) {
135+
self.status = status;
136+
}
137+
var scrollpane = self.context.find('> .scrollpane');
138+
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
110139
});
111140
};
112141

@@ -118,22 +147,19 @@ angular.scenario.ui.Html.Spec.prototype.complete = function() {
118147
var endTime = new Date().getTime();
119148
this.context.find("> .test-info .timer-result").
120149
text((endTime - this.startTime) + "ms");
150+
if (this.status === 'success') {
151+
this.context.find('> .test-info .test-name').addClass('closed');
152+
this.context.find('> .scrollpane .test-actions').hide();
153+
}
121154
};
122155

123156
/**
124157
* Finishes the spec, possibly with an error.
125158
*
126159
* @param {Object} An optional error
127160
*/
128-
angular.scenario.ui.Html.Spec.prototype.finish = function(error) {
161+
angular.scenario.ui.Html.Spec.prototype.finish = function() {
129162
this.complete();
130-
if (error) {
131-
if (this.status !== 'failure') {
132-
this.status = 'error';
133-
}
134-
this.context.append('<pre></pre>');
135-
this.context.find('pre:first').text(error.stack || error.toString());
136-
}
137163
this.context.addClass('status-' + this.status);
138164
this.doneFn(this.status);
139165
};
@@ -144,36 +170,50 @@ angular.scenario.ui.Html.Spec.prototype.finish = function(error) {
144170
* @param {Object} Required error
145171
*/
146172
angular.scenario.ui.Html.Spec.prototype.error = function(error) {
147-
this.finish(error);
173+
this.status = 'error';
174+
this.context.append('<pre></pre>');
175+
this.context.find('> pre').text(formatException(error));
176+
this.finish();
148177
};
149178

150179
/**
151180
* A single step inside an it block (or a before/after function).
152181
*
153182
* @param {Object} The jQuery object for the context of the step.
154183
* @param {String} The name of the step.
184+
* @param {Function} function() that returns file/line number of step.
155185
* @param {Function} Callback function(status) to call when complete.
156186
*/
157-
angular.scenario.ui.Html.Step = function(context, name, doneFn) {
187+
angular.scenario.ui.Html.Step = function(context, name, location, doneFn) {
158188
this.context = context;
159189
this.name = name;
190+
this.location = location;
160191
this.startTime = new Date().getTime();
161192
this.doneFn = doneFn;
162193
context.append(
163-
'<span class="timer-result"></span>' +
164-
'<span class="test-title"></span>'
194+
'<div class="timer-result"></div>' +
195+
'<div class="test-title"></div>'
165196
);
166197
context.find('> .test-title').text(name);
198+
var scrollpane = context.parents('.scrollpane');
199+
scrollpane.attr('scrollTop', scrollpane.attr('scrollHeight'));
167200
};
168201

169202
/**
170203
* Completes the step and sets the timer value.
171204
*/
172-
angular.scenario.ui.Html.Step.prototype.complete = function() {
205+
angular.scenario.ui.Html.Step.prototype.complete = function(error) {
173206
this.context.removeClass('status-pending');
174207
var endTime = new Date().getTime();
175208
this.context.find(".timer-result").
176209
text((endTime - this.startTime) + "ms");
210+
if (error) {
211+
if (!this.context.find('.test-title pre').length) {
212+
this.context.find('.test-title').append('<pre></pre>');
213+
}
214+
var message = _jQuery.trim(this.location() + '\n\n' + formatException(error));
215+
this.context.find('.test-title pre').text(message);
216+
}
177217
};
178218

179219
/**
@@ -182,7 +222,7 @@ angular.scenario.ui.Html.Step.prototype.complete = function() {
182222
* @param {Object} An optional error
183223
*/
184224
angular.scenario.ui.Html.Step.prototype.finish = function(error) {
185-
this.complete();
225+
this.complete(error);
186226
if (error) {
187227
this.context.addClass('status-failure');
188228
this.doneFn('failure');
@@ -198,7 +238,7 @@ angular.scenario.ui.Html.Step.prototype.finish = function(error) {
198238
* @param {Object} Required error
199239
*/
200240
angular.scenario.ui.Html.Step.prototype.error = function(error) {
201-
this.complete();
241+
this.complete(error);
202242
this.context.addClass('status-error');
203243
this.doneFn('error');
204244
};

src/scenario/Runner.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClas
9191
});
9292
angular.foreach(angular.scenario.dsl, function(fn, key) {
9393
self.$window[key] = function() {
94+
var line = callerFile(3);
9495
var scope = angular.scope(runner);
9596

9697
// Make the dsl accessible on the current chain
@@ -103,10 +104,12 @@ angular.scenario.Runner.prototype.run = function(ui, application, specRunnerClas
103104

104105
// Make these methods work on the current chain
105106
scope.addFuture = function() {
106-
return angular.scenario.SpecRunner.prototype.addFuture.apply(scope, arguments);
107+
Array.prototype.push.call(arguments, line);
108+
return specRunnerClass.prototype.addFuture.apply(scope, arguments);
107109
};
108110
scope.addFutureAction = function() {
109-
return angular.scenario.SpecRunner.prototype.addFutureAction.apply(scope, arguments);
111+
Array.prototype.push.call(arguments, line);
112+
return specRunnerClass.prototype.addFutureAction.apply(scope, arguments);
110113
};
111114

112115
return scope.dsl[key].apply(scope, arguments);

0 commit comments

Comments
 (0)
X Tutup