| Nils Diewald | 19ccee9 | 2014-12-08 11:30:08 +0000 | [diff] [blame] | 1 | /* | 
|  | 2 | Copyright (c) 2008-2014 Pivotal Labs | 
|  | 3 |  | 
|  | 4 | Permission is hereby granted, free of charge, to any person obtaining | 
|  | 5 | a copy of this software and associated documentation files (the | 
|  | 6 | "Software"), to deal in the Software without restriction, including | 
|  | 7 | without limitation the rights to use, copy, modify, merge, publish, | 
|  | 8 | distribute, sublicense, and/or sell copies of the Software, and to | 
|  | 9 | permit persons to whom the Software is furnished to do so, subject to | 
|  | 10 | the following conditions: | 
|  | 11 |  | 
|  | 12 | The above copyright notice and this permission notice shall be | 
|  | 13 | included in all copies or substantial portions of the Software. | 
|  | 14 |  | 
|  | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | 
|  | 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | 
|  | 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | 
|  | 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | 
|  | 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | 
|  | 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | 
|  | 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 
|  | 22 | */ | 
|  | 23 | jasmineRequire.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 |  | 
|  | 30 | jasmineRequire.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 |  | 
|  | 325 | jasmineRequire.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 |  | 
|  | 338 | jasmineRequire.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 |  | 
|  | 358 | jasmineRequire.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 | }; |