blob: 898108b77d6ad9d6ecdb417031dd913b7db71be0 [file] [log] [blame]
Nils Diewald19ccee92014-12-08 11:30:08 +00001/*
2Copyright (c) 2008-2014 Pivotal Labs
3
4Permission is hereby granted, free of charge, to any person obtaining
5a copy of this software and associated documentation files (the
6"Software"), to deal in the Software without restriction, including
7without limitation the rights to use, copy, modify, merge, publish,
8distribute, sublicense, and/or sell copies of the Software, and to
9permit persons to whom the Software is furnished to do so, subject to
10the following conditions:
11
12The above copyright notice and this permission notice shall be
13included in all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22*/
23jasmineRequire.html = function(j$) {
24 j$.ResultsNode = jasmineRequire.ResultsNode();
25 j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
26 j$.QueryString = jasmineRequire.QueryString();
27 j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
28};
29
30jasmineRequire.HtmlReporter = function(j$) {
31
32 var noopTimer = {
33 start: function() {},
34 elapsed: function() { return 0; }
35 };
36
37 function HtmlReporter(options) {
38 var env = options.env || {},
39 getContainer = options.getContainer,
40 createElement = options.createElement,
41 createTextNode = options.createTextNode,
42 onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
43 timer = options.timer || noopTimer,
44 results = [],
45 specsExecuted = 0,
46 failureCount = 0,
47 pendingSpecCount = 0,
48 htmlReporterMain,
49 symbols,
50 failedSuites = [];
51
52 this.initialize = function() {
53 clearPrior();
54 htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
55 createDom('div', {className: 'banner'},
56 createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}),
57 createDom('span', {className: 'version'}, j$.version)
58 ),
59 createDom('ul', {className: 'symbol-summary'}),
60 createDom('div', {className: 'alert'}),
61 createDom('div', {className: 'results'},
62 createDom('div', {className: 'failures'})
63 )
64 );
65 getContainer().appendChild(htmlReporterMain);
66
67 symbols = find('.symbol-summary');
68 };
69
70 var totalSpecsDefined;
71 this.jasmineStarted = function(options) {
72 totalSpecsDefined = options.totalSpecsDefined || 0;
73 timer.start();
74 };
75
76 var summary = createDom('div', {className: 'summary'});
77
78 var topResults = new j$.ResultsNode({}, '', null),
79 currentParent = topResults;
80
81 this.suiteStarted = function(result) {
82 currentParent.addChild(result, 'suite');
83 currentParent = currentParent.last();
84 };
85
86 this.suiteDone = function(result) {
87 if (result.status == 'failed') {
88 failedSuites.push(result);
89 }
90
91 if (currentParent == topResults) {
92 return;
93 }
94
95 currentParent = currentParent.parent;
96 };
97
98 this.specStarted = function(result) {
99 currentParent.addChild(result, 'spec');
100 };
101
102 var failures = [];
103 this.specDone = function(result) {
104 if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
105 console.error('Spec \'' + result.fullName + '\' has no expectations.');
106 }
107
108 if (result.status != 'disabled') {
109 specsExecuted++;
110 }
111
112 symbols.appendChild(createDom('li', {
113 className: noExpectations(result) ? 'empty' : result.status,
114 id: 'spec_' + result.id,
115 title: result.fullName
116 }
117 ));
118
119 if (result.status == 'failed') {
120 failureCount++;
121
122 var failure =
123 createDom('div', {className: 'spec-detail failed'},
124 createDom('div', {className: 'description'},
125 createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
126 ),
127 createDom('div', {className: 'messages'})
128 );
129 var messages = failure.childNodes[1];
130
131 for (var i = 0; i < result.failedExpectations.length; i++) {
132 var expectation = result.failedExpectations[i];
133 messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message));
134 messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack));
135 }
136
137 failures.push(failure);
138 }
139
140 if (result.status == 'pending') {
141 pendingSpecCount++;
142 }
143 };
144
145 this.jasmineDone = function() {
146 var banner = find('.banner');
147 banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
148
149 var alert = find('.alert');
150
151 alert.appendChild(createDom('span', { className: 'exceptions' },
152 createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'),
153 createDom('input', {
154 className: 'raise',
155 id: 'raise-exceptions',
156 type: 'checkbox'
157 })
158 ));
159 var checkbox = find('#raise-exceptions');
160
161 checkbox.checked = !env.catchingExceptions();
162 checkbox.onclick = onRaiseExceptionsClick;
163
164 if (specsExecuted < totalSpecsDefined) {
165 var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
166 alert.appendChild(
167 createDom('span', {className: 'bar skipped'},
168 createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
169 )
170 );
171 }
172 var statusBarMessage = '';
173 var statusBarClassName = 'bar ';
174
175 if (totalSpecsDefined > 0) {
176 statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
177 if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
178 statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
179 } else {
180 statusBarClassName += 'skipped';
181 statusBarMessage += 'No specs found';
182 }
183
184 alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
185
186 for(i = 0; i < failedSuites.length; i++) {
187 var failedSuite = failedSuites[i];
188 for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
189 var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
190 var errorBarClassName = 'bar errored';
191 alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
192 }
193 }
194
195 var results = find('.results');
196 results.appendChild(summary);
197
198 summaryList(topResults, summary);
199
200 function summaryList(resultsTree, domParent) {
201 var specListNode;
202 for (var i = 0; i < resultsTree.children.length; i++) {
203 var resultNode = resultsTree.children[i];
204 if (resultNode.type == 'suite') {
205 var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
206 createDom('li', {className: 'suite-detail'},
207 createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
208 )
209 );
210
211 summaryList(resultNode, suiteListNode);
212 domParent.appendChild(suiteListNode);
213 }
214 if (resultNode.type == 'spec') {
215 if (domParent.getAttribute('class') != 'specs') {
216 specListNode = createDom('ul', {className: 'specs'});
217 domParent.appendChild(specListNode);
218 }
219 var specDescription = resultNode.result.description;
220 if(noExpectations(resultNode.result)) {
221 specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
222 }
223 specListNode.appendChild(
224 createDom('li', {
225 className: resultNode.result.status,
226 id: 'spec-' + resultNode.result.id
227 },
228 createDom('a', {href: specHref(resultNode.result)}, specDescription)
229 )
230 );
231 }
232 }
233 }
234
235 if (failures.length) {
236 alert.appendChild(
237 createDom('span', {className: 'menu bar spec-list'},
238 createDom('span', {}, 'Spec List | '),
239 createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
240 alert.appendChild(
241 createDom('span', {className: 'menu bar failure-list'},
242 createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
243 createDom('span', {}, ' | Failures ')));
244
245 find('.failures-menu').onclick = function() {
246 setMenuModeTo('failure-list');
247 };
248 find('.spec-list-menu').onclick = function() {
249 setMenuModeTo('spec-list');
250 };
251
252 setMenuModeTo('failure-list');
253
254 var failureNode = find('.failures');
255 for (var i = 0; i < failures.length; i++) {
256 failureNode.appendChild(failures[i]);
257 }
258 }
259 };
260
261 return this;
262
263 function find(selector) {
264 return getContainer().querySelector('.jasmine_html-reporter ' + selector);
265 }
266
267 function clearPrior() {
268 // return the reporter
269 var oldReporter = find('');
270
271 if(oldReporter) {
272 getContainer().removeChild(oldReporter);
273 }
274 }
275
276 function createDom(type, attrs, childrenVarArgs) {
277 var el = createElement(type);
278
279 for (var i = 2; i < arguments.length; i++) {
280 var child = arguments[i];
281
282 if (typeof child === 'string') {
283 el.appendChild(createTextNode(child));
284 } else {
285 if (child) {
286 el.appendChild(child);
287 }
288 }
289 }
290
291 for (var attr in attrs) {
292 if (attr == 'className') {
293 el[attr] = attrs[attr];
294 } else {
295 el.setAttribute(attr, attrs[attr]);
296 }
297 }
298
299 return el;
300 }
301
302 function pluralize(singular, count) {
303 var word = (count == 1 ? singular : singular + 's');
304
305 return '' + count + ' ' + word;
306 }
307
308 function specHref(result) {
309 return '?spec=' + encodeURIComponent(result.fullName);
310 }
311
312 function setMenuModeTo(mode) {
313 htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
314 }
315
316 function noExpectations(result) {
317 return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
318 result.status === 'passed';
319 }
320 }
321
322 return HtmlReporter;
323};
324
325jasmineRequire.HtmlSpecFilter = function() {
326 function HtmlSpecFilter(options) {
327 var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
328 var filterPattern = new RegExp(filterString);
329
330 this.matches = function(specName) {
331 return filterPattern.test(specName);
332 };
333 }
334
335 return HtmlSpecFilter;
336};
337
338jasmineRequire.ResultsNode = function() {
339 function ResultsNode(result, type, parent) {
340 this.result = result;
341 this.type = type;
342 this.parent = parent;
343
344 this.children = [];
345
346 this.addChild = function(result, type) {
347 this.children.push(new ResultsNode(result, type, this));
348 };
349
350 this.last = function() {
351 return this.children[this.children.length - 1];
352 };
353 }
354
355 return ResultsNode;
356};
357
358jasmineRequire.QueryString = function() {
359 function QueryString(options) {
360
361 this.setParam = function(key, value) {
362 var paramMap = queryStringToParamMap();
363 paramMap[key] = value;
364 options.getWindowLocation().search = toQueryString(paramMap);
365 };
366
367 this.getParam = function(key) {
368 return queryStringToParamMap()[key];
369 };
370
371 return this;
372
373 function toQueryString(paramMap) {
374 var qStrPairs = [];
375 for (var prop in paramMap) {
376 qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
377 }
378 return '?' + qStrPairs.join('&');
379 }
380
381 function queryStringToParamMap() {
382 var paramStr = options.getWindowLocation().search.substring(1),
383 params = [],
384 paramMap = {};
385
386 if (paramStr.length > 0) {
387 params = paramStr.split('&');
388 for (var i = 0; i < params.length; i++) {
389 var p = params[i].split('=');
390 var value = decodeURIComponent(p[1]);
391 if (value === 'true' || value === 'false') {
392 value = JSON.parse(value);
393 }
394 paramMap[decodeURIComponent(p[0])] = value;
395 }
396 }
397
398 return paramMap;
399 }
400
401 }
402
403 return QueryString;
404};