1 // ========================================================================== 2 // Project: SproutCore Costello - Property Observing Library 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 /*globals CoreTest Q$ */ 9 10 sc_require('system/plan'); 11 12 /** @static 13 The runner will automatically run the default CoreTest.plan when the 14 document is fully loaded. It will also act as a delegate on the plan, 15 logging the output to the screen or console. 16 17 @since SproutCore 1.0 18 */ 19 20 21 CoreTest.Runner = { 22 23 /** 24 The CoreTest plan. If not set, a default plan will be created. 25 */ 26 plan: null, 27 errors: null, 28 29 showProgress: false, 30 31 create: function() { 32 var len = arguments.length, 33 ret = CoreTest.beget(this), 34 idx ; 35 36 for(idx=0;idx<len;idx++) CoreTest.mixin(ret, arguments[len]); 37 if (!ret.plan) ret.plan = CoreTest.Plan.create({ delegate: ret }); 38 window.resizeTo(1400, 800); 39 Q$(document).ready(function() { ret.begin(); }); 40 return ret ; 41 }, 42 43 begin: function() { 44 var plan = CoreTest.plan; 45 plan.delegate = this; 46 plan.run(); 47 }, 48 49 planDidBegin: function(plan) { 50 // setup the report DOM element. 51 var str = [ 52 '<div class="core-test">', 53 '<div class="useragent">UserAgent</div>', 54 '<div class="testresult">', 55 '<label class="hide-passed">', 56 '<input type="checkbox" checked="checked" /> Hide passed tests', 57 '</label>' 58 ]; 59 60 if (navigator.userAgent.indexOf('MSIE')==-1) { 61 str.push( 62 '<label class="show-progress">', 63 '<input type="checkbox"'+(this.showProgress?' checked="checked"':'')+'" /> Show progress (slower)', 64 '</label>' 65 ); 66 } 67 68 str.push( 69 '<label class="show-progress">', 70 '<input type="checkbox"'+(this.showProgress?' checked="checked"':'')+'" /> Show progress (slower)', 71 '</label>', 72 '<span class="status">Running...</span>', 73 '</div>', 74 '<div class="detail">', 75 '<table>', 76 '<thead><tr>', 77 '<th class="desc">Test</th><th>Result</th>', 78 '</tr></thead>', 79 '<tbody><tr></tr></tbody>', 80 '</table>', 81 '</div>', 82 '</div>' 83 ); 84 85 this.report = Q$(str.join('')); 86 87 this.report.find('.useragent').html(navigator.userAgent); 88 this.logq = this.report.find('tbody'); 89 this.testCount = 0 ; 90 91 // listen to change event 92 var runner = this; 93 this.hidePassedCheckbox = this.report.find('.hide-passed input'); 94 this.hidePassedCheckbox.change(function() { 95 runner.hidePassedTestsDidChange(); 96 }); 97 98 this.showProgressCheckbox = this.report.find('.show-progress input'); 99 this.showProgressCheckbox.change(function() { 100 runner.showProgressCheckboxDidChange(); 101 }); 102 103 Q$('body').append(this.report); 104 }, 105 106 hidePassedTestsDidChange: function() { 107 var checked = this.hidePassedCheckbox.is(':checked'); 108 109 if (checked) { 110 this.logq.addClass('hide-clean'); 111 } else { 112 this.logq.removeClass('hide-clean'); 113 } 114 }, 115 116 showProgressCheckboxDidChange: function(){ 117 this.showProgress = this.showProgressCheckbox.is(':checked'); 118 if (this.showProgress) { this.flush(); } 119 }, 120 121 planDidFinish: function(plan, r) { 122 this.flush(); 123 124 var result = this.report.find('.testresult .status'); 125 var str = CoreTest.fmt('<span>Completed %@ tests in %@ msec. </span>' 126 +'<span class="total">%@</span> total assertions: ', r.tests, 127 r.runtime, r.total); 128 129 if (r.passed > 0) { 130 str += CoreTest.fmt(' <span class="passed">%@ passed</span>', r.passed); 131 } 132 133 if (r.failed > 0) { 134 str += CoreTest.fmt(' <span class="failed">%@ failed</span>', r.failed); 135 } 136 137 if (r.errors > 0) { 138 str += CoreTest.fmt(' <span class="errors">%@ error%@</span>', 139 r.errors, (r.errors !== 1 ? 's' : '')); 140 } 141 142 if (r.warnings > 0) { 143 str += CoreTest.fmt(' <span class="warnings">%@ warning%@</span>', 144 r.warnings, (r.warnings !== 1 ? 's' : '')); 145 } 146 147 // if all tests passed, disable hiding them. if some tests failed, hide 148 // them by default. 149 if (this.errors) this.errors.push('</tr></tbody></table>'); 150 if ((r.failed + r.errors + r.warnings) > 0) { 151 this.hidePassedTestsDidChange(); // should be checked by default 152 } else { 153 this.report.find('.hide-passed').addClass('disabled') 154 .find('input').attr('disabled', true); 155 if (this.errors) this.errors.length = 0; 156 } 157 if(CoreTest.showUI) Q$('.core-test').css("right", "360px"); 158 result.html(str); 159 160 CoreTest.finished = true; 161 162 if (this.errors) CoreTest.errors=this.errors.join(''); 163 164 165 // Unload the SproutCore event system so that the user can select the text 166 // of the various events. (It is handy when looking at failed tests.) 167 if (SC && SC.Event && SC.Event.unload) { 168 try { 169 var responder = SC.RootResponder.responder; 170 SC.Event.remove(document, 'selectstart', responder, responder.selectstart); 171 } 172 catch (e) {} 173 } 174 175 if (typeof window.callPhantom === 'function') { 176 window.callPhantom(r); 177 } 178 }, 179 180 planDidRecord: function(plan, module, test, assertions, timings) { 181 var name = test, 182 s = { passed: 0, failed: 0, errors: 0, warnings: 0 }, 183 len = assertions.length, 184 clean = '', 185 idx, cur, q; 186 187 for(idx=0;idx<len;idx++) s[assertions[idx].result]++; 188 if ((s.failed + s.errors + s.warnings) === 0) clean = "clean" ; 189 190 if (module) name = module.replace(/\n/g, '<br />') + " module: " + test ; 191 name = CoreTest.fmt('%@ - %@msec', name, timings.total_end - timings.total_begin); 192 // place results into a single string to append all at once. 193 var logstr = this.logstr ; 194 var errors =this.errors; 195 if (!logstr) logstr = this.logstr = []; 196 if (!this.errors) { 197 this.errors = ['<style type="text/css">* {font: 12px arial;}'+ 198 '.passed { background-color: #80D175; color: white;}'+ 199 '.failed { background-color: #ea4d4; color: black; }'+ 200 '.errors { background-color: red; color: black; }'+ 201 '.warnings { background-color: #E49723; color: black;}'+ 202 '.desc { text-align: left;}'+ 203 '</style><table style="border:1px solid"><thead>'+ 204 '<tr><th class="desc">'+navigator.userAgent+ 205 '</th><th>Result</th></tr>'+ 206 '</thead><tbody><tr>']; 207 } 208 logstr.push(CoreTest.fmt('<tr class="test %@"><th class="desc" colspan="2">'+ 209 '%@ (<span class="passed">%@</span>, <span class="failed">%@</span>,'+ 210 ' <span class="errors">%@</span>, <span class="warnings">%@</span>)'+ 211 '</th></tr>', clean, name, s.passed, s.failed, s.errors, s.warnings)); 212 if(s.failed>0 || s.errors>0){ 213 this.errors.push(CoreTest.fmt('<tr class="test %@">'+ 214 '<th style="background:grey; color:white" class="desc" colspan="2">'+ 215 '%@ (<span class="passed">%@</span>, <span class="failed">%@</span>'+ 216 ', <span class="errors">%@</span>, <span class="warnings">%@</span>'+ 217 ')</th></tr>', clean, name, s.passed, s.failed, s.errors, s.warnings)); 218 } 219 220 len = assertions.length; 221 for(idx=0;idx<len;idx++) { 222 cur = assertions[idx]; 223 clean = cur.result === CoreTest.OK ? 'clean' : 'dirty'; 224 logstr.push(CoreTest.fmt('<tr class="%@"><td class="desc">%@</td>' 225 +'<td class="action %@">%@</td></tr>', clean, cur.message, cur.result, 226 (cur.result || '').toUpperCase())); 227 if(clean=='dirty'){ 228 this.errors.push(CoreTest.fmt('<tr class="%@"><td class="desc">%@</td>' 229 +'<td class="action %@">%@</td></tr>', clean, cur.message, cur.result, 230 (cur.result || '').toUpperCase())); 231 } 232 } 233 234 this.testCount++; 235 this.resultStr = CoreTest.fmt("Running – Completed %@ tests so far.", this.testCount); 236 }, 237 238 // called when the plan takes a break. Good time to flush HTML output. 239 planDidPause: function(plan) { 240 if(!this._cacheResultSelector){ 241 this._cacheResultSelector = this.report.find('.testresult .status'); 242 } 243 var result = this._cacheResultSelector; 244 245 if (this.resultStr && navigator.userAgent.indexOf('MSIE')==-1) result.html(this.resultStr); 246 this.resultStr = null ; 247 248 if (this.showProgress) { this.flush(); } 249 }, 250 251 // flush any pending HTML changes... 252 flush: function() { 253 var logstr = this.logstr, 254 resultStr = this.resultStr, 255 result = this.report.find('.testresult .status'); 256 257 if (logstr) this.logq.append(this.logstr.join('')) ; 258 259 if (resultStr) result.html(resultStr); 260 this.resultStr = this.logstr = null ; 261 } 262 263 }; 264