Chris Craik | 93216d0 | 2015-03-05 13:58:42 -0800 | [diff] [blame^] | 1 | <!DOCTYPE html> |
| 2 | <!-- |
| 3 | Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 4 | Use of this source code is governed by a BSD-style license that can be |
| 5 | found in the LICENSE file. |
| 6 | --> |
| 7 | <link rel="import" href="/base/events.html"> |
| 8 | <link rel="import" href="/base/utils.html"> |
| 9 | <link rel="import" href="/base/unittest/constants.html"> |
| 10 | <link rel="import" href="/base/ui.html"> |
| 11 | <link rel="stylesheet" href="/base/unittest/common.css"> |
| 12 | <style> |
| 13 | x-tv.b.unittest-test-resultsbase |
| 14 | display: -webkit-flex; |
| 15 | -webkit-flex-direction: column; |
| 16 | } |
| 17 | |
| 18 | x-tv.b.unittest-test-results > x-html-test-case-result.dark { |
| 19 | background-color: #eee; |
| 20 | } |
| 21 | |
| 22 | x-html-test-case-result { |
| 23 | display: block; |
| 24 | } |
| 25 | x-html-test-case-result > #title, |
| 26 | x-html-test-case-result > #status, |
| 27 | x-html-test-case-result > #details > x-html-test-case-error > #message, |
| 28 | x-html-test-case-result > #details > x-html-test-case-error > #stack, |
| 29 | x-html-test-case-result > #details > x-html-test-case-error > #return-value { |
| 30 | -webkit-user-select: auto; |
| 31 | } |
| 32 | |
| 33 | x-html-test-case-result > #details > x-html-test-case-error { |
| 34 | display: block; |
| 35 | border: 1px solid grey; |
| 36 | border-radius: 5px; |
| 37 | font-family: monospace; |
| 38 | margin-bottom: 14px; |
| 39 | } |
| 40 | |
| 41 | x-html-test-case-result > #details > x-html-test-case-error > #message, |
| 42 | x-html-test-case-result > #details > x-html-test-case-error > #stack { |
| 43 | white-space: pre; |
| 44 | } |
| 45 | |
| 46 | x-html-test-case-result > #details > x-html-test-case-html-result { |
| 47 | display: block; |
| 48 | } |
| 49 | |
| 50 | </style> |
| 51 | <template id="x-html-test-case-result-template"> |
| 52 | <span id="title"></span> <span id="status"></span> <span id="return-value"></span> |
| 53 | <div id="details"></div> |
| 54 | </template> |
| 55 | |
| 56 | <template id="x-html-test-case-error-template"> |
| 57 | <div id="stack"></div> |
| 58 | </template> |
| 59 | |
| 60 | <script> |
| 61 | 'use strict'; |
| 62 | tv.exportTo('tv.b.unittest', function() { |
| 63 | var THIS_DOC = document.currentScript.ownerDocument; |
| 64 | |
| 65 | var TestStatus = tv.b.unittest.TestStatus; |
| 66 | var TestTypes = tv.b.unittest.TestTypes; |
| 67 | |
| 68 | /** |
| 69 | * @constructor |
| 70 | */ |
| 71 | var HTMLTestCaseResult = tv.b.ui.define('x-html-test-case-result'); |
| 72 | |
| 73 | HTMLTestCaseResult.prototype = { |
| 74 | __proto__: HTMLUnknownElement.prototype, |
| 75 | |
| 76 | decorate: function() { |
| 77 | this.appendChild(tv.b.instantiateTemplate( |
| 78 | '#x-html-test-case-result-template', THIS_DOC)); |
| 79 | this.testCase_ = undefined; |
| 80 | this.testCaseHRef_ = undefined; |
| 81 | this.duration_ = undefined; |
| 82 | this.testStatus_ = TestStatus.PENDING; |
| 83 | this.testReturnValue_ = undefined; |
| 84 | this.showHTMLOutput_ = false; |
| 85 | this.updateColorAndStatus_(); |
| 86 | }, |
| 87 | |
| 88 | get showHTMLOutput() { |
| 89 | return this.showHTMLOutput_; |
| 90 | }, |
| 91 | |
| 92 | set showHTMLOutput(showHTMLOutput) { |
| 93 | this.showHTMLOutput_ = showHTMLOutput; |
| 94 | this.updateHTMLOutputDisplayState_(); |
| 95 | }, |
| 96 | |
| 97 | get testCase() { |
| 98 | return this.testCase_; |
| 99 | }, |
| 100 | |
| 101 | set testCase(testCase) { |
| 102 | this.testCase_ = testCase; |
| 103 | this.updateTitle_(); |
| 104 | }, |
| 105 | |
| 106 | get testCaseHRef() { |
| 107 | return this.testCaseHRef_; |
| 108 | }, |
| 109 | |
| 110 | set testCaseHRef(href) { |
| 111 | this.testCaseHRef_ = href; |
| 112 | this.updateTitle_(); |
| 113 | }, |
| 114 | updateTitle_: function() { |
| 115 | var titleEl = this.querySelector('#title'); |
| 116 | if (this.testCase_ === undefined) { |
| 117 | titleEl.textContent = ''; |
| 118 | return; |
| 119 | } |
| 120 | |
| 121 | if (this.testCaseHRef_) { |
| 122 | titleEl.innerHTML = '<a href="' + this.testCaseHRef_ + '">' + |
| 123 | this.testCase_.fullyQualifiedName + '</a>'; |
| 124 | } else { |
| 125 | titleEl.textContent = this.testCase_.fullyQualifiedName; |
| 126 | } |
| 127 | }, |
| 128 | |
| 129 | addError: function(normalizedException) { |
| 130 | var errorEl = document.createElement('x-html-test-case-error'); |
| 131 | errorEl.appendChild(tv.b.instantiateTemplate( |
| 132 | '#x-html-test-case-error-template', THIS_DOC)); |
| 133 | errorEl.querySelector('#stack').textContent = normalizedException.stack; |
| 134 | this.querySelector('#details').appendChild(errorEl); |
| 135 | this.updateColorAndStatus_(); |
| 136 | }, |
| 137 | |
| 138 | addHTMLOutput: function(element) { |
| 139 | var htmlResultEl = document.createElement('x-html-test-case-html-result'); |
| 140 | htmlResultEl.appendChild(element); |
| 141 | this.querySelector('#details').appendChild(htmlResultEl); |
| 142 | }, |
| 143 | |
| 144 | updateHTMLOutputDisplayState_: function() { |
| 145 | var htmlResults = this.querySelectorAll('x-html-test-case-html-result'); |
| 146 | var display; |
| 147 | if (this.showHTMLOutput) |
| 148 | display = ''; |
| 149 | else |
| 150 | display = (this.testStatus_ == TestStatus.RUNNING) ? '' : 'none'; |
| 151 | for (var i = 0; i < htmlResults.length; i++) |
| 152 | htmlResults[i].style.display = display; |
| 153 | }, |
| 154 | |
| 155 | get hadErrors() { |
| 156 | return !!this.querySelector('x-html-test-case-error'); |
| 157 | }, |
| 158 | |
| 159 | get duration() { |
| 160 | return this.duration_; |
| 161 | }, |
| 162 | |
| 163 | set duration(duration) { |
| 164 | this.duration_ = duration; |
| 165 | this.updateColorAndStatus_(); |
| 166 | }, |
| 167 | |
| 168 | get testStatus() { |
| 169 | return this.testStatus_; |
| 170 | }, |
| 171 | |
| 172 | set testStatus(testStatus) { |
| 173 | this.testStatus_ = testStatus; |
| 174 | this.updateColorAndStatus_(); |
| 175 | this.updateHTMLOutputDisplayState_(); |
| 176 | }, |
| 177 | |
| 178 | updateColorAndStatus_: function() { |
| 179 | var colorCls; |
| 180 | var status; |
| 181 | if (this.hadErrors) { |
| 182 | colorCls = 'unittest-failed'; |
| 183 | status = 'failed'; |
| 184 | } else if (this.testStatus_ == TestStatus.PENDING) { |
| 185 | colorCls = 'unittest-pending'; |
| 186 | status = 'pending'; |
| 187 | } else if (this.testStatus_ == TestStatus.RUNNING) { |
| 188 | colorCls = 'unittest-running'; |
| 189 | status = 'running'; |
| 190 | } else { // DONE_RUNNING and no errors |
| 191 | colorCls = 'unittest-passed'; |
| 192 | status = 'passed'; |
| 193 | } |
| 194 | |
| 195 | var statusEl = this.querySelector('#status'); |
| 196 | if (this.duration_) |
| 197 | statusEl.textContent = status + ' (' + |
| 198 | this.duration_.toFixed(2) + 'ms)'; |
| 199 | else |
| 200 | statusEl.textContent = status; |
| 201 | statusEl.className = colorCls; |
| 202 | }, |
| 203 | |
| 204 | get testReturnValue() { |
| 205 | return this.testReturnValue_; |
| 206 | }, |
| 207 | |
| 208 | set testReturnValue(testReturnValue) { |
| 209 | this.testReturnValue_ = testReturnValue; |
| 210 | this.querySelector('#return-value').textContent = testReturnValue; |
| 211 | } |
| 212 | }; |
| 213 | |
| 214 | |
| 215 | |
| 216 | |
| 217 | /** |
| 218 | * @constructor |
| 219 | */ |
| 220 | var HTMLTestResults = tv.b.ui.define('x-tv.b.unittest-test-results'); |
| 221 | |
| 222 | HTMLTestResults.prototype = { |
| 223 | __proto__: HTMLUnknownElement.prototype, |
| 224 | |
| 225 | decorate: function() { |
| 226 | this.currentTestCaseStartTime_ = undefined; |
| 227 | this.totalRunTime_ = 0; |
| 228 | this.numTestsThatPassed_ = 0; |
| 229 | this.numTestsThatFailed_ = 0; |
| 230 | this.showHTMLOutput_ = false; |
| 231 | this.showPendingAndPassedTests_ = false; |
| 232 | this.linkifyCallback_ = undefined; |
| 233 | }, |
| 234 | |
| 235 | getHRefForTestCase: function(testCase) { |
| 236 | /* Override this to create custom links */ |
| 237 | return undefined; |
| 238 | }, |
| 239 | |
| 240 | get showHTMLOutput() { |
| 241 | return this.showHTMLOutput_; |
| 242 | }, |
| 243 | |
| 244 | set showHTMLOutput(showHTMLOutput) { |
| 245 | this.showHTMLOutput_ = showHTMLOutput; |
| 246 | var testCaseResults = this.querySelectorAll('x-html-test-case-result'); |
| 247 | for (var i = 0; i < testCaseResults.length; i++) |
| 248 | testCaseResults[i].showHTMLOutput = showHTMLOutput; |
| 249 | }, |
| 250 | |
| 251 | get showPendingAndPassedTests() { |
| 252 | return this.showPendingAndPassedTests_; |
| 253 | }, |
| 254 | |
| 255 | set showPendingAndPassedTests(showPendingAndPassedTests) { |
| 256 | this.showPendingAndPassedTests_ = showPendingAndPassedTests; |
| 257 | |
| 258 | var testCaseResults = this.querySelectorAll('x-html-test-case-result'); |
| 259 | for (var i = 0; i < testCaseResults.length; i++) |
| 260 | this.updateDisplayStateForResult_(testCaseResults[i]); |
| 261 | }, |
| 262 | |
| 263 | updateDisplayStateForResult_: function(res) { |
| 264 | var display; |
| 265 | if (this.showPendingAndPassedTests_) { |
| 266 | if (res.testStatus == TestStatus.RUNNING || |
| 267 | res.hadErrors) { |
| 268 | display = ''; |
| 269 | } else { |
| 270 | display = 'none'; |
| 271 | } |
| 272 | } else { |
| 273 | display = ''; |
| 274 | } |
| 275 | res.style.display = display; |
| 276 | |
| 277 | // This bit of mess gives res objects a dark class based on whether their |
| 278 | // last visible sibling was not dark. It relies on the |
| 279 | // updateDisplayStateForResult_ being called on all previous siblings of |
| 280 | // an element before being called on the element itself. Yay induction. |
| 281 | var dark; |
| 282 | if (!res.previousSibling) { |
| 283 | dark = true; |
| 284 | } else { |
| 285 | var lastVisible; |
| 286 | for (var cur = res.previousSibling; |
| 287 | cur; |
| 288 | cur = cur.previousSibling) { |
| 289 | if (cur.style.display == '') { |
| 290 | lastVisible = cur; |
| 291 | break; |
| 292 | } |
| 293 | } |
| 294 | if (lastVisible) { |
| 295 | dark = !lastVisible.classList.contains('dark'); |
| 296 | } else { |
| 297 | dark = true; |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | if (dark) |
| 302 | res.classList.add('dark'); |
| 303 | else |
| 304 | res.classList.remove('dark'); |
| 305 | }, |
| 306 | |
| 307 | willRunTest: function(testCase) { |
| 308 | this.currentTestCaseResult_ = new HTMLTestCaseResult(); |
| 309 | this.currentTestCaseResult_.showHTMLOutput = this.showHTMLOutput_; |
| 310 | this.currentTestCaseResult_.testCase = testCase; |
| 311 | var href = this.getHRefForTestCase(testCase); |
| 312 | if (href) |
| 313 | this.currentTestCaseResult_.testCaseHRef = href; |
| 314 | this.currentTestCaseResult_.testStatus = TestStatus.RUNNING; |
| 315 | this.currentTestCaseStartTime_ = window.performance.now(); |
| 316 | this.appendChild(this.currentTestCaseResult_); |
| 317 | this.updateDisplayStateForResult_(this.currentTestCaseResult_); |
| 318 | this.log_(testCase.fullyQualifiedName + ': '); |
| 319 | }, |
| 320 | |
| 321 | addErrorForCurrentTest: function(error) { |
| 322 | this.log_('\n'); |
| 323 | |
| 324 | var normalizedException = tv.b.normalizeException(error); |
| 325 | this.log_('Exception: ' + normalizedException.message + '\n' + |
| 326 | normalizedException.stack); |
| 327 | |
| 328 | this.currentTestCaseResult_.addError(normalizedException); |
| 329 | this.updateDisplayStateForResult_(this.currentTestCaseResult_); |
| 330 | }, |
| 331 | |
| 332 | addHTMLOutputForCurrentTest: function(element) { |
| 333 | this.currentTestCaseResult_.addHTMLOutput(element); |
| 334 | this.updateDisplayStateForResult_(this.currentTestCaseResult_); |
| 335 | }, |
| 336 | |
| 337 | setReturnValueFromCurrentTest: function(returnValue) { |
| 338 | this.currentTestCaseResult_.testReturnValue = returnValue; |
| 339 | }, |
| 340 | |
| 341 | didCurrentTestEnd: function() { |
| 342 | var testCaseResult = this.currentTestCaseResult_; |
| 343 | var testCaseDuration = window.performance.now() - |
| 344 | this.currentTestCaseStartTime_; |
| 345 | this.currentTestCaseResult_.testStatus = TestStatus.DONE_RUNNING; |
| 346 | testCaseResult.duration = testCaseDuration; |
| 347 | this.totalRunTime_ += testCaseDuration; |
| 348 | if (testCaseResult.hadErrors) { |
| 349 | this.log_('[FAILED]\n'); |
| 350 | this.numTestsThatFailed_ += 1; |
| 351 | tv.b.dispatchSimpleEvent(this, 'testfailed'); |
| 352 | } else { |
| 353 | this.log_('[PASSED]\n'); |
| 354 | this.numTestsThatPassed_ += 1; |
| 355 | tv.b.dispatchSimpleEvent(this, 'testpassed'); |
| 356 | } |
| 357 | |
| 358 | this.updateDisplayStateForResult_(this.currentTestCaseResult_); |
| 359 | this.currentTestCaseResult_ = undefined; |
| 360 | }, |
| 361 | |
| 362 | didRunTests: function() { |
| 363 | this.log_('[DONE]\n'); |
| 364 | }, |
| 365 | |
| 366 | getStats: function() { |
| 367 | return { |
| 368 | numTestsThatPassed: this.numTestsThatPassed_, |
| 369 | numTestsThatFailed: this.numTestsThatFailed_, |
| 370 | totalRunTime: this.totalRunTime_ |
| 371 | }; |
| 372 | }, |
| 373 | |
| 374 | log_: function(msg) { |
| 375 | //this.textContent += msg; |
| 376 | tv.b.dispatchSimpleEvent(this, 'statschange'); |
| 377 | } |
| 378 | }; |
| 379 | |
| 380 | return { |
| 381 | HTMLTestResults: HTMLTestResults |
| 382 | }; |
| 383 | }); |
| 384 | </script> |