UNPKG

21.1 kBJavaScriptView Raw
1/*
2Copyright (c) 2008-2018 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 ResultsStateBuilder() {
38 this.topResults = new j$.ResultsNode({}, '', null);
39 this.currentParent = this.topResults;
40 this.specsExecuted = 0;
41 this.failureCount = 0;
42 this.pendingSpecCount = 0;
43 }
44
45 ResultsStateBuilder.prototype.suiteStarted = function(result) {
46 this.currentParent.addChild(result, 'suite');
47 this.currentParent = this.currentParent.last();
48 };
49
50 ResultsStateBuilder.prototype.suiteDone = function(result) {
51 this.currentParent.updateResult(result);
52 if (this.currentParent !== this.topResults) {
53 this.currentParent = this.currentParent.parent;
54 }
55
56 if (result.status === 'failed') {
57 this.failureCount++;
58 }
59 };
60
61 ResultsStateBuilder.prototype.specStarted = function(result) {
62 };
63
64 ResultsStateBuilder.prototype.specDone = function(result) {
65 this.currentParent.addChild(result, 'spec');
66
67 if (result.status !== 'excluded') {
68 this.specsExecuted++;
69 }
70
71 if (result.status === 'failed') {
72 this.failureCount++;
73 }
74
75 if (result.status == 'pending') {
76 this.pendingSpecCount++;
77 }
78 };
79
80
81
82 function HtmlReporter(options) {
83 var env = options.env || {},
84 getContainer = options.getContainer,
85 createElement = options.createElement,
86 createTextNode = options.createTextNode,
87 navigateWithNewParam = options.navigateWithNewParam || function() {},
88 addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
89 filterSpecs = options.filterSpecs,
90 timer = options.timer || noopTimer,
91 htmlReporterMain,
92 symbols,
93 deprecationWarnings = [];
94
95 this.initialize = function() {
96 clearPrior();
97 htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
98 createDom('div', {className: 'jasmine-banner'},
99 createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}),
100 createDom('span', {className: 'jasmine-version'}, j$.version)
101 ),
102 createDom('ul', {className: 'jasmine-symbol-summary'}),
103 createDom('div', {className: 'jasmine-alert'}),
104 createDom('div', {className: 'jasmine-results'},
105 createDom('div', {className: 'jasmine-failures'})
106 )
107 );
108 getContainer().appendChild(htmlReporterMain);
109 };
110
111 var totalSpecsDefined;
112 this.jasmineStarted = function(options) {
113 totalSpecsDefined = options.totalSpecsDefined || 0;
114 timer.start();
115 };
116
117 var summary = createDom('div', {className: 'jasmine-summary'});
118
119 var stateBuilder = new ResultsStateBuilder();
120
121 this.suiteStarted = function(result) {
122 stateBuilder.suiteStarted(result);
123 };
124
125 this.suiteDone = function(result) {
126 stateBuilder.suiteDone(result);
127
128 if (result.status === 'failed') {
129 failures.push(failureDom(result));
130 }
131 addDeprecationWarnings(result);
132 };
133
134 this.specStarted = function(result) {
135 stateBuilder.specStarted(result);
136 };
137
138 var failures = [];
139 this.specDone = function(result) {
140 stateBuilder.specDone(result);
141
142 if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
143 console.error('Spec \'' + result.fullName + '\' has no expectations.');
144 }
145
146 if (!symbols){
147 symbols = find('.jasmine-symbol-summary');
148 }
149
150 symbols.appendChild(createDom('li', {
151 className: this.displaySpecInCorrectFormat(result),
152 id: 'spec_' + result.id,
153 title: result.fullName
154 }
155 ));
156
157 if (result.status === 'failed') {
158 failures.push(failureDom(result));
159 }
160
161 addDeprecationWarnings(result);
162 };
163
164 this.displaySpecInCorrectFormat = function(result) {
165 return noExpectations(result) ? 'jasmine-empty' : this.resultStatus(result.status);
166 };
167
168 this.resultStatus = function(status) {
169 if(status === 'excluded') {
170 return env.hidingDisabled() ? 'jasmine-excluded-no-display' : 'jasmine-excluded';
171 }
172 return 'jasmine-' + status;
173 };
174
175 this.jasmineDone = function(doneResult) {
176 var banner = find('.jasmine-banner');
177 var alert = find('.jasmine-alert');
178 var order = doneResult && doneResult.order;
179 var i;
180 alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
181
182 banner.appendChild(optionsMenu(env));
183
184 if (stateBuilder.specsExecuted < totalSpecsDefined) {
185 var skippedMessage = 'Ran ' + stateBuilder.specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
186 var skippedLink = addToExistingQueryString('spec', '');
187 alert.appendChild(
188 createDom('span', {className: 'jasmine-bar jasmine-skipped'},
189 createDom('a', {href: skippedLink, title: 'Run all specs'}, skippedMessage)
190 )
191 );
192 }
193 var statusBarMessage = '';
194 var statusBarClassName = 'jasmine-overall-result jasmine-bar ';
195 var globalFailures = (doneResult && doneResult.failedExpectations) || [];
196 var failed = stateBuilder.failureCount + globalFailures.length > 0;
197
198 if (totalSpecsDefined > 0 || failed) {
199 statusBarMessage += pluralize('spec', stateBuilder.specsExecuted) + ', ' + pluralize('failure', stateBuilder.failureCount);
200 if (stateBuilder.pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount); }
201 }
202
203 if (doneResult.overallStatus === 'passed') {
204 statusBarClassName += ' jasmine-passed ';
205 } else if (doneResult.overallStatus === 'incomplete') {
206 statusBarClassName += ' jasmine-incomplete ';
207 statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage;
208 } else {
209 statusBarClassName += ' jasmine-failed ';
210 }
211
212 var seedBar;
213 if (order && order.random) {
214 seedBar = createDom('span', {className: 'jasmine-seed-bar'},
215 ', randomized with seed ',
216 createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
217 );
218 }
219
220 alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));
221
222 var errorBarClassName = 'jasmine-bar jasmine-errored';
223 var afterAllMessagePrefix = 'AfterAll ';
224
225 for(i = 0; i < globalFailures.length; i++) {
226 alert.appendChild(createDom('span', {className: errorBarClassName}, globalFailureMessage(globalFailures[i])));
227 }
228
229 function globalFailureMessage(failure) {
230 if (failure.globalErrorType === 'load') {
231 var prefix = 'Error during loading: ' + failure.message;
232
233 if (failure.filename) {
234 return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
235 } else {
236 return prefix;
237 }
238 } else {
239 return afterAllMessagePrefix + failure.message;
240 }
241 }
242
243 addDeprecationWarnings(doneResult);
244
245 var warningBarClassName = 'jasmine-bar jasmine-warning';
246 for(i = 0; i < deprecationWarnings.length; i++) {
247 var warning = deprecationWarnings[i];
248 alert.appendChild(createDom('span', {className: warningBarClassName}, 'DEPRECATION: ' + warning));
249 }
250
251 var results = find('.jasmine-results');
252 results.appendChild(summary);
253
254 summaryList(stateBuilder.topResults, summary);
255
256 if (failures.length) {
257 alert.appendChild(
258 createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'},
259 createDom('span', {}, 'Spec List | '),
260 createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures')));
261 alert.appendChild(
262 createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'},
263 createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'),
264 createDom('span', {}, ' | Failures ')));
265
266 find('.jasmine-failures-menu').onclick = function() {
267 setMenuModeTo('jasmine-failure-list');
268 };
269 find('.jasmine-spec-list-menu').onclick = function() {
270 setMenuModeTo('jasmine-spec-list');
271 };
272
273 setMenuModeTo('jasmine-failure-list');
274
275 var failureNode = find('.jasmine-failures');
276 for (i = 0; i < failures.length; i++) {
277 failureNode.appendChild(failures[i]);
278 }
279 }
280 };
281
282 return this;
283
284 function failureDom(result) {
285 var failure =
286 createDom('div', {className: 'jasmine-spec-detail jasmine-failed'},
287 failureDescription(result, stateBuilder.currentParent),
288 createDom('div', {className: 'jasmine-messages'})
289 );
290 var messages = failure.childNodes[1];
291
292 for (var i = 0; i < result.failedExpectations.length; i++) {
293 var expectation = result.failedExpectations[i];
294 messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message));
295 messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack));
296 }
297
298 return failure;
299 }
300
301 function summaryList(resultsTree, domParent) {
302 var specListNode;
303 for (var i = 0; i < resultsTree.children.length; i++) {
304 var resultNode = resultsTree.children[i];
305 if (filterSpecs && !hasActiveSpec(resultNode)) {
306 continue;
307 }
308 if (resultNode.type === 'suite') {
309 var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id},
310 createDom('li', {className: 'jasmine-suite-detail jasmine-' + resultNode.result.status},
311 createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
312 )
313 );
314
315 summaryList(resultNode, suiteListNode);
316 domParent.appendChild(suiteListNode);
317 }
318 if (resultNode.type === 'spec') {
319 if (domParent.getAttribute('class') !== 'jasmine-specs') {
320 specListNode = createDom('ul', {className: 'jasmine-specs'});
321 domParent.appendChild(specListNode);
322 }
323 var specDescription = resultNode.result.description;
324 if(noExpectations(resultNode.result)) {
325 specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
326 }
327 if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
328 specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
329 }
330 specListNode.appendChild(
331 createDom('li', {
332 className: 'jasmine-' + resultNode.result.status,
333 id: 'spec-' + resultNode.result.id
334 },
335 createDom('a', {href: specHref(resultNode.result)}, specDescription)
336 )
337 );
338 }
339 }
340 }
341
342 function optionsMenu(env) {
343 var optionsMenuDom = createDom('div', { className: 'jasmine-run-options' },
344 createDom('span', { className: 'jasmine-trigger' }, 'Options'),
345 createDom('div', { className: 'jasmine-payload' },
346 createDom('div', { className: 'jasmine-stop-on-failure' },
347 createDom('input', {
348 className: 'jasmine-fail-fast',
349 id: 'jasmine-fail-fast',
350 type: 'checkbox'
351 }),
352 createDom('label', { className: 'jasmine-label', 'for': 'jasmine-fail-fast' }, 'stop execution on spec failure')),
353 createDom('div', { className: 'jasmine-throw-failures' },
354 createDom('input', {
355 className: 'jasmine-throw',
356 id: 'jasmine-throw-failures',
357 type: 'checkbox'
358 }),
359 createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
360 createDom('div', { className: 'jasmine-random-order' },
361 createDom('input', {
362 className: 'jasmine-random',
363 id: 'jasmine-random-order',
364 type: 'checkbox'
365 }),
366 createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')),
367 createDom('div', { className: 'jasmine-hide-disabled' },
368 createDom('input', {
369 className: 'jasmine-disabled',
370 id: 'jasmine-hide-disabled',
371 type: 'checkbox'
372 }),
373 createDom('label', { className: 'jasmine-label', 'for': 'jasmine-hide-disabled' }, 'hide disabled tests'))
374 )
375 );
376
377 var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast');
378 failFastCheckbox.checked = env.stoppingOnSpecFailure();
379 failFastCheckbox.onclick = function() {
380 navigateWithNewParam('failFast', !env.stoppingOnSpecFailure());
381 };
382
383 var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures');
384 throwCheckbox.checked = env.throwingExpectationFailures();
385 throwCheckbox.onclick = function() {
386 navigateWithNewParam('throwFailures', !env.throwingExpectationFailures());
387 };
388
389 var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order');
390 randomCheckbox.checked = env.randomTests();
391 randomCheckbox.onclick = function() {
392 navigateWithNewParam('random', !env.randomTests());
393 };
394
395 var hideDisabled = optionsMenuDom.querySelector('#jasmine-hide-disabled');
396 hideDisabled.checked = env.hidingDisabled();
397 hideDisabled.onclick = function() {
398 navigateWithNewParam('hideDisabled', !env.hidingDisabled());
399 };
400
401 var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
402 optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
403 isOpen = /\bjasmine-open\b/;
404
405 optionsTrigger.onclick = function() {
406 if (isOpen.test(optionsPayload.className)) {
407 optionsPayload.className = optionsPayload.className.replace(isOpen, '');
408 } else {
409 optionsPayload.className += ' jasmine-open';
410 }
411 };
412
413 return optionsMenuDom;
414 }
415
416 function failureDescription(result, suite) {
417 var wrapper = createDom('div', {className: 'jasmine-description'},
418 createDom('a', {title: result.description, href: specHref(result)}, result.description)
419 );
420 var suiteLink;
421
422 while (suite && suite.parent) {
423 wrapper.insertBefore(createTextNode(' > '), wrapper.firstChild);
424 suiteLink = createDom('a', {href: suiteHref(suite)}, suite.result.description);
425 wrapper.insertBefore(suiteLink, wrapper.firstChild);
426
427 suite = suite.parent;
428 }
429
430 return wrapper;
431 }
432
433 function suiteHref(suite) {
434 var els = [];
435
436 while (suite && suite.parent) {
437 els.unshift(suite.result.description);
438 suite = suite.parent;
439 }
440
441 return addToExistingQueryString('spec', els.join(' '));
442 }
443
444 function addDeprecationWarnings(result) {
445 if (result && result.deprecationWarnings) {
446 for(var i = 0; i < result.deprecationWarnings.length; i++) {
447 var warning = result.deprecationWarnings[i].message;
448 if (!j$.util.arrayContains(warning)) {
449 deprecationWarnings.push(warning);
450 }
451 }
452 }
453 }
454
455 function find(selector) {
456 return getContainer().querySelector('.jasmine_html-reporter ' + selector);
457 }
458
459 function clearPrior() {
460 // return the reporter
461 var oldReporter = find('');
462
463 if(oldReporter) {
464 getContainer().removeChild(oldReporter);
465 }
466 }
467
468 function createDom(type, attrs, childrenVarArgs) {
469 var el = createElement(type);
470
471 for (var i = 2; i < arguments.length; i++) {
472 var child = arguments[i];
473
474 if (typeof child === 'string') {
475 el.appendChild(createTextNode(child));
476 } else {
477 if (child) {
478 el.appendChild(child);
479 }
480 }
481 }
482
483 for (var attr in attrs) {
484 if (attr == 'className') {
485 el[attr] = attrs[attr];
486 } else {
487 el.setAttribute(attr, attrs[attr]);
488 }
489 }
490
491 return el;
492 }
493
494 function pluralize(singular, count) {
495 var word = (count == 1 ? singular : singular + 's');
496
497 return '' + count + ' ' + word;
498 }
499
500 function specHref(result) {
501 return addToExistingQueryString('spec', result.fullName);
502 }
503
504 function seedHref(seed) {
505 return addToExistingQueryString('seed', seed);
506 }
507
508 function defaultQueryString(key, value) {
509 return '?' + key + '=' + value;
510 }
511
512 function setMenuModeTo(mode) {
513 htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
514 }
515
516 function noExpectations(result) {
517 return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
518 result.status === 'passed';
519 }
520
521 function hasActiveSpec(resultNode) {
522 if (resultNode.type == 'spec' && resultNode.result.status != 'excluded') {
523 return true;
524 }
525
526 if (resultNode.type == 'suite') {
527 for (var i = 0, j = resultNode.children.length; i < j; i++) {
528 if (hasActiveSpec(resultNode.children[i])) {
529 return true;
530 }
531 }
532 }
533 }
534 }
535
536 return HtmlReporter;
537};
538
539jasmineRequire.HtmlSpecFilter = function() {
540 function HtmlSpecFilter(options) {
541 var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
542 var filterPattern = new RegExp(filterString);
543
544 this.matches = function(specName) {
545 return filterPattern.test(specName);
546 };
547 }
548
549 return HtmlSpecFilter;
550};
551
552jasmineRequire.ResultsNode = function() {
553 function ResultsNode(result, type, parent) {
554 this.result = result;
555 this.type = type;
556 this.parent = parent;
557
558 this.children = [];
559
560 this.addChild = function(result, type) {
561 this.children.push(new ResultsNode(result, type, this));
562 };
563
564 this.last = function() {
565 return this.children[this.children.length - 1];
566 };
567
568 this.updateResult = function(result) {
569 this.result = result;
570 };
571 }
572
573 return ResultsNode;
574};
575
576jasmineRequire.QueryString = function() {
577 function QueryString(options) {
578
579 this.navigateWithNewParam = function(key, value) {
580 options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
581 };
582
583 this.fullStringWithNewParam = function(key, value) {
584 var paramMap = queryStringToParamMap();
585 paramMap[key] = value;
586 return toQueryString(paramMap);
587 };
588
589 this.getParam = function(key) {
590 return queryStringToParamMap()[key];
591 };
592
593 return this;
594
595 function toQueryString(paramMap) {
596 var qStrPairs = [];
597 for (var prop in paramMap) {
598 qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
599 }
600 return '?' + qStrPairs.join('&');
601 }
602
603 function queryStringToParamMap() {
604 var paramStr = options.getWindowLocation().search.substring(1),
605 params = [],
606 paramMap = {};
607
608 if (paramStr.length > 0) {
609 params = paramStr.split('&');
610 for (var i = 0; i < params.length; i++) {
611 var p = params[i].split('=');
612 var value = decodeURIComponent(p[1]);
613 if (value === 'true' || value === 'false') {
614 value = JSON.parse(value);
615 }
616 paramMap[decodeURIComponent(p[0])] = value;
617 }
618 }
619
620 return paramMap;
621 }
622
623 }
624
625 return QueryString;
626};