web/wp-includes/js/scriptaculous/unittest.js
branchwordpress
changeset 109 03b0d1493584
child 132 4d4862461b8d
equal deleted inserted replaced
-1:000000000000 109:03b0d1493584
       
     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 };