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