home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Blogs / wordpress2.6.exe / wordpress2.6 / wp-includes / js / scriptaculous / unittest.js < prev    next >
Encoding:
Text File  |  2008-03-03  |  19.7 KB  |  568 lines

  1. // script.aculo.us unittest.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007
  2.  
  3. // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
  4. //           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
  5. //           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
  6. //
  7. // script.aculo.us is freely distributable under the terms of an MIT-style license.
  8. // For details, see the script.aculo.us web site: http://script.aculo.us/
  9.  
  10. // experimental, Firefox-only
  11. Event.simulateMouse = function(element, eventName) {
  12.   var options = Object.extend({
  13.     pointerX: 0,
  14.     pointerY: 0,
  15.     buttons:  0,
  16.     ctrlKey:  false,
  17.     altKey:   false,
  18.     shiftKey: false,
  19.     metaKey:  false
  20.   }, arguments[2] || {});
  21.   var oEvent = document.createEvent("MouseEvents");
  22.   oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
  23.     options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
  24.     options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
  25.   
  26.   if(this.mark) Element.remove(this.mark);
  27.   this.mark = document.createElement('div');
  28.   this.mark.appendChild(document.createTextNode(" "));
  29.   document.body.appendChild(this.mark);
  30.   this.mark.style.position = 'absolute';
  31.   this.mark.style.top = options.pointerY + "px";
  32.   this.mark.style.left = options.pointerX + "px";
  33.   this.mark.style.width = "5px";
  34.   this.mark.style.height = "5px;";
  35.   this.mark.style.borderTop = "1px solid red;"
  36.   this.mark.style.borderLeft = "1px solid red;"
  37.   
  38.   if(this.step)
  39.     alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
  40.   
  41.   $(element).dispatchEvent(oEvent);
  42. };
  43.  
  44. // Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
  45. // You need to downgrade to 1.0.4 for now to get this working
  46. // See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
  47. Event.simulateKey = function(element, eventName) {
  48.   var options = Object.extend({
  49.     ctrlKey: false,
  50.     altKey: false,
  51.     shiftKey: false,
  52.     metaKey: false,
  53.     keyCode: 0,
  54.     charCode: 0
  55.   }, arguments[2] || {});
  56.  
  57.   var oEvent = document.createEvent("KeyEvents");
  58.   oEvent.initKeyEvent(eventName, true, true, window, 
  59.     options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
  60.     options.keyCode, options.charCode );
  61.   $(element).dispatchEvent(oEvent);
  62. };
  63.  
  64. Event.simulateKeys = function(element, command) {
  65.   for(var i=0; i<command.length; i++) {
  66.     Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
  67.   }
  68. };
  69.  
  70. var Test = {}
  71. Test.Unit = {};
  72.  
  73. // security exception workaround
  74. Test.Unit.inspect = Object.inspect;
  75.  
  76. Test.Unit.Logger = Class.create();
  77. Test.Unit.Logger.prototype = {
  78.   initialize: function(log) {
  79.     this.log = $(log);
  80.     if (this.log) {
  81.       this._createLogTable();
  82.     }
  83.   },
  84.   start: function(testName) {
  85.     if (!this.log) return;
  86.     this.testName = testName;
  87.     this.lastLogLine = document.createElement('tr');
  88.     this.statusCell = document.createElement('td');
  89.     this.nameCell = document.createElement('td');
  90.     this.nameCell.className = "nameCell";
  91.     this.nameCell.appendChild(document.createTextNode(testName));
  92.     this.messageCell = document.createElement('td');
  93.     this.lastLogLine.appendChild(this.statusCell);
  94.     this.lastLogLine.appendChild(this.nameCell);
  95.     this.lastLogLine.appendChild(this.messageCell);
  96.     this.loglines.appendChild(this.lastLogLine);
  97.   },
  98.   finish: function(status, summary) {
  99.     if (!this.log) return;
  100.     this.lastLogLine.className = status;
  101.     this.statusCell.innerHTML = status;
  102.     this.messageCell.innerHTML = this._toHTML(summary);
  103.     this.addLinksToResults();
  104.   },
  105.   message: function(message) {
  106.     if (!this.log) return;
  107.     this.messageCell.innerHTML = this._toHTML(message);
  108.   },
  109.   summary: function(summary) {
  110.     if (!this.log) return;
  111.     this.logsummary.innerHTML = this._toHTML(summary);
  112.   },
  113.   _createLogTable: function() {
  114.     this.log.innerHTML =
  115.     '<div id="logsummary"></div>' +
  116.     '<table id="logtable">' +
  117.     '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
  118.     '<tbody id="loglines"></tbody>' +
  119.     '</table>';
  120.     this.logsummary = $('logsummary')
  121.     this.loglines = $('loglines');
  122.   },
  123.   _toHTML: function(txt) {
  124.     return txt.escapeHTML().replace(/\n/g,"<br/>");
  125.   },
  126.   addLinksToResults: function(){ 
  127.     $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
  128.       td.title = "Run only this test"
  129.       Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
  130.     });
  131.     $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
  132.       td.title = "Run all tests"
  133.       Event.observe(td, 'click', function(){ window.location.search = "";});
  134.     });
  135.   }
  136. }
  137.  
  138. Test.Unit.Runner = Class.create();
  139. Test.Unit.Runner.prototype = {
  140.   initialize: function(testcases) {
  141.     this.options = Object.extend({
  142.       testLog: 'testlog'
  143.     }, arguments[1] || {});
  144.     this.options.resultsURL = this.parseResultsURLQueryParameter();
  145.     this.options.tests      = this.parseTestsQueryParameter();
  146.     if (this.options.testLog) {
  147.       this.options.testLog = $(this.options.testLog) || null;
  148.     }
  149.     if(this.options.tests) {
  150.       this.tests = [];
  151.       for(var i = 0; i < this.options.tests.length; i++) {
  152.         if(/^test/.test(this.options.tests[i])) {
  153.           this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
  154.         }
  155.       }
  156.     } else {
  157.       if (this.options.test) {
  158.         this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
  159.       } else {
  160.         this.tests = [];
  161.         for(var testcase in testcases) {
  162.           if(/^test/.test(testcase)) {
  163.             this.tests.push(
  164.                new Test.Unit.Testcase(
  165.                  this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
  166.                  testcases[testcase], testcases["setup"], testcases["teardown"]
  167.                ));
  168.           }
  169.         }
  170.       }
  171.     }
  172.     this.currentTest = 0;
  173.     this.logger = new Test.Unit.Logger(this.options.testLog);
  174.     setTimeout(this.runTests.bind(this), 1000);
  175.   },
  176.   parseResultsURLQueryParameter: function() {
  177.     return window.location.search.parseQuery()["resultsURL"];
  178.   },
  179.   parseTestsQueryParameter: function(){
  180.     if (window.location.search.parseQuery()["tests"]){
  181.         return window.location.search.parseQuery()["tests"].split(',');
  182.     };
  183.   },
  184.   // Returns:
  185.   //  "ERROR" if there was an error,
  186.   //  "FAILURE" if there was a failure, or
  187.   //  "SUCCESS" if there was neither
  188.   getResult: function() {
  189.     var hasFailure = false;
  190.     for(var i=0;i<this.tests.length;i++) {
  191.       if (this.tests[i].errors > 0) {
  192.         return "ERROR";
  193.       }
  194.       if (this.tests[i].failures > 0) {
  195.         hasFailure = true;
  196.       }
  197.     }
  198.     if (hasFailure) {
  199.       return "FAILURE";
  200.     } else {
  201.       return "SUCCESS";
  202.     }
  203.   },
  204.   postResults: function() {
  205.     if (this.options.resultsURL) {
  206.       new Ajax.Request(this.options.resultsURL, 
  207.         { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
  208.     }
  209.   },
  210.   runTests: function() {
  211.     var test = this.tests[this.currentTest];
  212.     if (!test) {
  213.       // finished!
  214.       this.postResults();
  215.       this.logger.summary(this.summary());
  216.       return;
  217.     }
  218.     if(!test.isWaiting) {
  219.       this.logger.start(test.name);
  220.     }
  221.     test.run();
  222.     if(test.isWaiting) {
  223.       this.logger.message("Waiting for " + test.timeToWait + "ms");
  224.       setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
  225.     } else {
  226.       this.logger.finish(test.status(), test.summary());
  227.       this.currentTest++;
  228.       // tail recursive, hopefully the browser will skip the stackframe
  229.       this.runTests();
  230.     }
  231.   },
  232.   summary: function() {
  233.     var assertions = 0;
  234.     var failures = 0;
  235.     var errors = 0;
  236.     var messages = [];
  237.     for(var i=0;i<this.tests.length;i++) {
  238.       assertions +=   this.tests[i].assertions;
  239.       failures   +=   this.tests[i].failures;
  240.       errors     +=   this.tests[i].errors;
  241.     }
  242.     return (
  243.       (this.options.context ? this.options.context + ': ': '') + 
  244.       this.tests.length + " tests, " + 
  245.       assertions + " assertions, " + 
  246.       failures   + " failures, " +
  247.       errors     + " errors");
  248.   }
  249. }
  250.  
  251. Test.Unit.Assertions = Class.create();
  252. Test.Unit.Assertions.prototype = {
  253.   initialize: function() {
  254.     this.assertions = 0;
  255.     this.failures   = 0;
  256.     this.errors     = 0;
  257.     this.messages   = [];
  258.   },
  259.   summary: function() {
  260.     return (
  261.       this.assertions + " assertions, " + 
  262.       this.failures   + " failures, " +
  263.       this.errors     + " errors" + "\n" +
  264.       this.messages.join("\n"));
  265.   },
  266.   pass: function() {
  267.     this.assertions++;
  268.   },
  269.   fail: function(message) {
  270.     this.failures++;
  271.     this.messages.push("Failure: " + message);
  272.   },
  273.   info: function(message) {
  274.     this.messages.push("Info: " + message);
  275.   },
  276.   error: function(error) {
  277.     this.errors++;
  278.     this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
  279.   },
  280.   status: function() {
  281.     if (this.failures > 0) return 'failed';
  282.     if (this.errors > 0) return 'error';
  283.     return 'passed';
  284.   },
  285.   assert: function(expression) {
  286.     var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
  287.     try { expression ? this.pass() : 
  288.       this.fail(message); }
  289.     catch(e) { this.error(e); }
  290.   },
  291.   assertEqual: function(expected, actual) {
  292.     var message = arguments[2] || "assertEqual";
  293.     try { (expected == actual) ? this.pass() :
  294.       this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
  295.         '", actual "' + Test.Unit.inspect(actual) + '"'); }
  296.     catch(e) { this.error(e); }
  297.   },
  298.   assertInspect: function(expected, actual) {
  299.     var message = arguments[2] || "assertInspect";
  300.     try { (expected == actual.inspect()) ? this.pass() :
  301.       this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
  302.         '", actual "' + Test.Unit.inspect(actual) + '"'); }
  303.     catch(e) { this.error(e); }
  304.   },
  305.   assertEnumEqual: function(expected, actual) {
  306.     var message = arguments[2] || "assertEnumEqual";
  307.     try { $A(expected).length == $A(actual).length && 
  308.       expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
  309.         this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
  310.           ', actual ' + Test.Unit.inspect(actual)); }
  311.     catch(e) { this.error(e); }
  312.   },
  313.   assertNotEqual: function(expected, actual) {
  314.     var message = arguments[2] || "assertNotEqual";
  315.     try { (expected != actual) ? this.pass() : 
  316.       this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
  317.     catch(e) { this.error(e); }
  318.   },
  319.   assertIdentical: function(expected, actual) { 
  320.     var message = arguments[2] || "assertIdentical"; 
  321.     try { (expected === actual) ? this.pass() : 
  322.       this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
  323.         '", actual "' + Test.Unit.inspect(actual) + '"'); } 
  324.     catch(e) { this.error(e); } 
  325.   },
  326.   assertNotIdentical: function(expected, actual) { 
  327.     var message = arguments[2] || "assertNotIdentical"; 
  328.     try { !(expected === actual) ? this.pass() : 
  329.       this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
  330.         '", actual "' + Test.Unit.inspect(actual) + '"'); } 
  331.     catch(e) { this.error(e); } 
  332.   },
  333.   assertNull: function(obj) {
  334.     var message = arguments[1] || 'assertNull'
  335.     try { (obj==null) ? this.pass() : 
  336.       this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
  337.     catch(e) { this.error(e); }
  338.   },
  339.   assertMatch: function(expected, actual) {
  340.     var message = arguments[2] || 'assertMatch';
  341.     var regex = new RegExp(expected);
  342.     try { (regex.exec(actual)) ? this.pass() :
  343.       this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
  344.     catch(e) { this.error(e); }
  345.   },
  346.   assertHidden: function(element) {
  347.     var message = arguments[1] || 'assertHidden';
  348.     this.assertEqual("none", element.style.display, message);
  349.   },
  350.   assertNotNull: function(object) {
  351.     var message = arguments[1] || 'assertNotNull';
  352.     this.assert(object != null, message);
  353.   },
  354.   assertType: function(expected, actual) {
  355.     var message = arguments[2] || 'assertType';
  356.     try { 
  357.       (actual.constructor == expected) ? this.pass() : 
  358.       this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
  359.         '", actual "' + (actual.constructor) + '"'); }
  360.     catch(e) { this.error(e); }
  361.   },
  362.   assertNotOfType: function(expected, actual) {
  363.     var message = arguments[2] || 'assertNotOfType';
  364.     try { 
  365.       (actual.constructor != expected) ? this.pass() : 
  366.       this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
  367.         '", actual "' + (actual.constructor) + '"'); }
  368.     catch(e) { this.error(e); }
  369.   },
  370.   assertInstanceOf: function(expected, actual) {
  371.     var message = arguments[2] || 'assertInstanceOf';
  372.     try { 
  373.       (actual instanceof expected) ? this.pass() : 
  374.       this.fail(message + ": object was not an instance of the expected type"); }
  375.     catch(e) { this.error(e); } 
  376.   },
  377.   assertNotInstanceOf: function(expected, actual) {
  378.     var message = arguments[2] || 'assertNotInstanceOf';
  379.     try { 
  380.       !(actual instanceof expected) ? this.pass() : 
  381.       this.fail(message + ": object was an instance of the not expected type"); }
  382.     catch(e) { this.error(e); } 
  383.   },
  384.   assertRespondsTo: function(method, obj) {
  385.     var message = arguments[2] || 'assertRespondsTo';
  386.     try {
  387.       (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
  388.       this.fail(message + ": object doesn't respond to [" + method + "]"); }
  389.     catch(e) { this.error(e); }
  390.   },
  391.   assertReturnsTrue: function(method, obj) {
  392.     var message = arguments[2] || 'assertReturnsTrue';
  393.     try {
  394.       var m = obj[method];
  395.       if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
  396.       m() ? this.pass() : 
  397.       this.fail(message + ": method returned false"); }
  398.     catch(e) { this.error(e); }
  399.   },
  400.   assertReturnsFalse: function(method, obj) {
  401.     var message = arguments[2] || 'assertReturnsFalse';
  402.     try {
  403.       var m = obj[method];
  404.       if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
  405.       !m() ? this.pass() : 
  406.       this.fail(message + ": method returned true"); }
  407.     catch(e) { this.error(e); }
  408.   },
  409.   assertRaise: function(exceptionName, method) {
  410.     var message = arguments[2] || 'assertRaise';
  411.     try { 
  412.       method();
  413.       this.fail(message + ": exception expected but none was raised"); }
  414.     catch(e) {
  415.       ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
  416.     }
  417.   },
  418.   assertElementsMatch: function() {
  419.     var expressions = $A(arguments), elements = $A(expressions.shift());
  420.     if (elements.length != expressions.length) {
  421.       this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
  422.       return false;
  423.     }
  424.     elements.zip(expressions).all(function(pair, index) {
  425.       var element = $(pair.first()), expression = pair.last();
  426.       if (element.match(expression)) return true;
  427.       this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
  428.     }.bind(this)) && this.pass();
  429.   },
  430.   assertElementMatches: function(element, expression) {
  431.     this.assertElementsMatch([element], expression);
  432.   },
  433.   benchmark: function(operation, iterations) {
  434.     var startAt = new Date();
  435.     (iterations || 1).times(operation);
  436.     var timeTaken = ((new Date())-startAt);
  437.     this.info((arguments[2] || 'Operation') + ' finished ' + 
  438.        iterations + ' iterations in ' + (timeTaken/1000)+'s' );
  439.     return timeTaken;
  440.   },
  441.   _isVisible: function(element) {
  442.     element = $(element);
  443.     if(!element.parentNode) return true;
  444.     this.assertNotNull(element);
  445.     if(element.style && Element.getStyle(element, 'display') == 'none')
  446.       return false;
  447.     
  448.     return this._isVisible(element.parentNode);
  449.   },
  450.   assertNotVisible: function(element) {
  451.     this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
  452.   },
  453.   assertVisible: function(element) {
  454.     this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
  455.   },
  456.   benchmark: function(operation, iterations) {
  457.     var startAt = new Date();
  458.     (iterations || 1).times(operation);
  459.     var timeTaken = ((new Date())-startAt);
  460.     this.info((arguments[2] || 'Operation') + ' finished ' + 
  461.        iterations + ' iterations in ' + (timeTaken/1000)+'s' );
  462.     return timeTaken;
  463.   }
  464. }
  465.  
  466. Test.Unit.Testcase = Class.create();
  467. Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
  468.   initialize: function(name, test, setup, teardown) {
  469.     Test.Unit.Assertions.prototype.initialize.bind(this)();
  470.     this.name           = name;
  471.     
  472.     if(typeof test == 'string') {
  473.       test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
  474.       test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
  475.       this.test = function() {
  476.         eval('with(this){'+test+'}');
  477.       }
  478.     } else {
  479.       this.test = test || function() {};
  480.     }
  481.     
  482.     this.setup          = setup || function() {};
  483.     this.teardown       = teardown || function() {};
  484.     this.isWaiting      = false;
  485.     this.timeToWait     = 1000;
  486.   },
  487.   wait: function(time, nextPart) {
  488.     this.isWaiting = true;
  489.     this.test = nextPart;
  490.     this.timeToWait = time;
  491.   },
  492.   run: function() {
  493.     try {
  494.       try {
  495.         if (!this.isWaiting) this.setup.bind(this)();
  496.         this.isWaiting = false;
  497.         this.test.bind(this)();
  498.       } finally {
  499.         if(!this.isWaiting) {
  500.           this.teardown.bind(this)();
  501.         }
  502.       }
  503.     }
  504.     catch(e) { this.error(e); }
  505.   }
  506. });
  507.  
  508. // *EXPERIMENTAL* BDD-style testing to please non-technical folk
  509. // This draws many ideas from RSpec http://rspec.rubyforge.org/
  510.  
  511. Test.setupBDDExtensionMethods = function(){
  512.   var METHODMAP = {
  513.     shouldEqual:     'assertEqual',
  514.     shouldNotEqual:  'assertNotEqual',
  515.     shouldEqualEnum: 'assertEnumEqual',
  516.     shouldBeA:       'assertType',
  517.     shouldNotBeA:    'assertNotOfType',
  518.     shouldBeAn:      'assertType',
  519.     shouldNotBeAn:   'assertNotOfType',
  520.     shouldBeNull:    'assertNull',
  521.     shouldNotBeNull: 'assertNotNull',
  522.     
  523.     shouldBe:        'assertReturnsTrue',
  524.     shouldNotBe:     'assertReturnsFalse',
  525.     shouldRespondTo: 'assertRespondsTo'
  526.   };
  527.   var makeAssertion = function(assertion, args, object) { 
  528.        this[assertion].apply(this,(args || []).concat([object]));
  529.   }
  530.   
  531.   Test.BDDMethods = {};   
  532.   $H(METHODMAP).each(function(pair) { 
  533.     Test.BDDMethods[pair.key] = function() { 
  534.        var args = $A(arguments); 
  535.        var scope = args.shift(); 
  536.        makeAssertion.apply(scope, [pair.value, args, this]); }; 
  537.   });
  538.   
  539.   [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
  540.     function(p){ Object.extend(p, Test.BDDMethods) }
  541.   );
  542. }
  543.  
  544. Test.context = function(name, spec, log){
  545.   Test.setupBDDExtensionMethods();
  546.   
  547.   var compiledSpec = {};
  548.   var titles = {};
  549.   for(specName in spec) {
  550.     switch(specName){
  551.       case "setup":
  552.       case "teardown":
  553.         compiledSpec[specName] = spec[specName];
  554.         break;
  555.       default:
  556.         var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
  557.         var body = spec[specName].toString().split('\n').slice(1);
  558.         if(/^\{/.test(body[0])) body = body.slice(1);
  559.         body.pop();
  560.         body = body.map(function(statement){ 
  561.           return statement.strip()
  562.         });
  563.         compiledSpec[testName] = body.join('\n');
  564.         titles[testName] = specName;
  565.     }
  566.   }
  567.   new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
  568. };