Changeset 8572
- Timestamp:
- 01/06/08 00:34:39 (8 months ago)
- Files:
-
- spinoffs/prototype/trunk/CHANGELOG (modified) (1 diff)
- spinoffs/prototype/trunk/Rakefile (modified) (1 diff)
- spinoffs/prototype/trunk/src/dom.js (modified) (1 diff)
- spinoffs/prototype/trunk/test/lib/jstest.rb (modified) (2 diffs)
- spinoffs/prototype/trunk/test/lib/unittest.js (modified) (6 diffs)
- spinoffs/prototype/trunk/test/test.css (modified) (4 diffs)
- spinoffs/prototype/trunk/test/unit/ajax.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/array.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/base.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/dom.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/element_mixins.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/enumerable.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/event.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/hash.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/number.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/position.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/range.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/selector.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/string.html (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/unit_tests.html (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
spinoffs/prototype/trunk/CHANGELOG
r8557 r8572 1 1 *SVN* 2 3 * Test.Unit refactoring. Allow running multiple instances of Test.Unit.Runner on the same page. Allow rake to run specific testcases (e.g.: rake test BROWSERS=firefox TESTS=array TESTCASES=testUniq,test$w). Closes #10704, #10705, #10706. [nicwilliams, Tobie Langel] 2 4 3 5 * Optimize property detection of outerHTML. Avoids triggering FOUC in Safari 3.0.4. Closes #10702. [subimage, Tobie Langel] spinoffs/prototype/trunk/Rakefile
r8274 r8572 38 38 desc "Runs all the JavaScript unit tests and collects the results" 39 39 JavaScriptTestTask.new(:test_units) do |t| 40 testcases = ENV['TESTCASES'] 40 41 tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',') 41 42 browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',') 42 43 43 44 t.mount("/dist") 44 45 t.mount("/test") 45 46 46 47 Dir["test/unit/*.html"].sort.each do |test_file| 47 test _file ="/#{test_file}"48 test_ name = test_file[/.*\/(.+?)\.html/, 1]49 t.run(test _file) unless tests_to_run && !tests_to_run.include?(test_name)48 tests = testcases ? { :url => "/#{test_file}", :testcases => testcases } : "/#{test_file}" 49 test_filename = test_file[/.*\/(.+?)\.html/, 1] 50 t.run(tests) unless tests_to_run && !tests_to_run.include?(test_filename) 50 51 end 51 52 spinoffs/prototype/trunk/src/dom.js
r8557 r8572 186 186 187 187 descendants: function(element) { 188 return $(element). getElementsBySelector("*");188 return $(element).select("*"); 189 189 }, 190 190 spinoffs/prototype/trunk/test/lib/jstest.rb
r8514 r8572 309 309 puts "\nStarted tests in #{browser}" 310 310 @tests.each do |test| 311 browser.visit("http://localhost:4711#{test}?resultsURL=http://localhost:4711/results&t=" + ("%.6f" % Time.now.to_f)) 311 params = "resultsURL=http://localhost:4711/results&t=" + ("%.6f" % Time.now.to_f) 312 if test.is_a?(Hash) 313 params << "&tests=#{test[:testcases]}" if test[:testcases] 314 test = test[:url] 315 end 316 browser.visit("http://localhost:4711#{test}?#{params}") 312 317 313 318 result = @queue.pop … … 350 355 end 351 356 352 # test should be specified as a url 357 # test should be specified as a url or as a hash of the form 358 # {:url => "url", :testcases => "testFoo,testBar"} 353 359 def run(test) 354 360 @tests<<test spinoffs/prototype/trunk/test/lib/unittest.js
r7983 r8572 36 36 37 37 if(this.mark) Element.remove(this.mark); 38 this.mark = document.createElement('div'); 38 39 var style = 'position: absolute; width: 5px; height: 5px;' + 40 'top: #{pointerY}px; left: #{pointerX}px;'.interpolate(options) + 41 'border-top: 1px solid red; border-left: 1px solid red;' 42 43 this.mark = new Element('div', { style: style }); 39 44 this.mark.appendChild(document.createTextNode(" ")); 40 45 document.body.appendChild(this.mark); 41 this.mark.style.position = 'absolute';42 this.mark.style.top = options.pointerY + "px";43 this.mark.style.left = options.pointerX + "px";44 this.mark.style.width = "5px";45 this.mark.style.height = "5px;";46 this.mark.style.borderTop = "1px solid red;"47 this.mark.style.borderLeft = "1px solid red;"48 46 49 47 if(this.step) … … 79 77 }; 80 78 81 var Test = {} 82 Test.Unit = {}; 83 84 // security exception workaround 85 Test.Unit.inspect = Object.inspect; 86 87 Test.Unit.Logger = Class.create(); 88 Test.Unit.Logger.prototype = { 89 initialize: function(log) { 90 this.log = $(log); 91 if (this.log) { 92 this._createLogTable(); 93 } 94 }, 79 var Test = { 80 Unit: { 81 inspect: Object.inspect // security exception workaround 82 } 83 }; 84 85 Test.Unit.Logger = Class.create({ 86 initialize: function(element) { 87 this.element = $(element); 88 if (this.element) this._createLogTable(); 89 }, 90 95 91 start: function(testName) { 96 if (!this.log) return; 97 this.testName = testName; 98 this.lastLogLine = document.createElement('tr'); 99 this.statusCell = document.createElement('td'); 100 this.nameCell = document.createElement('td'); 101 this.nameCell.appendChild(document.createTextNode(testName)); 102 this.messageCell = document.createElement('td'); 103 this.lastLogLine.appendChild(this.statusCell); 104 this.lastLogLine.appendChild(this.nameCell); 105 this.lastLogLine.appendChild(this.messageCell); 106 this.loglines.appendChild(this.lastLogLine); 107 }, 92 if (!this.element) return; 93 this.element.down('tbody').insert('<tr><td>' + testName + '</td><td></td><td></td></tr>'); 94 }, 95 96 setStatus: function(status) { 97 this.getLastLogLine().addClassName(status).down('td', 1).update(status); 98 }, 99 108 100 finish: function(status, summary) { 109 if (!this. log) return;110 this. lastLogLine.className = status;111 this. statusCell.innerHTML = status;112 this.messageCell.innerHTML = this._toHTML(summary);113 },101 if (!this.element) return; 102 this.setStatus(status); 103 this.message(summary); 104 }, 105 114 106 message: function(message) { 115 if (!this.log) return; 116 this.messageCell.innerHTML = this._toHTML(message); 117 }, 107 if (!this.element) return; 108 this.getMessageCell().update(this._toHTML(message)); 109 }, 110 118 111 summary: function(summary) { 119 if (!this.log) return; 120 this.logsummary.innerHTML = this._toHTML(summary); 121 }, 112 if (!this.element) return; 113 this.element.down('div').update(this._toHTML(summary)); 114 }, 115 116 getLastLogLine: function() { 117 return this.element.select('tr').last() 118 }, 119 120 getMessageCell: function() { 121 return this.getLastLogLine().down('td', 2); 122 }, 123 122 124 _createLogTable: function() { 123 this.log.innerHTML = 124 '<div id="logsummary"></div>' + 125 '<table id="logtable">' + 125 var html = '<div class="logsummary">running...</div>' + 126 '<table class="logtable">' + 126 127 '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' + 127 '<tbody id="loglines"></tbody>' +128 '<tbody class="loglines"></tbody>' + 128 129 '</table>'; 129 this.logsummary = $('logsummary') 130 this.loglines = $('loglines'); 131 }, 130 this.element.update(html) 131 132 }, 133 134 appendActionButtons: function(actions) { 135 actions = $H(actions); 136 if (!actions.any()) return; 137 var div = new Element("div", {className: 'action_buttons'}); 138 actions.inject(div, function(container, action) { 139 var button = new Element("input").setValue(action.key).observe("click", action.value); 140 button.type = "button"; 141 return container.insert(button); 142 }); 143 this.getMessageCell().insert(div); 144 }, 145 132 146 _toHTML: function(txt) { 133 147 return txt.escapeHTML().replace(/\n/g,"<br/>"); 134 148 } 135 } 136 137 Test.Unit.Runner = Class.create(); 138 Test.Unit.Runner.prototype = { 149 }); 150 151 Test.Unit.Runner = Class.create({ 139 152 initialize: function(testcases) { 140 this.options = Object.extend({153 var options = this.options = Object.extend({ 141 154 testLog: 'testlog' 142 155 }, arguments[1] || {}); 143 this.options.resultsURL = this.parseResultsURLQueryParameter(); 144 if (this.options.testLog) { 145 this.options.testLog = $(this.options.testLog) || null; 146 } 147 if(this.options.tests) { 148 this.tests = []; 149 for(var i = 0; i < this.options.tests.length; i++) { 150 if(/^test/.test(this.options.tests[i])) { 151 this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); 152 } 153 } 154 } else { 155 if (this.options.test) { 156 this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; 157 } else { 158 this.tests = []; 159 for(var testcase in testcases) { 160 if(/^test/.test(testcase)) { 161 this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"])); 162 } 163 } 164 } 165 } 156 157 options.resultsURL = this.queryParams.resultsURL; 158 options.testLog = $(options.testLog); 159 160 this.tests = this.getTests(testcases, options); 166 161 this.currentTest = 0; 167 this.logger = new Test.Unit.Logger( this.options.testLog);168 Event.observe(window, "load", function() { 169 setTimeout(this.runTests.bind(this), 100);162 this.logger = new Test.Unit.Logger(options.testLog); 163 Event.observe(window, "load", function() { 164 this.runTests.bind(this).delay(0.1); 170 165 }.bind(this)); 171 166 }, 172 parseResultsURLQueryParameter: function() { 173 return window.location.search.parseQuery()["resultsURL"]; 174 }, 175 // Returns: 176 // "ERROR" if there was an error, 177 // "FAILURE" if there was a failure, or 178 // "SUCCESS" if there was neither 167 168 queryParams: window.location.search.parseQuery(), 169 170 getTests: function(testcases, options) { 171 var tests; 172 if (this.queryParams.tests) tests = this.queryParams.tests.split(','); 173 else if (options.tests) tests = options.tests; 174 else if (options.test) tests = [option.test]; 175 else tests = Object.keys(testcases).grep(/^test/); 176 177 return tests.map(function(test) { 178 if (testcases[test]) 179 return new Test.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown); 180 }).compact(); 181 }, 182 179 183 getResult: function() { 180 184 var results = { … … 199 203 } 200 204 }, 205 201 206 runTests: function() { 202 var test = this.tests[this.currentTest]; 203 if (!test) { 204 // finished! 205 this.postResults(); 206 this.logger.summary(this.summary()); 207 return; 208 } 209 if(!test.isWaiting) { 210 this.logger.start(test.name); 211 } 207 var test = this.tests[this.currentTest], actions; 208 209 if (!test) return this.finish(); 210 if (!test.isWaiting) this.logger.start(test.name); 212 211 test.run(); 213 212 if(test.isWaiting) { 214 213 this.logger.message("Waiting for " + test.timeToWait + "ms"); 215 214 setTimeout(this.runTests.bind(this), test.timeToWait || 1000); 216 } else { 217 this.logger.finish(test.status(), test.summary()); 218 var actionButtons = test.actionButtons(); 219 if (actionButtons) 220 $(this.logger.lastLogLine).down('td', 2).appendChild(actionButtons); 221 222 this.currentTest++; 223 // tail recursive, hopefully the browser will skip the stackframe 224 this.runTests(); 225 } 215 return; 216 } 217 218 this.logger.finish(test.status(), test.summary()); 219 if (actions = test.actions) this.logger.appendActionButtons(actions); 220 this.currentTest++; 221 // tail recursive, hopefully the browser will skip the stackframe 222 this.runTests(); 223 }, 224 225 finish: function() { 226 this.postResults(); 227 this.logger.summary(this.summary()); 226 228 }, 227 229 … … 230 232 .interpolate(this.getResult()); 231 233 } 232 } 233 234 Test.Unit.Assertions = Class.create(); 235 Test.Unit.Assertions.prototype = { 236 initialize: function() { 237 this.assertions = 0; 238 this.failures = 0; 239 this.errors = 0; 240 this.messages = []; 241 this.actions = {}; 242 }, 243 summary: function() { 244 return ( 245 this.assertions + " assertions, " + 246 this.failures + " failures, " + 247 this.errors + " errors" + "\n" + 248 this.messages.join("\n")); 249 }, 250 actionButtons: function() { 251 if (!Object.keys(this.actions).any()) return false; 252 var div = $(document.createElement("div")); 253 div.addClassName("action_buttons"); 254 255 for (var title in this.actions) { 256 var button = $(document.createElement("input")); 257 button.value = title; 258 button.type = "button"; 259 button.observe("click", this.actions[title]); 260 div.appendChild(button); 261 } 262 return div; 263 }, 264 pass: function() { 265 this.assertions++; 266 }, 267 fail: function(message) { 268 this.failures++; 269 270 var line = ""; 271 try { 272 throw new Error("stack"); 273 } catch(e){ 274 line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1]; 275 } 276 277 this.messages.push("Failure: " + message + (line ? " Line #" + line : "")); 278 }, 279 info: function(message) { 280 this.messages.push("Info: " + message); 281 }, 282 error: function(error, test) { 283 this.errors++; 284 this.actions['retry with throw'] = function() { test.run(true) }; 285 this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) + ")"); 286 }, 287 status: function() { 288 if (this.failures > 0) return 'failed'; 289 if (this.errors > 0) return 'error'; 290 return 'passed'; 291 }, 292 isRunningFromRake: (function() { 293 return window.location.port == 4711; 294 })(), 234 }); 235 236 Test.Unit.Assertions = { 295 237 assert: function(expression) { 296 238 var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; … … 498 440 assertElementMatches: function(element, expression) { 499 441 this.assertElementsMatch([element], expression); 500 }, 442 } 443 }; 444 445 Test.Unit.Testcase = Class.create(Test.Unit.Assertions, { 446 initialize: function(name, test, setup, teardown) { 447 this.name = name; 448 this.test = test || Prototype.emptyFunction; 449 this.setup = setup || Prototype.emptyFunction; 450 this.teardown = teardown || Prototype.emptyFunction; 451 this.messages = []; 452 this.actions = {}; 453 }, 454 455 isWaiting: false, 456 timeToWait: 1000, 457 assertions: 0, 458 failures: 0, 459 errors: 0, 460 isRunningFromRake: window.location.port == 4711, 461 462 wait: function(time, nextPart) { 463 this.isWaiting = true; 464 this.test = nextPart; 465 this.timeToWait = time; 466 }, 467 468 run: function(rethrow) { 469 try { 470 try { 471 if (!this.isWaiting) this.setup(); 472 this.isWaiting = false; 473 this.test(); 474 } finally { 475 if(!this.isWaiting) { 476 this.teardown(); 477 } 478 } 479 } 480 catch(e) { 481 if (rethrow) throw e; 482 this.error(e, this); 483 } 484 }, 485 486 summary: function() { 487 var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors\n'; 488 return msg.interpolate(this) + this.messages.join("\n"); 489 }, 490 491 pass: function() { 492 this.assertions++; 493 }, 494 495 fail: function(message) { 496 this.failures++; 497 var line = ""; 498 try { 499 throw new Error("stack"); 500 } catch(e){ 501 line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1]; 502 } 503 this.messages.push("Failure: " + message + (line ? " Line #" + line : "")); 504 }, 505 506 info: function(message) { 507 this.messages.push("Info: " + message); 508 }, 509 510 error: function(error, test) { 511 this.errors++; 512 this.actions['retry with throw'] = function() { test.run(true) }; 513 this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) + ")"); 514 }, 515 516 status: function() { 517 if (this.failures > 0) return 'failed'; 518 if (this.errors > 0) return 'error'; 519 return 'passed'; 520 }, 521 501 522 benchmark: function(operation, iterations) { 502 523 var startAt = new Date(); … … 507 528 return timeTaken; 508 529 } 509 }510 511 Test.Unit.Testcase = Class.create();512 Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {513 initialize: function(name, test, setup, teardown) {514 Test.Unit.Assertions.prototype.initialize.bind(this)();515 this.name = name;516 this.test = test || function() {};517 this.setup = setup || function() {};518 this.teardown = teardown || function() {};519 this.isWaiting = false;520 this.timeToWait = 1000;521 },522 wait: function(time, nextPart) {523 this.isWaiting = true;524 this.test = nextPart;525 this.timeToWait = time;526 },527 run: function(rethrow) {528 try {529 try {530 if (!this.isWaiting) this.setup.bind(this)();531 this.isWaiting = false;532 this.test.bind(this)();533 } finally {534 if(!this.isWaiting) {535 this.teardown.bind(this)();536 }537 }538 }539 catch(e) {540 if (rethrow) throw e;541 this.error(e, this);542 }543 }544 530 }); spinoffs/prototype/trunk/test/test.css
r5456 r8572 13 13 } 14 14 15 #logsummary { 15 .logsummary { 16 margin-top: 1em; 16 17 margin-bottom: 1em; 17 18 padding: 1ex; … … 20 21 } 21 22 22 #logtable {23 .logtable { 23 24 width:100%; 24 25 border-collapse: collapse; … … 26 27 } 27 28 28 #logtable td, #logtable th {29 .logtable td, .logtable th { 29 30 text-align: left; 30 31 padding: 3px 8px; … … 32 33 } 33 34 34 #logtable .passed {35 .logtable .passed { 35 36 background-color: #cfc; 36 37 } 37 38 38 #logtable .failed, #logtable .error {39 .logtable .failed, .logtable .error { 39 40 background-color: #fcc; 40 41 } 41 42 42 #logtable td div.action_buttons {43 .logtable td div.action_buttons { 43 44 display: inline; 44 45 } 45 46 46 #logtable td div.action_buttons input {47 .logtable td div.action_buttons input { 47 48 margin: 0 5px; 48 49 font-size: 10px; spinoffs/prototype/trunk/test/unit/ajax.html
r8512 r8572 412 412 } 413 413 }} 414 } , 'testlog');414 }); 415 415 // ]]> 416 416 </script> spinoffs/prototype/trunk/test/unit/array.html
r8451 r8572 219 219 }} 220 220 221 } , 'testlog');221 }); 222 222 // ]]> 223 223 </script> spinoffs/prototype/trunk/test/unit/base.html
r8190 r8572 624 624 }} 625 625 626 } , 'testlog');626 }); 627 627 628 628 // ]]> spinoffs/prototype/trunk/test/unit/dom.html
r8537 r8572 1684 1684 }, this); 1685 1685 }} 1686 } , 'testlog');1686 }); 1687 1687 1688 1688 function preservingBrowserDimensions(callback) { spinoffs/prototype/trunk/test/unit/element_mixins.html
r6598 r8572 67 67 })); 68 68 }} 69 } , 'testlog');69 }); 70 70 // ]]> 71 71 </script> spinoffs/prototype/trunk/test/unit/enumerable.html
r7333 r8572 319 319 assertEqual(0, [].size()); 320 320 }} 321 } , 'testlog');321 }); 322 322 // ]]> 323 323 </script> spinoffs/prototype/trunk/test/unit/event.html
r8548 r8572 243 243 244 244 245 } , 'testlog');245 }); 246 246 247 247 document.observe("dom:loaded", function(event) { spinoffs/prototype/trunk/test/unit/hash.html
r8139 r8572 227 227 }} 228 228 229 } , 'testlog');229 }); 230 230 // ]]> 231 231 </script> spinoffs/prototype/trunk/test/unit/number.html
r6957 r8572 62 62 }} 63 63 64 } , 'testlog');64 }); 65 65 66 66 // ]]> spinoffs/prototype/trunk/test/unit/position.html
r7303 r8572 82 82 }} 83 83 84 } , 'testlog');84 }); 85 85 86 86 // ]]> spinoffs/prototype/trunk/test/unit/range.html
r4942 r8572 87 87 }} 88 88 89 } , 'testlog');89 }); 90 90 // ]]> 91 91 </script> spinoffs/prototype/trunk/test/unit/selector.html
r8449 r8572 432 432 assert(typeof results[2].show == 'function'); 433 433 }} 434 } , 'testlog');434 }); 435 435 // ]]> 436 436 </script> spinoffs/prototype/trunk/test/unit/string.html
r8148 r8572 566 566 assertEqual('"', '"\\""'.evalJSON()); 567 567 }} 568 } , 'testlog');568 }); 569 569 // ]]> 570 570 </script> spinoffs/prototype/trunk/test/unit/unit_tests.html
r7852 r8572 23 23 <!-- Log output --> 24 24 <div id="testlog"> </div> 25 <div id="testlog_2"> </div> 25 26 26 27 <!-- Test elements follow --> … … 174 175 }} 175 176 176 }, "testlog"); 177 }); 178 179 new Test.Unit.Runner({ 180 testDummy: function() { with(this) { 181 assert(true); 182 }}, 183 184 testMultipleTestRunner: function() { with(this) { 185 assertEqual('passed', $('testlog_2').down('td', 1).innerHTML); 186 }} 187 188 }, {testLog: 'testlog_2'}); 177 189 // ]]> 178 190 </script>