Index: pyenvjasmine/envjasmine/EnvJasmine.gemspec
===================================================================
--- pyenvjasmine/envjasmine/EnvJasmine.gemspec	(revision 19)
+++ pyenvjasmine/envjasmine/EnvJasmine.gemspec	(revision 19)
@@ -0,0 +1,37 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+  s.name = "EnvJasmine"
+  s.version = "1.7.2"
+
+  s.authors = ["Trevor Menagh"]
+  s.date = "2012-12-13"
+  s.description = "EnvJasmine allows you to run headless JavaScript tests."
+  s.summary = "EnvJasmine"
+  s.email = "github@trevreport.org"
+  s.executables = ['envjs_run_test','run_coverage.sh','run_all_tests.sh','run_test.sh']
+  s.homepage = 'https://github.com/trevmex/envjasmine'
+  s.files = ['lib/envjasmine.html',
+			'lib/envjasmine.js',
+			'lib/envjs/env.rhino.1.2.js',
+			'lib/jasmine/MIT.LICENSE',
+			'lib/jasmine/jasmine-html.js',
+			'lib/jasmine/jasmine.css',
+			'lib/jasmine/jasmine.js',
+			'lib/jasmine/jasmine_favicon.png',
+			'lib/jasmine-ajax/mock-ajax.js',
+			'lib/jasmine-ajax/spec-helper.js',
+			'lib/jasmine-jquery/jasmine-jquery-1.2.0.js',
+			'lib/jasmine-jquery/jasmine-jquery.js',
+			'lib/jasmine-rhino-reporter/jasmine-rhino-reporter.js',
+      'lib/jscover/envjasmine-coverage.js',
+      'lib/jscover/envjasmine-sonar-coverage-helper.js',
+      'lib/jscover/envjasmine-sonar-coverage-properties.js',
+      'lib/jscover/envjasmine-sonar-coverage-runner.js',
+      'lib/jscover/JSCover-all.jar',
+			'lib/rhino/js.jar',
+			'lib/spanDir/spanDir.js',
+			'include/dependencies.js',
+			'include/jquery-1.4.4.js'
+		]
+end
Index: pyenvjasmine/envjasmine/README.textile
===================================================================
--- pyenvjasmine/envjasmine/README.textile	(revision 0)
+++ pyenvjasmine/envjasmine/README.textile	(revision 19)
@@ -156,5 +156,31 @@
 bc. ./tests/EnvJasmine/bin/run_test.sh --configFile=</absolute/path/to/demo/tests/EnvJasmine/etc/conf/demo.conf.js> specs/add-numbers.spec.js
 
+
+h2. Code Coverage
+
+See @lib/jscover/README.textile@
+
+
+h2. EnvJasmine as a Ruby gem
+
+EnvJasmine can also be compiled to a Ruby gem.
+
+h3. How to build and install the EnvJasmine gem
+
+    git clone git://github.com/trevmex/EnvJasmine.git
+    cd EnvJasmine
+    gem build EnvJasmine.gemspec
+    gem install EnvJasmine-1.7.1.gem
+
+h3. Using the EnvJasmine Ruby gem
+
+Note that, when used as a Ruby gem, EnvJasmine requires a few command line arguments be specified.
+
+Here is a Ruby usage example:
+
+    envjs_run_test --configFile='some_spec_helper.js' --testDir='specs' --rootDir='some_project'
+
 That's it! For more help on Jasmine docs at https://github.com/pivotal/jasmine/wiki
 
 Please contact Trevor Lalish-Menagh through github (https://github.com/trevmex) with any defects or feature requests!
+
Index: pyenvjasmine/envjasmine/bin/envjs_run_coverage
===================================================================
--- pyenvjasmine/envjasmine/bin/envjs_run_coverage	(revision 19)
+++ pyenvjasmine/envjasmine/bin/envjs_run_coverage	(revision 19)
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+
+root = File.join(File.dirname(__FILE__), "..")
+puts "root: #{root}"
+
+cmd = "java -Duser.timezone='US/Eastern' -Dfile.encoding=utf-8 -jar '#{root}/lib/rhino/js.jar' '#{root}/lib/jscover/envjasmine-sonar-coverage-runner.js' --environment='UNIX' --rootDir='#{root}' #{ARGV.join(' ')}"
+
+puts "cmd: #{cmd}"
+exit(system(cmd))
Index: pyenvjasmine/envjasmine/bin/envjs_run_test
===================================================================
--- pyenvjasmine/envjasmine/bin/envjs_run_test	(revision 19)
+++ pyenvjasmine/envjasmine/bin/envjs_run_test	(revision 19)
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+
+root = File.join(File.dirname(__FILE__), "..")
+puts "root: #{root}"
+
+cmd = "java -Duser.timezone='US/Eastern' -Dfile.encoding=utf-8 -jar '#{root}/lib/rhino/js.jar' '#{root}/lib/envjasmine.js' --environment='UNIX' --rootDir='#{root}' #{ARGV.join(' ')}"
+
+puts "cmd: #{cmd}"
+exit(system(cmd))
Index: pyenvjasmine/envjasmine/bin/run_coverage.sh
===================================================================
--- pyenvjasmine/envjasmine/bin/run_coverage.sh	(revision 19)
+++ pyenvjasmine/envjasmine/bin/run_coverage.sh	(revision 19)
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+cd "`dirname \"$0\"`"
+PWD=$(pwd)
+
+CMD="java -Duser.timezone=\"US/Eastern\" -Dfile.encoding=utf-8 -jar \"$PWD/../lib/rhino/js.jar\" \"$PWD/../lib/jscover/envjasmine-sonar-coverage-runner.js\" --environment=\"UNIX\" "
+
+for (( i=1 ; i < $#+1 ; i=$i+1 )) do
+    CMD="$CMD \"${!i}\""
+done
+
+eval $CMD
Index: pyenvjasmine/envjasmine/include/dependencies.js
===================================================================
--- pyenvjasmine/envjasmine/include/dependencies.js	(revision 0)
+++ pyenvjasmine/envjasmine/include/dependencies.js	(revision 19)
@@ -2,5 +2,4 @@
 //
 // NOTE: Load order does matter.
-
 // Load the envjasmine environment
 EnvJasmine.loadGlobal(EnvJasmine.libDir + "envjs/env.rhino.1.2.js");
@@ -12,6 +11,11 @@
 
 // This is your main JavaScript directory in your project.
-EnvJasmine.jsDir = EnvJasmine.rootDir + "/samples/"; // TODO: Change this to your project's main js directory.
+EnvJasmine.jsDir = EnvJasmine.jsDir || EnvJasmine.rootDir + "/samples/"; // TODO: Change this to your project's main js directory.
 
 EnvJasmine.loadGlobal(EnvJasmine.includeDir + "jquery-1.4.4.js"); // for example, load jquery.
 // TODO: Add your own
+
+
+// this will include the code coverage plugin
+//EnvJasmine.loadGlobal(EnvJasmine.libDir + "/jscover/envjasmine-sonar-coverage-properties.js"); // TODO: Uncomment and update if you want code coverage
+//EnvJasmine.loadGlobal(EnvJasmine.coverage.envjasmine_coverage_js); // TODO: Uncomment if you want code coverage
Index: pyenvjasmine/envjasmine/lib/envjasmine.js
===================================================================
--- pyenvjasmine/envjasmine/lib/envjasmine.js	(revision 0)
+++ pyenvjasmine/envjasmine/lib/envjasmine.js	(revision 19)
@@ -40,4 +40,10 @@
 };
 
+EnvJasmine.printDebug = function(str) {
+    if (EnvJasmine.debug) {
+        print(str);
+    }
+}
+
 EnvJasmine.normalizePath = function(path) {
     var endsInSlash = (path.slice(-1) == "/");
@@ -59,4 +65,5 @@
             EnvJasmine.cx.evaluateReader(scope, fileIn, normalizedPath, 0, null);
         } catch (e) {
+            print (e);
             print('Could not read file: ' + normalizedPath );
         } finally {
@@ -88,6 +95,9 @@
 };
 
+EnvJasmine.setJsDir = function(jsDir) {
+    EnvJasmine.jsDir = jsDir;
+};
+
 // Process command line options
-
 (function(argumentList) {
     var arg, nameValue, spec = "", specLoc;
@@ -97,11 +107,8 @@
     EnvJasmine.failedCount = 0;
     EnvJasmine.totalCount = 0;
-
     for (var i = 0; i < argumentList.length; i++) {
         arg = argumentList[i];
-
         if (arg.slice(0, 2) == "--") {
             nameValue = arg.slice(2).split('=');
-
             switch(nameValue[0]) {
                 case "testDir":
@@ -112,4 +119,7 @@
                     EnvJasmine.setTestDir(nameValue[1]); // Set the root as the default testDir.
                     break;
+                case "jsDir":
+                    EnvJasmine.setJsDir(nameValue[1]);
+                    break;
                 case "environment":
                     EnvJasmine.environment = nameValue[1];
@@ -129,4 +139,10 @@
                 case "customSpecsDir":
                     EnvJasmine.customSpecsDir = nameValue[1];
+                    break;
+                case "plugin":
+                    EnvJasmine.addPlugin(nameValue[1]);
+                    break;
+                case "debug":
+                    EnvJasmine.debug = true;
                     break;
                 case "help":
@@ -196,18 +212,33 @@
 EnvJasmine.results = [];
 
+EnvJasmine.addFinallyFunction = function(f) {
+    EnvJasmine.finallyFunctions.push(f);
+};
+EnvJasmine.finallyFunctions = [];
+
+
 EnvJasmine.loadConfig = function () {
     EnvJasmine.loadGlobal(EnvJasmine.configFile);
 };
 
+EnvJasmine.plugins = [];
+EnvJasmine.addPlugin = function(path) {
+    EnvJasmine.plugins.push(path);
+};
+
 (function() {
     var i, fileIn, len;
 
     EnvJasmine.loadConfig();
-    
+
+    for (i = 0; i < EnvJasmine.plugins.length; i++) {
+        EnvJasmine.loadGlobal(EnvJasmine.plugins[i]);
+    }
+
     if (typeof EnvJasmine.reporterClass === "undefined") {
     	// Use the standard reporter
     	EnvJasmine.reporterClass = RhinoReporter;
     }
-    
+
 
     jasmine.getEnv().addReporter(new EnvJasmine.reporterClass());
@@ -242,7 +273,16 @@
             EnvJasmine.cx.evaluateString(EnvJasmine.currentScope, 'window.location.assign(["file://", EnvJasmine.libDir, "envjasmine.html"].join(EnvJasmine.SEPARATOR));', 'Executing '+EnvJasmine.specs[i], 0, null);
         }
+        catch (e) {
+            print("Problem opening " + EnvJasmine.specFile + ": " + e);
+        }
         finally {
-            fileIn.close();
-        }
+            if(fileIn) {
+                fileIn.close();
+            }
+        }
+    }
+
+    for (i = 0; i < EnvJasmine.finallyFunctions.length; i++) {
+        EnvJasmine.finallyFunctions[i]();
     }
 
Index: pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery-1.2.0.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery-1.2.0.js	(revision 0)
+++ 	(revision )
@@ -1,288 +1,0 @@
-var readFixtures = function() {
-  return jasmine.getFixtures().proxyCallTo_('read', arguments);
-};
-
-var preloadFixtures = function() {
-  jasmine.getFixtures().proxyCallTo_('preload', arguments);
-};
-
-var loadFixtures = function() {
-  jasmine.getFixtures().proxyCallTo_('load', arguments);
-};
-
-var setFixtures = function(html) {
-  jasmine.getFixtures().set(html);
-};
-
-var sandbox = function(attributes) {
-  return jasmine.getFixtures().sandbox(attributes);
-};
-
-var spyOnEvent = function(selector, eventName) {
-  jasmine.JQuery.events.spyOn(selector, eventName);
-}
-
-jasmine.getFixtures = function() {
-  return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
-};
-
-jasmine.Fixtures = function() {
-  this.containerId = 'jasmine-fixtures';
-  this.fixturesCache_ = {};
-  this.fixturesPath = 'spec/javascripts/fixtures';
-};
-
-jasmine.Fixtures.prototype.set = function(html) {
-  this.cleanUp();
-  this.createContainer_(html);
-};
-
-jasmine.Fixtures.prototype.preload = function() {
-  this.read.apply(this, arguments);
-};
-
-jasmine.Fixtures.prototype.load = function() {
-  this.cleanUp();
-  this.createContainer_(this.read.apply(this, arguments));
-};
-
-jasmine.Fixtures.prototype.read = function() {
-  var htmlChunks = [];
-
-  var fixtureUrls = arguments;
-  for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
-    htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
-  }
-
-  return htmlChunks.join('');
-};
-
-jasmine.Fixtures.prototype.clearCache = function() {
-  this.fixturesCache_ = {};
-};
-
-jasmine.Fixtures.prototype.cleanUp = function() {
-  jQuery('#' + this.containerId).remove();
-};
-
-jasmine.Fixtures.prototype.sandbox = function(attributes) {
-  var attributesToSet = attributes || {};
-  return jQuery('<div id="sandbox" />').attr(attributesToSet);
-};
-
-jasmine.Fixtures.prototype.createContainer_ = function(html) {
-  var container;
-  if(html instanceof jQuery) {
-    container = jQuery('<div id="' + this.containerId + '" />');
-    container.html(html);
-  } else {
-    container = '<div id="' + this.containerId + '">' + html + '</div>'
-  }
-  jQuery('body').append(container);
-};
-
-jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {  
-  if (typeof this.fixturesCache_[url] == 'undefined') {
-    this.loadFixtureIntoCache_(url);
-  }
-  return this.fixturesCache_[url];
-};
-
-jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
-  var self = this;
-  var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
-  jQuery.ajax({
-    async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
-    cache: false,
-    dataType: 'html',
-    url: url,
-    success: function(data) {
-      self.fixturesCache_[relativeUrl] = data;
-    },
-    error: function(jqXHR, status, errorThrown) {
-        throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
-    }
-  });
-};
-
-jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
-  return this[methodName].apply(this, passedArguments);
-};
-
-
-jasmine.JQuery = function() {};
-
-jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
-  return jQuery('<div/>').append(html).html();
-};
-
-jasmine.JQuery.elementToString = function(element) {
-  return jQuery('<div />').append(element.clone()).html();
-};
-
-jasmine.JQuery.matchersClass = {};
-
-(function(namespace) {
-  var data = {
-    spiedEvents: {},
-    handlers:    []
-  };
-
-  namespace.events = {
-    spyOn: function(selector, eventName) {
-      var handler = function(e) {
-        data.spiedEvents[[selector, eventName]] = e;
-      };
-      jQuery(selector).bind(eventName, handler);
-      data.handlers.push(handler);
-    },
-
-    wasTriggered: function(selector, eventName) {
-      return !!(data.spiedEvents[[selector, eventName]]);
-    },
-
-    cleanUp: function() {
-      data.spiedEvents = {};
-      data.handlers    = [];
-    }
-  }
-})(jasmine.JQuery);
-
-(function(){
-  var jQueryMatchers = {
-    toHaveClass: function(className) {
-      return this.actual.hasClass(className);
-    },
-
-    toBeVisible: function() {
-      return this.actual.is(':visible');
-    },
-
-    toBeHidden: function() {
-      return this.actual.is(':hidden');
-    },
-
-    toBeSelected: function() {
-      return this.actual.is(':selected');
-    },
-
-    toBeChecked: function() {
-      return this.actual.is(':checked');
-    },
-
-    toBeEmpty: function() {
-      return this.actual.is(':empty');
-    },
-
-    toExist: function() {
-      return this.actual.size() > 0;
-    },
-
-    toHaveAttr: function(attributeName, expectedAttributeValue) {
-      return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
-    },
-
-    toHaveId: function(id) {
-      return this.actual.attr('id') == id;
-    },
-
-    toHaveHtml: function(html) {
-      return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
-    },
-
-    toHaveText: function(text) {
-      if (text && jQuery.isFunction(text.test)) {
-        return text.test(this.actual.text());
-      } else {
-        return this.actual.text() == text;
-      }
-    },
-
-    toHaveValue: function(value) {
-      return this.actual.val() == value;
-    },
-
-    toHaveData: function(key, expectedValue) {
-      return hasProperty(this.actual.data(key), expectedValue);
-    },
-
-    toBe: function(selector) {
-      return this.actual.is(selector);
-    },
-
-    toContain: function(selector) {
-      return this.actual.find(selector).size() > 0;
-    },
-
-    toBeDisabled: function(selector){
-      return this.actual.is(':disabled');
-    },
-
-    // tests the existence of a specific event binding
-    toHandle: function(eventName) {
-      var events = this.actual.data("events");
-      return events && events[eventName].length > 0;
-    },
-    
-    // tests the existence of a specific event binding + handler
-    toHandleWith: function(eventName, eventHandler) {
-      var stack = this.actual.data("events")[eventName];
-      var i;
-      for (i = 0; i < stack.length; i++) {
-        if (stack[i].handler == eventHandler) {
-          return true;
-        }
-      }
-      return false;
-    }
-  };
-
-  var hasProperty = function(actualValue, expectedValue) {
-    if (expectedValue === undefined) {
-      return actualValue !== undefined;
-    }
-    return actualValue == expectedValue;
-  };
-
-  var bindMatcher = function(methodName) {
-    var builtInMatcher = jasmine.Matchers.prototype[methodName];
-
-    jasmine.JQuery.matchersClass[methodName] = function() {
-      if (this.actual instanceof jQuery) {
-        var result = jQueryMatchers[methodName].apply(this, arguments);
-        this.actual = jasmine.JQuery.elementToString(this.actual);
-        return result;
-      }
-
-      if (builtInMatcher) {
-        return builtInMatcher.apply(this, arguments);
-      }
-
-      return false;
-    };
-  };
-
-  for(var methodName in jQueryMatchers) {
-    bindMatcher(methodName);
-  }
-})();
-
-beforeEach(function() {
-  this.addMatchers(jasmine.JQuery.matchersClass);
-  this.addMatchers({
-    toHaveBeenTriggeredOn: function(selector) {
-      this.message = function() {
-        return [
-          "Expected event " + this.actual + " to have been triggered on" + selector,
-          "Expected event " + this.actual + " not to have been triggered on" + selector
-        ];
-      };
-      return jasmine.JQuery.events.wasTriggered(selector, this.actual);
-    }
-  })
-});
-
-afterEach(function() {
-  jasmine.getFixtures().cleanUp();
-  jasmine.JQuery.events.cleanUp();
-});
Index: pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery-master.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery-master.js	(revision 19)
+++ pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery-master.js	(revision 19)
@@ -0,0 +1,551 @@
+var readFixtures = function() {
+  return jasmine.getFixtures().proxyCallTo_('read', arguments)
+}
+
+var preloadFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('preload', arguments)
+}
+
+var loadFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('load', arguments)
+}
+
+var appendLoadFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('appendLoad', arguments)
+}
+
+var setFixtures = function(html) {
+  jasmine.getFixtures().proxyCallTo_('set', arguments)
+}
+
+var appendSetFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('appendSet', arguments)
+}
+
+var sandbox = function(attributes) {
+  return jasmine.getFixtures().sandbox(attributes)
+}
+
+var spyOnEvent = function(selector, eventName) {
+  return jasmine.JQuery.events.spyOn(selector, eventName)
+}
+
+var preloadStyleFixtures = function() {
+  jasmine.getStyleFixtures().proxyCallTo_('preload', arguments)
+}
+
+var loadStyleFixtures = function() {
+  jasmine.getStyleFixtures().proxyCallTo_('load', arguments)
+}
+
+var appendLoadStyleFixtures = function() {
+  jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments)
+}
+
+var setStyleFixtures = function(html) {
+  jasmine.getStyleFixtures().proxyCallTo_('set', arguments)
+}
+
+var appendSetStyleFixtures = function(html) {
+  jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments)
+}
+
+var loadJSONFixtures = function() {
+  return jasmine.getJSONFixtures().proxyCallTo_('load', arguments)
+}
+
+var getJSONFixture = function(url) {
+  return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url]
+}
+
+jasmine.spiedEventsKey = function (selector, eventName) {
+  return [$(selector).selector, eventName].toString()
+}
+
+jasmine.getFixtures = function() {
+  return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures()
+}
+
+jasmine.getStyleFixtures = function() {
+  return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures()
+}
+
+jasmine.Fixtures = function() {
+  this.containerId = 'jasmine-fixtures'
+  this.fixturesCache_ = {}
+  this.fixturesPath = 'spec/javascripts/fixtures'
+}
+
+jasmine.Fixtures.prototype.set = function(html) {
+  this.cleanUp()
+  this.createContainer_(html)
+}
+
+jasmine.Fixtures.prototype.appendSet= function(html) {
+  this.addToContainer_(html)
+}
+
+jasmine.Fixtures.prototype.preload = function() {
+  this.read.apply(this, arguments)
+}
+
+jasmine.Fixtures.prototype.load = function() {
+  this.cleanUp()
+  this.createContainer_(this.read.apply(this, arguments))
+}
+
+jasmine.Fixtures.prototype.appendLoad = function() {
+  this.addToContainer_(this.read.apply(this, arguments))
+}
+
+jasmine.Fixtures.prototype.read = function() {
+  var htmlChunks = []
+
+  var fixtureUrls = arguments
+  for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
+    htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]))
+  }
+
+  return htmlChunks.join('')
+}
+
+jasmine.Fixtures.prototype.clearCache = function() {
+  this.fixturesCache_ = {}
+}
+
+jasmine.Fixtures.prototype.cleanUp = function() {
+  $('#' + this.containerId).remove()
+}
+
+jasmine.Fixtures.prototype.sandbox = function(attributes) {
+  var attributesToSet = attributes || {}
+  return $('<div id="sandbox" />').attr(attributesToSet)
+}
+
+jasmine.Fixtures.prototype.createContainer_ = function(html) {
+  var container
+  if(html instanceof $) {
+    container = $('<div id="' + this.containerId + '" />')
+    container.html(html)
+  } else {
+    container = '<div id="' + this.containerId + '">' + html + '</div>'
+  }
+  $(document.body).append(container)
+}
+
+jasmine.Fixtures.prototype.addToContainer_ = function(html){
+  var container = $(document.body).find('#'+this.containerId).append(html)
+  if(!container.length){
+    this.createContainer_(html)
+  }
+}
+
+jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
+  if (typeof this.fixturesCache_[url] === 'undefined') {
+    this.loadFixtureIntoCache_(url)
+  }
+  return this.fixturesCache_[url]
+}
+
+jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
+  var url = this.makeFixtureUrl_(relativeUrl)
+  var request = $.ajax({
+    type: "GET",
+    url: url + "?" + new Date().getTime(),
+    async: false
+  })
+  this.fixturesCache_[relativeUrl] = request.responseText
+}
+
+jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){
+  return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
+}
+
+jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
+  return this[methodName].apply(this, passedArguments)
+}
+
+
+jasmine.StyleFixtures = function() {
+  this.fixturesCache_ = {}
+  this.fixturesNodes_ = []
+  this.fixturesPath = 'spec/javascripts/fixtures'
+}
+
+jasmine.StyleFixtures.prototype.set = function(css) {
+  this.cleanUp()
+  this.createStyle_(css)
+}
+
+jasmine.StyleFixtures.prototype.appendSet = function(css) {
+  this.createStyle_(css)
+}
+
+jasmine.StyleFixtures.prototype.preload = function() {
+  this.read_.apply(this, arguments)
+}
+
+jasmine.StyleFixtures.prototype.load = function() {
+  this.cleanUp()
+  this.createStyle_(this.read_.apply(this, arguments))
+}
+
+jasmine.StyleFixtures.prototype.appendLoad = function() {
+  this.createStyle_(this.read_.apply(this, arguments))
+}
+
+jasmine.StyleFixtures.prototype.cleanUp = function() {
+  while(this.fixturesNodes_.length) {
+    this.fixturesNodes_.pop().remove()
+  }
+}
+
+jasmine.StyleFixtures.prototype.createStyle_ = function(html) {
+  var styleText = $('<div></div>').html(html).text(),
+    style = $('<style>' + styleText + '</style>')
+
+  this.fixturesNodes_.push(style)
+
+  $('head').append(style)
+}
+
+jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache
+
+jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read
+
+jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_
+
+jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_
+
+jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_
+
+jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_
+
+jasmine.getJSONFixtures = function() {
+  return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures()
+}
+
+jasmine.JSONFixtures = function() {
+  this.fixturesCache_ = {}
+  this.fixturesPath = 'spec/javascripts/fixtures/json'
+}
+
+jasmine.JSONFixtures.prototype.load = function() {
+  this.read.apply(this, arguments)
+  return this.fixturesCache_
+}
+
+jasmine.JSONFixtures.prototype.read = function() {
+  var fixtureUrls = arguments
+  for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
+    this.getFixtureData_(fixtureUrls[urlIndex])
+  }
+  return this.fixturesCache_
+}
+
+jasmine.JSONFixtures.prototype.clearCache = function() {
+  this.fixturesCache_ = {}
+}
+
+jasmine.JSONFixtures.prototype.getFixtureData_ = function(url) {
+  this.loadFixtureIntoCache_(url)
+  return this.fixturesCache_[url]
+}
+
+jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
+  var self = this
+  var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
+  $.ajax({
+    async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
+    cache: false,
+    dataType: 'json',
+    url: url,
+    success: function(data) {
+      self.fixturesCache_[relativeUrl] = data
+    },
+    fail: function(jqXHR, status, errorThrown) {
+        throw Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')')
+    }
+  })
+}
+
+jasmine.JSONFixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
+  return this[methodName].apply(this, passedArguments)
+}
+
+jasmine.JQuery = function() {}
+
+jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
+  return $('<div/>').append(html).html()
+}
+
+jasmine.JQuery.elementToString = function(element) {
+  var domEl = $(element).get(0)
+  if (domEl == undefined || domEl.cloneNode)
+    return $('<div />').append($(element).clone()).html()
+  else
+    return element.toString()
+}
+
+jasmine.JQuery.matchersClass = {}
+
+!function(namespace) {
+  var data = {
+    spiedEvents: {},
+    handlers:    []
+  }
+
+  namespace.events = {
+    spyOn: function(selector, eventName) {
+      var handler = function(e) {
+        data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = e
+      }
+      $(selector).bind(eventName, handler)
+      data.handlers.push(handler)
+      return {
+        selector: selector,
+        eventName: eventName,
+        handler: handler,
+        reset: function(){
+          delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
+        }
+      }
+    },
+
+    wasTriggered: function(selector, eventName) {
+      return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)])
+    },
+
+    wasPrevented: function(selector, eventName) {
+      var e;
+      return (e = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) && e.isDefaultPrevented()
+    },
+
+    cleanUp: function() {
+      data.spiedEvents = {}
+      data.handlers    = []
+    }
+  }
+}(jasmine.JQuery)
+
+!function(){
+  var jQueryMatchers = {
+    toHaveClass: function(className) {
+      return this.actual.hasClass(className)
+    },
+
+    toHaveCss: function(css){
+      for (var prop in css){
+        if (this.actual.css(prop) !== css[prop]) return false
+      }
+      return true
+    },
+
+    toBeVisible: function() {
+      return this.actual.is(':visible')
+    },
+
+    toBeHidden: function() {
+      return this.actual.is(':hidden')
+    },
+
+    toBeSelected: function() {
+      return this.actual.is(':selected')
+    },
+
+    toBeChecked: function() {
+      return this.actual.is(':checked')
+    },
+
+    toBeEmpty: function() {
+      return this.actual.is(':empty')
+    },
+
+    toExist: function() {
+      return $(document).find(this.actual).length
+    },
+
+    toHaveLength: function(length) {
+      return this.actual.length === length
+    },
+
+    toHaveAttr: function(attributeName, expectedAttributeValue) {
+      return hasProperty(this.actual.attr(attributeName), expectedAttributeValue)
+    },
+
+    toHaveProp: function(propertyName, expectedPropertyValue) {
+      return hasProperty(this.actual.prop(propertyName), expectedPropertyValue)
+    },
+
+    toHaveId: function(id) {
+      return this.actual.attr('id') == id
+    },
+
+    toHaveHtml: function(html) {
+      return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html)
+    },
+
+    toContainHtml: function(html){
+      var actualHtml = this.actual.html()
+      var expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html)
+      return (actualHtml.indexOf(expectedHtml) >= 0)
+    },
+
+    toHaveText: function(text) {
+      var trimmedText = $.trim(this.actual.text())
+      if (text && $.isFunction(text.test)) {
+        return text.test(trimmedText)
+      } else {
+        return trimmedText == text
+      }
+    },
+
+    toHaveValue: function(value) {
+      return this.actual.val() === value
+    },
+
+    toHaveData: function(key, expectedValue) {
+      return hasProperty(this.actual.data(key), expectedValue)
+    },
+
+    toBe: function(selector) {
+      return this.actual.is(selector)
+    },
+
+    toContain: function(selector) {
+      return this.actual.find(selector).length
+    },
+
+    toBeDisabled: function(selector){
+      return this.actual.is(':disabled')
+    },
+
+    toBeFocused: function(selector) {
+      return this.actual[0] === this.actual[0].ownerDocument.activeElement
+    },
+
+    toHandle: function(event) {
+
+      var events = $._data(this.actual.get(0), "events")
+
+      if(!events || !event || typeof event !== "string") {
+        return false
+      }
+
+      var namespaces = event.split(".")
+      var eventType = namespaces.shift()
+      var sortedNamespaces = namespaces.slice(0).sort()
+      var namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)")
+
+      if(events[eventType] && namespaces.length) {
+        for(var i = 0; i < events[eventType].length; i++) {
+          var namespace = events[eventType][i].namespace
+          if(namespaceRegExp.test(namespace)) {
+            return true
+          }
+        }
+      } else {
+        return events[eventType] && events[eventType].length > 0
+      }
+    },
+
+    // tests the existence of a specific event binding + handler
+    toHandleWith: function(eventName, eventHandler) {
+      var stack = $._data(this.actual.get(0), "events")[eventName]
+      for (var i = 0; i < stack.length; i++) {
+        if (stack[i].handler == eventHandler) return true
+      }
+      return false
+    }
+  }
+
+  var hasProperty = function(actualValue, expectedValue) {
+    if (expectedValue === undefined) return actualValue !== undefined
+    return actualValue == expectedValue
+  }
+
+  var bindMatcher = function(methodName) {
+    var builtInMatcher = jasmine.Matchers.prototype[methodName]
+
+    jasmine.JQuery.matchersClass[methodName] = function() {
+      if (this.actual
+        && (this.actual instanceof $
+          || jasmine.isDomNode(this.actual))) {
+            this.actual = $(this.actual)
+            var result = jQueryMatchers[methodName].apply(this, arguments)
+            var element
+            if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML")
+              this.actual = jasmine.JQuery.elementToString(this.actual)
+            return result
+          }
+
+          if (builtInMatcher) {
+            return builtInMatcher.apply(this, arguments)
+          }
+
+          return false
+    }
+  }
+
+  for(var methodName in jQueryMatchers) {
+    bindMatcher(methodName)
+  }
+}()
+
+beforeEach(function() {
+  this.addMatchers(jasmine.JQuery.matchersClass)
+  this.addMatchers({
+    toHaveBeenTriggeredOn: function(selector) {
+      this.message = function() {
+        return [
+          "Expected event " + this.actual + " to have been triggered on " + selector,
+          "Expected event " + this.actual + " not to have been triggered on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasTriggered(selector, this.actual)
+    }
+  })
+  this.addMatchers({
+    toHaveBeenTriggered: function(){
+      var eventName = this.actual.eventName,
+          selector = this.actual.selector
+      this.message = function() {
+        return [
+          "Expected event " + eventName + " to have been triggered on " + selector,
+          "Expected event " + eventName + " not to have been triggered on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasTriggered(selector, eventName)
+     }
+  })
+  this.addMatchers({
+    toHaveBeenPreventedOn: function(selector) {
+      this.message = function() {
+        return [
+          "Expected event " + this.actual + " to have been prevented on " + selector,
+          "Expected event " + this.actual + " not to have been prevented on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasPrevented(selector, this.actual)
+    }
+  })
+  this.addMatchers({
+    toHaveBeenPrevented: function() {
+      var eventName = this.actual.eventName,
+          selector = this.actual.selector
+      this.message = function() {
+        return [
+          "Expected event " + eventName + " to have been prevented on " + selector,
+          "Expected event " + eventName + " not to have been prevented on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasPrevented(selector, eventName)
+    }
+  })
+})
+
+afterEach(function() {
+  jasmine.getFixtures().cleanUp()
+  jasmine.getStyleFixtures().cleanUp()
+  jasmine.JQuery.events.cleanUp()
+})
Index: pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery.js	(revision 0)
+++ pyenvjasmine/envjasmine/lib/jasmine-jquery/jasmine-jquery.js	(revision 19)
@@ -1,288 +1,551 @@
 var readFixtures = function() {
-  return jasmine.getFixtures().proxyCallTo_('read', arguments);
-};
+  return jasmine.getFixtures().proxyCallTo_('read', arguments)
+}
 
 var preloadFixtures = function() {
-  jasmine.getFixtures().proxyCallTo_('preload', arguments);
-};
+  jasmine.getFixtures().proxyCallTo_('preload', arguments)
+}
 
 var loadFixtures = function() {
-  jasmine.getFixtures().proxyCallTo_('load', arguments);
-};
+  jasmine.getFixtures().proxyCallTo_('load', arguments)
+}
+
+var appendLoadFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('appendLoad', arguments)
+}
 
 var setFixtures = function(html) {
-  jasmine.getFixtures().set(html);
-};
+  jasmine.getFixtures().proxyCallTo_('set', arguments)
+}
+
+var appendSetFixtures = function() {
+  jasmine.getFixtures().proxyCallTo_('appendSet', arguments)
+}
 
 var sandbox = function(attributes) {
-  return jasmine.getFixtures().sandbox(attributes);
-};
+  return jasmine.getFixtures().sandbox(attributes)
+}
 
 var spyOnEvent = function(selector, eventName) {
-  jasmine.JQuery.events.spyOn(selector, eventName);
+  return jasmine.JQuery.events.spyOn(selector, eventName)
+}
+
+var preloadStyleFixtures = function() {
+  jasmine.getStyleFixtures().proxyCallTo_('preload', arguments)
+}
+
+var loadStyleFixtures = function() {
+  jasmine.getStyleFixtures().proxyCallTo_('load', arguments)
+}
+
+var appendLoadStyleFixtures = function() {
+  jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments)
+}
+
+var setStyleFixtures = function(html) {
+  jasmine.getStyleFixtures().proxyCallTo_('set', arguments)
+}
+
+var appendSetStyleFixtures = function(html) {
+  jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments)
+}
+
+var loadJSONFixtures = function() {
+  return jasmine.getJSONFixtures().proxyCallTo_('load', arguments)
+}
+
+var getJSONFixture = function(url) {
+  return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url]
+}
+
+jasmine.spiedEventsKey = function (selector, eventName) {
+  return [$(selector).selector, eventName].toString()
 }
 
 jasmine.getFixtures = function() {
-  return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
-};
+  return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures()
+}
+
+jasmine.getStyleFixtures = function() {
+  return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures()
+}
 
 jasmine.Fixtures = function() {
-  this.containerId = 'jasmine-fixtures';
-  this.fixturesCache_ = {};
-  this.fixturesPath = 'spec/javascripts/fixtures';
-};
+  this.containerId = 'jasmine-fixtures'
+  this.fixturesCache_ = {}
+  this.fixturesPath = 'spec/javascripts/fixtures'
+}
 
 jasmine.Fixtures.prototype.set = function(html) {
-  this.cleanUp();
-  this.createContainer_(html);
-};
+  this.cleanUp()
+  this.createContainer_(html)
+}
+
+jasmine.Fixtures.prototype.appendSet= function(html) {
+  this.addToContainer_(html)
+}
 
 jasmine.Fixtures.prototype.preload = function() {
-  this.read.apply(this, arguments);
-};
+  this.read.apply(this, arguments)
+}
 
 jasmine.Fixtures.prototype.load = function() {
-  this.cleanUp();
-  this.createContainer_(this.read.apply(this, arguments));
-};
+  this.cleanUp()
+  this.createContainer_(this.read.apply(this, arguments))
+}
+
+jasmine.Fixtures.prototype.appendLoad = function() {
+  this.addToContainer_(this.read.apply(this, arguments))
+}
 
 jasmine.Fixtures.prototype.read = function() {
-  var htmlChunks = [];
-
-  var fixtureUrls = arguments;
+  var htmlChunks = []
+
+  var fixtureUrls = arguments
   for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
-    htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
-  }
-
-  return htmlChunks.join('');
-};
+    htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]))
+  }
+
+  return htmlChunks.join('')
+}
 
 jasmine.Fixtures.prototype.clearCache = function() {
-  this.fixturesCache_ = {};
-};
+  this.fixturesCache_ = {}
+}
 
 jasmine.Fixtures.prototype.cleanUp = function() {
-  jQuery('#' + this.containerId).remove();
-};
+  $('#' + this.containerId).remove()
+}
 
 jasmine.Fixtures.prototype.sandbox = function(attributes) {
-  var attributesToSet = attributes || {};
-  return jQuery('<div id="sandbox" />').attr(attributesToSet);
-};
+  var attributesToSet = attributes || {}
+  return $('<div id="sandbox" />').attr(attributesToSet)
+}
 
 jasmine.Fixtures.prototype.createContainer_ = function(html) {
-  var container;
-  if(html instanceof jQuery) {
-    container = jQuery('<div id="' + this.containerId + '" />');
-    container.html(html);
+  var container
+  if(html instanceof $) {
+    container = $('<div id="' + this.containerId + '" />')
+    container.html(html)
   } else {
     container = '<div id="' + this.containerId + '">' + html + '</div>'
   }
-  jQuery('body').append(container);
-};
-
-jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {  
-  if (typeof this.fixturesCache_[url] == 'undefined') {
-    this.loadFixtureIntoCache_(url);
-  }
-  return this.fixturesCache_[url];
-};
+  $(document.body).append(container)
+}
+
+jasmine.Fixtures.prototype.addToContainer_ = function(html){
+  var container = $(document.body).find('#'+this.containerId).append(html)
+  if(!container.length){
+    this.createContainer_(html)
+  }
+}
+
+jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
+  if (typeof this.fixturesCache_[url] === 'undefined') {
+    this.loadFixtureIntoCache_(url)
+  }
+  return this.fixturesCache_[url]
+}
 
 jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
-  var self = this;
-  var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
-  jQuery.ajax({
+  var url = this.makeFixtureUrl_(relativeUrl)
+  var request = $.ajax({
+    type: "GET",
+    url: url + "?" + new Date().getTime(),
+    async: false
+  })
+  this.fixturesCache_[relativeUrl] = request.responseText
+}
+
+jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){
+  return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
+}
+
+jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
+  return this[methodName].apply(this, passedArguments)
+}
+
+
+jasmine.StyleFixtures = function() {
+  this.fixturesCache_ = {}
+  this.fixturesNodes_ = []
+  this.fixturesPath = 'spec/javascripts/fixtures'
+}
+
+jasmine.StyleFixtures.prototype.set = function(css) {
+  this.cleanUp()
+  this.createStyle_(css)
+}
+
+jasmine.StyleFixtures.prototype.appendSet = function(css) {
+  this.createStyle_(css)
+}
+
+jasmine.StyleFixtures.prototype.preload = function() {
+  this.read_.apply(this, arguments)
+}
+
+jasmine.StyleFixtures.prototype.load = function() {
+  this.cleanUp()
+  this.createStyle_(this.read_.apply(this, arguments))
+}
+
+jasmine.StyleFixtures.prototype.appendLoad = function() {
+  this.createStyle_(this.read_.apply(this, arguments))
+}
+
+jasmine.StyleFixtures.prototype.cleanUp = function() {
+  while(this.fixturesNodes_.length) {
+    this.fixturesNodes_.pop().remove()
+  }
+}
+
+jasmine.StyleFixtures.prototype.createStyle_ = function(html) {
+  var styleText = $('<div></div>').html(html).text(),
+    style = $('<style>' + styleText + '</style>')
+
+  this.fixturesNodes_.push(style)
+
+  $('head').append(style)
+}
+
+jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache
+
+jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read
+
+jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_
+
+jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_
+
+jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_
+
+jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_
+
+jasmine.getJSONFixtures = function() {
+  return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures()
+}
+
+jasmine.JSONFixtures = function() {
+  this.fixturesCache_ = {}
+  this.fixturesPath = 'spec/javascripts/fixtures/json'
+}
+
+jasmine.JSONFixtures.prototype.load = function() {
+  this.read.apply(this, arguments)
+  return this.fixturesCache_
+}
+
+jasmine.JSONFixtures.prototype.read = function() {
+  var fixtureUrls = arguments
+  for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
+    this.getFixtureData_(fixtureUrls[urlIndex])
+  }
+  return this.fixturesCache_
+}
+
+jasmine.JSONFixtures.prototype.clearCache = function() {
+  this.fixturesCache_ = {}
+}
+
+jasmine.JSONFixtures.prototype.getFixtureData_ = function(url) {
+  this.loadFixtureIntoCache_(url)
+  return this.fixturesCache_[url]
+}
+
+jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
+  var self = this
+  var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl
+  $.ajax({
     async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
     cache: false,
-    dataType: 'html',
+    dataType: 'json',
     url: url,
     success: function(data) {
-      self.fixturesCache_[relativeUrl] = data;
-    },
-    error: function(jqXHR, status, errorThrown) {
-        throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
-    }
-  });
-};
-
-jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
-  return this[methodName].apply(this, passedArguments);
-};
-
-
-jasmine.JQuery = function() {};
+      self.fixturesCache_[relativeUrl] = data
+    },
+    fail: function(jqXHR, status, errorThrown) {
+        throw Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')')
+    }
+  })
+}
+
+jasmine.JSONFixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
+  return this[methodName].apply(this, passedArguments)
+}
+
+jasmine.JQuery = function() {}
 
 jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
-  return jQuery('<div/>').append(html).html();
-};
+  return $('<div/>').append(html).html()
+}
 
 jasmine.JQuery.elementToString = function(element) {
-  return jQuery('<div />').append(element.clone()).html();
-};
-
-jasmine.JQuery.matchersClass = {};
-
-(function(namespace) {
+  var domEl = $(element).get(0)
+  if (domEl == undefined || domEl.cloneNode)
+    return $('<div />').append($(element).clone()).html()
+  else
+    return element.toString()
+}
+
+jasmine.JQuery.matchersClass = {}
+
+!function(namespace) {
   var data = {
     spiedEvents: {},
     handlers:    []
-  };
+  }
 
   namespace.events = {
     spyOn: function(selector, eventName) {
       var handler = function(e) {
-        data.spiedEvents[[selector, eventName]] = e;
-      };
-      jQuery(selector).bind(eventName, handler);
-      data.handlers.push(handler);
+        data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = e
+      }
+      $(selector).bind(eventName, handler)
+      data.handlers.push(handler)
+      return {
+        selector: selector,
+        eventName: eventName,
+        handler: handler,
+        reset: function(){
+          delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]
+        }
+      }
     },
 
     wasTriggered: function(selector, eventName) {
-      return !!(data.spiedEvents[[selector, eventName]]);
+      return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)])
+    },
+
+    wasPrevented: function(selector, eventName) {
+      var e;
+      return (e = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) && e.isDefaultPrevented()
     },
 
     cleanUp: function() {
-      data.spiedEvents = {};
-      data.handlers    = [];
-    }
-  }
-})(jasmine.JQuery);
-
-(function(){
+      data.spiedEvents = {}
+      data.handlers    = []
+    }
+  }
+}(jasmine.JQuery)
+
+!function(){
   var jQueryMatchers = {
     toHaveClass: function(className) {
-      return this.actual.hasClass(className);
+      return this.actual.hasClass(className)
+    },
+
+    toHaveCss: function(css){
+      for (var prop in css){
+        if (this.actual.css(prop) !== css[prop]) return false
+      }
+      return true
     },
 
     toBeVisible: function() {
-      return this.actual.is(':visible');
+      return this.actual.is(':visible')
     },
 
     toBeHidden: function() {
-      return this.actual.is(':hidden');
+      return this.actual.is(':hidden')
     },
 
     toBeSelected: function() {
-      return this.actual.is(':selected');
+      return this.actual.is(':selected')
     },
 
     toBeChecked: function() {
-      return this.actual.is(':checked');
+      return this.actual.is(':checked')
     },
 
     toBeEmpty: function() {
-      return this.actual.is(':empty');
+      return this.actual.is(':empty')
     },
 
     toExist: function() {
-      return this.actual.size() > 0;
+      return $(document).find(this.actual).length
+    },
+
+    toHaveLength: function(length) {
+      return this.actual.length === length
     },
 
     toHaveAttr: function(attributeName, expectedAttributeValue) {
-      return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
+      return hasProperty(this.actual.attr(attributeName), expectedAttributeValue)
+    },
+
+    toHaveProp: function(propertyName, expectedPropertyValue) {
+      return hasProperty(this.actual.prop(propertyName), expectedPropertyValue)
     },
 
     toHaveId: function(id) {
-      return this.actual.attr('id') == id;
+      return this.actual.attr('id') == id
     },
 
     toHaveHtml: function(html) {
-      return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
+      return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html)
+    },
+
+    toContainHtml: function(html){
+      var actualHtml = this.actual.html()
+      var expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html)
+      return (actualHtml.indexOf(expectedHtml) >= 0)
     },
 
     toHaveText: function(text) {
-      if (text && jQuery.isFunction(text.test)) {
-        return text.test(this.actual.text());
+      var trimmedText = $.trim(this.actual.text())
+      if (text && $.isFunction(text.test)) {
+        return text.test(trimmedText)
       } else {
-        return this.actual.text() == text;
+        return trimmedText == text
       }
     },
 
     toHaveValue: function(value) {
-      return this.actual.val() == value;
+      return this.actual.val() === value
     },
 
     toHaveData: function(key, expectedValue) {
-      return hasProperty(this.actual.data(key), expectedValue);
+      return hasProperty(this.actual.data(key), expectedValue)
     },
 
     toBe: function(selector) {
-      return this.actual.is(selector);
+      return this.actual.is(selector)
     },
 
     toContain: function(selector) {
-      return this.actual.find(selector).size() > 0;
+      return this.actual.find(selector).length
     },
 
     toBeDisabled: function(selector){
-      return this.actual.is(':disabled');
-    },
-
-    // tests the existence of a specific event binding
-    toHandle: function(eventName) {
-      var events = this.actual.data("events");
-      return events && events[eventName].length > 0;
-    },
-    
+      return this.actual.is(':disabled')
+    },
+
+    toBeFocused: function(selector) {
+      return this.actual[0] === this.actual[0].ownerDocument.activeElement
+    },
+
+    toHandle: function(event) {
+
+      var events = $._data(this.actual.get(0), "events")
+
+      if(!events || !event || typeof event !== "string") {
+        return false
+      }
+
+      var namespaces = event.split(".")
+      var eventType = namespaces.shift()
+      var sortedNamespaces = namespaces.slice(0).sort()
+      var namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)")
+
+      if(events[eventType] && namespaces.length) {
+        for(var i = 0; i < events[eventType].length; i++) {
+          var namespace = events[eventType][i].namespace
+          if(namespaceRegExp.test(namespace)) {
+            return true
+          }
+        }
+      } else {
+        return events[eventType] && events[eventType].length > 0
+      }
+    },
+
     // tests the existence of a specific event binding + handler
     toHandleWith: function(eventName, eventHandler) {
-      var stack = this.actual.data("events")[eventName];
-      var i;
-      for (i = 0; i < stack.length; i++) {
-        if (stack[i].handler == eventHandler) {
-          return true;
-        }
-      }
-      return false;
-    }
-  };
+      var stack = $._data(this.actual.get(0), "events")[eventName]
+      for (var i = 0; i < stack.length; i++) {
+        if (stack[i].handler == eventHandler) return true
+      }
+      return false
+    }
+  }
 
   var hasProperty = function(actualValue, expectedValue) {
-    if (expectedValue === undefined) {
-      return actualValue !== undefined;
-    }
-    return actualValue == expectedValue;
-  };
+    if (expectedValue === undefined) return actualValue !== undefined
+    return actualValue == expectedValue
+  }
 
   var bindMatcher = function(methodName) {
-    var builtInMatcher = jasmine.Matchers.prototype[methodName];
+    var builtInMatcher = jasmine.Matchers.prototype[methodName]
 
     jasmine.JQuery.matchersClass[methodName] = function() {
-      if (this.actual instanceof jQuery) {
-        var result = jQueryMatchers[methodName].apply(this, arguments);
-        this.actual = jasmine.JQuery.elementToString(this.actual);
-        return result;
-      }
-
-      if (builtInMatcher) {
-        return builtInMatcher.apply(this, arguments);
-      }
-
-      return false;
-    };
-  };
+      if (this.actual
+        && (this.actual instanceof $
+          || jasmine.isDomNode(this.actual))) {
+            this.actual = $(this.actual)
+            var result = jQueryMatchers[methodName].apply(this, arguments)
+            var element
+            if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML")
+              this.actual = jasmine.JQuery.elementToString(this.actual)
+            return result
+          }
+
+          if (builtInMatcher) {
+            return builtInMatcher.apply(this, arguments)
+          }
+
+          return false
+    }
+  }
 
   for(var methodName in jQueryMatchers) {
-    bindMatcher(methodName);
-  }
-})();
+    bindMatcher(methodName)
+  }
+}()
 
 beforeEach(function() {
-  this.addMatchers(jasmine.JQuery.matchersClass);
+  this.addMatchers(jasmine.JQuery.matchersClass)
   this.addMatchers({
     toHaveBeenTriggeredOn: function(selector) {
       this.message = function() {
         return [
-          "Expected event " + this.actual + " to have been triggered on" + selector,
-          "Expected event " + this.actual + " not to have been triggered on" + selector
-        ];
-      };
-      return jasmine.JQuery.events.wasTriggered(selector, this.actual);
+          "Expected event " + this.actual + " to have been triggered on " + selector,
+          "Expected event " + this.actual + " not to have been triggered on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasTriggered(selector, this.actual)
     }
   })
-});
+  this.addMatchers({
+    toHaveBeenTriggered: function(){
+      var eventName = this.actual.eventName,
+          selector = this.actual.selector
+      this.message = function() {
+        return [
+          "Expected event " + eventName + " to have been triggered on " + selector,
+          "Expected event " + eventName + " not to have been triggered on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasTriggered(selector, eventName)
+     }
+  })
+  this.addMatchers({
+    toHaveBeenPreventedOn: function(selector) {
+      this.message = function() {
+        return [
+          "Expected event " + this.actual + " to have been prevented on " + selector,
+          "Expected event " + this.actual + " not to have been prevented on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasPrevented(selector, this.actual)
+    }
+  })
+  this.addMatchers({
+    toHaveBeenPrevented: function() {
+      var eventName = this.actual.eventName,
+          selector = this.actual.selector
+      this.message = function() {
+        return [
+          "Expected event " + eventName + " to have been prevented on " + selector,
+          "Expected event " + eventName + " not to have been prevented on " + selector
+        ]
+      }
+      return jasmine.JQuery.events.wasPrevented(selector, eventName)
+    }
+  })
+})
 
 afterEach(function() {
-  jasmine.getFixtures().cleanUp();
-  jasmine.JQuery.events.cleanUp();
-});
+  jasmine.getFixtures().cleanUp()
+  jasmine.getStyleFixtures().cleanUp()
+  jasmine.JQuery.events.cleanUp()
+})
Index: pyenvjasmine/envjasmine/lib/jscover/README.textile
===================================================================
--- pyenvjasmine/envjasmine/lib/jscover/README.textile	(revision 19)
+++ pyenvjasmine/envjasmine/lib/jscover/README.textile	(revision 19)
@@ -0,0 +1,157 @@
+h1. JSCover and Sonar integration for EnvJasmine (beta)
+
+Allows you to pull code coverage metrics for EnvJasmine tests and view the results in Sonar.
+
+h2. 3rd Party Tools
+
+JSCover is a code coverage tool for javascript. See http://tntim96.github.com/JSCover/
+
+Sonar is a free, open source, code quality analytics tool. See http://www.sonarsource.org/
+
+Sonar has a plugin to report on javascript quality metrics. See http://docs.codehaus.org/display/SONAR/JavaScript+Plugin
+
+Maven (included example) or Sonar Runner (default example). See http://maven.apache.org/ or http://docs.codehaus.org/display/SONAR/Installing+and+Configuring+Sonar+Runner
+
+h2. Prerequisites
+
+This readme assumes: 
+
+you already familiar with Sonar,
+you are already the concept of code coverage,
+you have maven or Sonar Runner installed (depending on which example you plan to run)
+
+h2. Running The example
+
+To calculate and report code coverage metrics to Sonar:
+
+Install Sonar.
+Install Sonar-Runner.
+Install the Sonar javascript plugin.
+Run your local Sonar server.
+In @include/dependencies.js@, uncomment @EnvJasmine.loadGlobal(EnvJasmine.libDir + "/jscover/envjasmine-sonar-coverage-properties.js");@
+In @include/dependencies.js@, uncomment @EnvJasmine.loadGlobal(EnvJasmine.coverage.envjasmine_coverage_js);@
+Execute @bin/run_coverage.sh --sonarMethod=sonar-runner@
+Find the project in Sonar and add the code coverage widget to your dashboard.
+
+h2. How to configure for use in your project
+
+h3. dependencies.js
+
+In your @dependencies.js@ uncomment the 2 lines loading the coverage properties file and the coverage plugin.
+
+h3. command line options
+
+projectRoot - the root folder of your project.
+originalDir - the root folder of your javascript.
+instrumentedDir - the folder you want your javascript copied to.
+reportsDir - the output directory for sonar reports.
+sonarMethod - "sonar-runner" or "maven" depending on which method you use. Or "none" if you are going to run sonar manually.
+noInstrument - files you want excluded from instrumentation (such as jquery). This can be set multiple times. This path is relative to the originalDir.
+debug - will output extra information to the console.
+cleanup - will delete the reports and instrumentation directory after completion.
+
+h3. sonar.js.xml (if you use maven)
+
+This must stay in the root directory.
+The "modelVersion" property must be compatible with your version of maven.
+"sourceDirectory" should be set to the root javascript directory.
+"sonar.exclusions" will exclude files from sonar analytics. "*" and "**" wildcards can be used.
+"sonar.javascript.testframework" must not be edited
+"sonar.javascript.jstestdriver.reportsfolder" should be set to your temp reports folder.
+All other properties are identifying labels and can be edited as long as no other process relies on them.
+
+h3. sonar-project.properties (if you use sonar-runner)
+
+Check out the documentation at http://docs.codehaus.org/display/SONAR/Installing+and+Configuring+Sonar+Runner
+
+h2. File overview
+
+@bin/run_coverage.sh@ is the main files which kick off the process.
+@include/dependencies.js@ loads EnvJasmine dependencies including the coverage plugin.
+@lib/envjasmine.js@ is the same EnvJasmine file you're been using to run your EnvJasmine tests.
+@lib/jscover/envjasmine-sonar-coverage-runner.js@ is the main execution script called by run_coverage.
+@lib/jscover/envjasmine-sonar-coverage-properties.js@ contains almost of the variables you will need to set to configure your setup.
+@lib/jscover/envjasmine-sonar-coverage-helper.js@ contains helper functions.
+@lib/jscover/envjasmine-coverage.js@ is the EnvJasmine coverage plugin. This adds itself to EnvJasmine's post test functions and writes the coverage data to disk.
+@lib/jscover/JSCover-all.jar@ is a copy of the JSCover jar.
+@samples/notCoveredDemo.js@ and @specs/notCoveredDemo.js@ are files that demo coverage gaps.
+@sonar.js.xml@ is the minimal pom file needed to get data into sonar if you use maven. Unfortunately, this needs to be in the project root.
+@sonar-project.properties@ is the config file for sonar-runner.
+
+h2. Detailed discussion of envjasmine-sonar-coverage-runner.js
+
+h3. Initialization
+
+This script file starts by reading the command line arguments and loading it's dependencies including properties and helper functions. It also create temp directories.
+
+h3. Instrumenting javascript code
+
+We need to create an instrumented copy of javascript code using JSCover. We pass in "no-instrument" args to tell jscover not instrument a file or directory and instead copy the original(s). This is good for ignoring vendor code, such as jquery which takes a long time to instrument and some vendor minified code breaks JSCover.
+
+h3. Exposing coverage data to EnvJasmine
+
+JSCover creates instrumented code that puts the output data into a local javascript variable. That variable is inaccessible to EnvJasmine. For now we're implementing a hack to find/replace the variable with an EnvJasmine scoped variable in the instrumented code files.
+
+h3. Run all EnvJasmine tests against the instrumented code
+
+Run the tests as normal against the instrumented code. Since the envjasmine-coverage plugin was included in @include/dependencies.js@ the plugin will write out the code coverage json data object to the reports directory.
+
+h3. Convert the json data to lcov format
+
+Sonar uses lcov format. JSCover has the ability to do this conversion.
+
+h3. Fix source code location in the lcov file
+
+For some reason, the source code root is set to the reports directory. Change this to the original source directory.
+
+h3. Remove branch data
+
+JSCover supports branch data. The Sonar javascript plugin doesn't. (It's on their roadmap.) For now we have to strip out branch data or sonar will break.
+
+h3. Run Sonar
+
+Runs sonar against the reports directory
+
+h3. Cleanup
+
+Deletes temp directories.
+
+h2. FAQ/Common Problems
+
+h3. Directory/Path problems
+
+It's really easy to be looking for the wrong file in the wrong place.  The important directories are the source directory, the instrumented directory, the reports directory, and the lib directory. If you're having trouble make sure the files are all being read and output in the correct places. Debug statements can be helpful if you've lost your way setting this up.
+
+h3. Could not find jscoverage.json
+
+Exception in thread "main" java.lang.RuntimeException: Problem loading file: '/my/reports/directory/jscoverage.json'
+
+Make sure to uncomment the necessary lines in the dependencies.js files.
+
+h3. Everything looks like it's working correctly but Sonar is failing to include the coverage results.
+
+Check the final lcov file (@reports/jsTestDriver.conf-coverage.dat@). Each original javascript source file should be referenced by the correct absolute path. If the path is not correct, the "Fix source code location" step failed. Double check that your directories have trailing slashes EXCEPT for the reports directory which can't have a trailing slash.
+
+h3. What's with all the jsTestDriver references?
+
+The sonar javascript plugin has been designed to work with two different javascript testing frameworks. One of them is jsTestDriver. We have to select one in order for sonar to know that there's coverage data ready. But as long as the data output is the same, Sonar doesn't really care who made the output.
+
+h3. Why is the runner written in rhino/js? Wouldn't ruby/python/etc be easier to maintain?
+
+Yes it would. The prototypes were actually written in ruby and python, but I thought it would be more valuable to get rid of the external dependency.
+
+h2. Roadmap
+
+Add unit test success/failure metrics to sonar
+Add a .bat file
+Un-remove branch coverage (blocked by lack of sonar-javascript plugin support)
+Remove "data cleaning" when writing out coverage results (will require help jscover project modification)
+
+h2. Release Notes
+
+version beta: initial release. 1/11/2013
+version 1.7.1: Removed python dependency. Simplified configuration. Made sonar-runner the default sonar method. 1/17/2013
+version 1.7.2: Now works as ruby gem. Made many properties configurable from command line. There is no longer a default sonar method. 2/12/2013
+  
+
+Please contact Daniel Freiman (https://github.com/DannyJF) or Trevor Lalish-Menagh (https://github.com/trevmex) through github with any defects or feature requests!
Index: pyenvjasmine/envjasmine/lib/jscover/envjasmine-coverage.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jscover/envjasmine-coverage.js	(revision 19)
+++ pyenvjasmine/envjasmine/lib/jscover/envjasmine-coverage.js	(revision 19)
@@ -0,0 +1,40 @@
+if (!this.EnvJasmine.coverage) {
+    this.EnvJasmine.coverage = {};
+};
+
+EnvJasmine.coverage.writeCoverageResults = function() {
+    var coverage = EnvJasmine.jscoverage;
+    try {
+        // clean coverage object
+        EnvJasmine.coverage.cleanCoverageObject(coverage);
+
+        // write file
+        fstream = new FileWriter(EnvJasmine.coverage.reportJson);
+        out = new BufferedWriter(fstream);
+        out.write(JSON.stringify(coverage));
+
+        //Close the output stream
+        out.close();
+    } catch (e) {//Catch exception if any
+        System.err.println("Error: " + e);
+    }
+};
+
+EnvJasmine.coverage.cleanCoverageObject = function(jsonObj) {
+    // recursively iterate over the output object
+    // we need to remove the toJSON function which is causing JSON.stringify to break
+    if ( typeof jsonObj == "object") {
+        $.each(jsonObj, function(k, v) {
+            if ( typeof v != "undefined") {
+                delete v.toJSON;
+                // jscover ovrrides this function, so we have to delete it bfore writing the json string.
+            }
+            EnvJasmine.coverage.cleanCoverageObject(v);
+        });
+    } // else do nothing
+};
+
+// add functions to envjasmine
+EnvJasmine.addFinallyFunction(EnvJasmine.coverage.writeCoverageResults);
+
+print("EnvJasmine-Coverage Plugin Loaded");
Index: pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-helper.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-helper.js	(revision 19)
+++ pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-helper.js	(revision 19)
@@ -0,0 +1,50 @@
+importPackage(java.io);
+importPackage(java.util);
+
+EnvJasmine.coverage.executeCommand = function(str, dir) {
+    // run shell command
+    try {
+        p = Runtime.getRuntime().exec(str, null, dir ? new File(dir) : null);
+        br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        line = null;
+        while (( line = br.readLine()) != null) {
+            print(line);
+        }
+    } catch (e) {
+        e.printStackTrace();
+    }
+}
+
+EnvJasmine.coverage.recurseTransform = function(path, transform) {
+    // recurse over every file in directory tree and do a transform on the js files
+    var file = new File(path);
+    var list = file.listFiles();
+    for ( var i = 0; i < list.length; i++) {
+        var f = list[i];
+        if (f.isDirectory()) {
+            EnvJasmine.coverage.recurseTransform(f.getAbsolutePath(), transform);
+        } else if (f.getAbsolutePath().endsWith(".js")) {
+            s = (new Scanner(f)).useDelimiter("\\Z");
+            if (s) {
+                try {
+                    content = s.next();
+                    content = transform(content);
+                    s.close();
+                    out = new PrintWriter(f);
+                    out.print(content);
+                } catch (e) {
+                    print(e);
+                } finally {
+                    try {
+                        s.close();
+                    } catch (e) {
+                    }
+                    try {
+                        out.close();
+                    } catch (e) {
+                    }
+                }
+            }
+        }
+    }
+}
Index: pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-properties.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-properties.js	(revision 19)
+++ pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-properties.js	(revision 19)
@@ -0,0 +1,41 @@
+importPackage(java.io);
+importPackage(org.mozilla.javascript);
+
+if (!this.EnvJasmine) {
+    this.EnvJasmine = {};
+}
+
+if (!EnvJasmine.coverage) {
+    EnvJasmine.coverage = {};
+}
+
+//  envjasmine directories
+EnvJasmine.coverage.rootDir = (new File("..")).getCanonicalPath(); // root directory of project or gem
+EnvJasmine.coverage.libDir = EnvJasmine.coverage.rootDir+"/lib/"; // lib directory (see example project structure)
+EnvJasmine.coverage.jscoverDir = EnvJasmine.coverage.libDir+"/jscover/"; // jscover-envjasmine plugin directory
+
+// envjasmine files
+EnvJasmine.coverage.run_script = EnvJasmine.coverage.rootDir+"/bin/run_all_tests.sh"; // location of envjasmine run script file
+EnvJasmine.coverage.jscoverJar = EnvJasmine.coverage.jscoverDir+"/JSCover-all.jar"; // location of jscover jar
+EnvJasmine.coverage.envjasmine_coverage_js = EnvJasmine.coverage.jscoverDir + "/envjasmine-coverage.js"; // location of envjasmine plugin file
+
+// project directories
+EnvJasmine.coverage.projectRoot = EnvJasmine.coverage.projectRoot || EnvJasmine.coverage.rootDir; // root directory of project
+EnvJasmine.coverage.originalDir = EnvJasmine.coverage.originalDir || EnvJasmine.coverage.projectRoot+"/samples/"; // root js directory
+EnvJasmine.coverage.instrumentedDir = EnvJasmine.coverage.instrumentedDir || EnvJasmine.coverage.projectRoot+"/instrumented/"; // directory to copy instrumented code to
+EnvJasmine.coverage.reportsDir = EnvJasmine.coverage.reportsDir || EnvJasmine.coverage.projectRoot + "/reports";  // NO TRAILING SLASH ON PURPOSE! Where to put reports.
+EnvJasmine.coverage.reportsDir = (EnvJasmine.coverage.reportsDir.lastIndexOf("/") == EnvJasmine.coverage.reportsDir.length-1 ? EnvJasmine.coverage.reportsDir.substring(0, EnvJasmine.coverage.reportsDir.length-1) : EnvJasmine.coverage.reportsDir);
+
+
+// project files
+EnvJasmine.coverage.sonar_runner_properties = EnvJasmine.coverage.projectRoot + "/sonar-project.properties"; // location of the properties files for sonar-runner
+EnvJasmine.coverage.reportJson = EnvJasmine.coverage.reportsDir+"/jscoverage.json"; // jscover output file
+EnvJasmine.coverage.reportLcov = EnvJasmine.coverage.reportsDir+"/jscover.lcov"; // lcov file converted from jscover output file
+EnvJasmine.coverage.reportSonar = EnvJasmine.coverage.reportsDir+"/jsTestDriver.conf-coverage.dat"; // final coverage data file to be picked up by sonar
+EnvJasmine.coverage.sonar_pom_xml = "sonar.js.xml"; // name of the pom file for sonar
+
+
+// variables
+EnvJasmine.coverage.noInstrument = ["ajaxDemo.js"]; // files or directories not to instrument (relative to root js directory)
+EnvJasmine.coverage.sonarMethod = EnvJasmine.coverage.sonarMethod || "none"; // "maven" or "sonar-runner" or "none"
+EnvJasmine.coverage.cleanup = EnvJasmine.coverage.cleanup || false;
Index: pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-runner.js
===================================================================
--- pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-runner.js	(revision 19)
+++ pyenvjasmine/envjasmine/lib/jscover/envjasmine-sonar-coverage-runner.js	(revision 19)
@@ -0,0 +1,142 @@
+importPackage(java.io);
+importPackage(java.lang);
+importPackage(java.util.regex);
+importPackage(org.mozilla.javascript);
+
+( function(argumentList) {
+
+    if (!this.EnvJasmine) {
+        this.EnvJasmine = {};
+    }
+
+    if (!EnvJasmine.coverage) {
+        EnvJasmine.coverage = {};
+    }
+
+    EnvJasmine.printDebug = function(str) {
+        if (EnvJasmine.debug) {
+            print(str);
+        }
+    };
+
+    var arg, nameValue;
+
+    for (var i = 0; i < argumentList.length; i++) {
+        arg = argumentList[i];
+        EnvJasmine.printDebug("ARGUMENT: " + arg);
+        if (arg.slice(0, 2) == "--") {
+            nameValue = arg.slice(2).split('=');
+
+            switch(nameValue[0]) {
+                case "projectRoot":
+                     EnvJasmine.coverage.projectRoot = nameValue[1];
+                    break;
+                case "originalDir":
+                     EnvJasmine.coverage.originalDir = nameValue[1];
+                    break;
+                case "instrumentedDir":
+                     EnvJasmine.coverage.instrumentedDir = nameValue[1];
+                    break;
+                case "reportsDir":
+                     EnvJasmine.coverage.reportsDir = nameValue[1];
+                    break;
+                case "cleanup":
+                    EnvJasmine.coverage.cleanup = true;
+                    break;
+                case "sonarMethod":
+                    EnvJasmine.coverage.sonarMethod = nameValue[1];
+                    break;
+                case "noInstrument":
+                    if (EnvJasmine.coverage.noInstrument[0] == "ajaxDemo.js") {
+                        // override default example
+                        EnvJasmine.coverage.noInstrument = [];
+                    }
+                    EnvJasmine.coverage.noInstrument.push(nameValue[1]);
+                    break;
+                case "debug":
+                    EnvJasmine.debug = true;
+                    break;
+            }
+        }
+    }
+
+    properties_file = "../lib/jscover/envjasmine-sonar-coverage-properties.js";
+
+    print("Loading Helper Files");
+    fileIn = new FileReader(properties_file);
+    Context.getCurrentContext().evaluateReader(this, fileIn, "envjasmine-sonar-coverage-properties.js", 0, null);
+    fileIn.close();
+
+    fileIn = new FileReader(EnvJasmine.coverage.jscoverDir + "envjasmine-sonar-coverage-helper.js");
+    Context.getCurrentContext().evaluateReader(this, fileIn, "envjasmine-sonar-coverage-helper.js", 0, null);
+    fileIn.close();
+
+    // create directories that will be used
+    print("Creating temp directories");
+    EnvJasmine.printDebug("INSTRUMENTED DIRECTORY: " + EnvJasmine.coverage.instrumentedDir);
+    (new File(EnvJasmine.coverage.instrumentedDir)).mkdir();
+    (new File(EnvJasmine.coverage.reportsDir)).mkdir();
+
+
+    // instrument the javascript code
+    // no-instrument documentation:  http://tntim96.github.com/JSCover/manual/manual.xml#fileMode
+    print("Instrumenting")
+    no_i = ""
+    for (var i = 0; i < EnvJasmine.coverage.noInstrument.length; i++) {
+        no_i += (" --no-instrument=" + EnvJasmine.coverage.noInstrument[i]);
+    }
+    cmd = "java -jar " + EnvJasmine.coverage.jscoverJar + " -fs --branch " + no_i + " " + EnvJasmine.coverage.originalDir + " " + EnvJasmine.coverage.instrumentedDir;
+    EnvJasmine.printDebug("EXECUTING: " + cmd)
+    EnvJasmine.coverage.executeCommand(cmd);
+
+    // some evil code is neccessary to get this to work :(
+    // hopefully we'll get rid of this soon jscover can be changed a little
+    print("Exposing coverage hooks to EnvJasmine");
+    p = Pattern.compile("this\\._\\$jscoverage|(?<!\\.)_\\$jscoverage", Pattern.MULTILINE);
+    EnvJasmine.coverage.recurseTransform(EnvJasmine.coverage.instrumentedDir, function(content) {
+        return p.matcher(content).replaceAll("EnvJasmine.jscoverage");
+    });
+
+    // run tests with instrumented code
+    print("Running instrumented tests");
+    cmd = EnvJasmine.coverage.run_script + " --jsDir=" + EnvJasmine.coverage.instrumentedDir + " --plugin=" + EnvJasmine.coverage.envjasmine_coverage_js + " " + argumentList.join(" ");
+    EnvJasmine.printDebug("EXECUTING: " + cmd)
+    EnvJasmine.coverage.executeCommand(cmd);
+
+    // convert json file into lcov file which sonar can read
+    print("Converting Output to lcov")
+    cmd = "java -cp " + EnvJasmine.coverage.jscoverJar + " jscover.report.Main --format=LCOV " + EnvJasmine.coverage.reportsDir + " " + EnvJasmine.coverage.reportsDir
+    EnvJasmine.printDebug("EXECUTING: " + cmd)
+    EnvJasmine.coverage.executeCommand(cmd);
+
+    // The lcov file includes branch coverage but sonar can't read it, so remove it
+    print("converting lcov to be compatible with sonar 2");
+    file = new File(EnvJasmine.coverage.reportLcov);
+    content = (new Scanner(file)).useDelimiter("\\Z").next();
+    pattern = new Pattern.compile(EnvJasmine.coverage.reportsDir);
+    content = pattern.matcher(content).replaceAll(EnvJasmine.coverage.originalDir);
+    pattern = Pattern.compile("^BRDA:.*$", Pattern.MULTILINE);
+    content = pattern.matcher(content).replaceAll("");
+    out = new PrintWriter(EnvJasmine.coverage.reportSonar);
+    out.print(content);
+    out.close();
+
+    print("Coverage file ready to be picked up by sonar");
+
+    // run sonar, it will pick up the code coverage file
+    if (EnvJasmine.coverage.sonarMethod === "sonar-runner") {
+        print("Running Sonar");
+        EnvJasmine.coverage.executeCommand("sonar-runner -Dproject.settings="+EnvJasmine.coverage.sonar_runner_properties, EnvJasmine.coverage.rootDir); // sonar runner
+    } else if (EnvJasmine.coverage.sonarMethod === "maven") {
+        print("Running Sonar");
+        EnvJasmine.coverage.executeCommand("mvn sonar:sonar -f "+EnvJasmine.coverage.sonar_pom_xml, EnvJasmine.coverage.rootDir); // maven
+    }
+
+    if (EnvJasmine.coverage.cleanup) {
+        print("Cleanup");
+        EnvJasmine.coverage.executeCommand("rm -rf " + EnvJasmine.coverage.reportsDir);
+        EnvJasmine.coverage.executeCommand("rm -rf " + EnvJasmine.coverage.instrumentedDir);
+    }
+
+    print("envjasmine-sonar-coverage-runner complete")
+}(arguments));
Index: pyenvjasmine/envjasmine/samples/ajaxDemo.js
===================================================================
--- pyenvjasmine/envjasmine/samples/ajaxDemo.js	(revision 0)
+++ pyenvjasmine/envjasmine/samples/ajaxDemo.js	(revision 19)
@@ -1,22 +1,21 @@
 // This is the contents of ajaxDemo.js, the file to test.
-var TwitterWidget = {
-    makeRequest: function() {
-        var self = this;
-        
-        $.ajax({
-            method: "GET",
-            url: "http://api.twitter.com/1/statuses/show/trevmex.json",
-            datatype: "json",
-            success: function (data) {
-                self.addDataToDOM(data);
-            }
-        });
-    },
 
-    addDataToDOM: function(data) {
-        // does something
-        // We will mock this behavior with a spy.
-        
-        return data;
-    }
+if (typeof(NS) === 'undefined' || !NS) {
+    var NS = {};
+}
+
+NS.greeter = function (name) {
+    return 'Hello ' + name + '!';
 };
+
+NS.greetUser = function (id) {
+    return $.ajax({
+        data: id,
+        url: 'name.html',
+        success: function (data) {
+            NS.greeter(data.name);
+        }
+    });
+};
+
+
Index: pyenvjasmine/envjasmine/samples/callbackDemo.js
===================================================================
--- pyenvjasmine/envjasmine/samples/callbackDemo.js	(revision 19)
+++ pyenvjasmine/envjasmine/samples/callbackDemo.js	(revision 19)
@@ -0,0 +1,13 @@
+// This is a demo of using jasmine in a callback!
+
+if (typeof(NS) === 'undefined' || !NS) {
+    var NS = {};
+}
+
+NS.runIfTrevor = function (name, callback) {
+    if (name === 'Trevor') {
+        callback(name);
+    } else {
+        throw 'You aren\'t Trevor!';
+    }
+};
Index: pyenvjasmine/envjasmine/samples/demo.js
===================================================================
--- pyenvjasmine/envjasmine/samples/demo.js	(revision 0)
+++ pyenvjasmine/envjasmine/samples/demo.js	(revision 19)
@@ -1,9 +1,9 @@
 // Sample demo
 
-if (!this.Demo) {
-    Demo = {};
+if (typeof(NS) === 'undefined' || !NS) {
+    var NS = {};
 }
 
-Demo.checkBirthdate = function (user) {
-    return !user.birthdate ? false : true;
-}
+NS.greeter = function (name) {
+    return 'Hello ' + name + '!';
+};
Index: pyenvjasmine/envjasmine/samples/notCoveredDemo.js
===================================================================
--- pyenvjasmine/envjasmine/samples/notCoveredDemo.js	(revision 19)
+++ pyenvjasmine/envjasmine/samples/notCoveredDemo.js	(revision 19)
@@ -0,0 +1,9 @@
+// Sample demo
+
+if (typeof(NS) === 'undefined' || !NS) {
+    var NS = {};
+}
+
+NS.greeter = function (name) {
+    return 'Hello ' + name + '! Cover Me!';
+};
Index: pyenvjasmine/envjasmine/sonar-project.properties
===================================================================
--- pyenvjasmine/envjasmine/sonar-project.properties	(revision 19)
+++ pyenvjasmine/envjasmine/sonar-project.properties	(revision 19)
@@ -0,0 +1,24 @@
+# required metadata
+sonar.projectKey=net.project.my:myproject
+sonar.projectName=My Project
+sonar.projectVersion=1.0.0.0
+
+# optional description
+sonar.projectDescription=Example Project for EnvJasmine, JSCover, Sonar integration
+
+# path to source directories (required)
+sonar.sources=samples
+
+# optional comma-separated list of paths to libraries. Only path to JAR file and path to directory of classes are supported.
+#sonar.libraries=path/to/library/*.jar,path/to/classes/dir,path/to/specific/library/myLibrary.jar,parent/*/*.jar
+
+# The value of the property must be the key of the language.
+sonar.language=js
+
+# Additional parameters
+sonar.dynamicAnalysis=true
+sonar.branch=javascript
+sonar.exclusions=ajaxDemo.js
+sonar.javascript.testframework=jstest
+sonar.javascript.jstest.reportsfolder=reports
+sonar.surefire.reportsPath=reports
Index: pyenvjasmine/envjasmine/sonar.js.xml
===================================================================
--- pyenvjasmine/envjasmine/sonar.js.xml	(revision 19)
+++ pyenvjasmine/envjasmine/sonar.js.xml	(revision 19)
@@ -0,0 +1,21 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>net.project.my</groupId>
+  <artifactId>myproject</artifactId>
+  <version>1.0.0.0</version>
+  <name>My Project</name>
+  <description>
+    Example Project for EnvJasmine, JSCover, Sonar integration
+  </description>
+  <build>
+    <finalName>my-project</finalName>
+    <sourceDirectory>samples</sourceDirectory>
+  </build>
+  <properties>
+    <sonar.language>js</sonar.language>
+    <sonar.branch>javascript</sonar.branch>
+    <sonar.exclusions>ajaxDemo.js</sonar.exclusions>
+    <sonar.javascript.testframework>jstestdriver</sonar.javascript.testframework>
+    <sonar.javascript.jstestdriver.reportsfolder>reports</sonar.javascript.jstestdriver.reportsfolder>
+  </properties>
+</project>
Index: pyenvjasmine/envjasmine/specs/ajaxDemo.spec.js
===================================================================
--- pyenvjasmine/envjasmine/specs/ajaxDemo.spec.js	(revision 0)
+++ pyenvjasmine/envjasmine/specs/ajaxDemo.spec.js	(revision 19)
@@ -1,17 +1,13 @@
-// Load the file to test here.
-//
-// Example:
 EnvJasmine.load(EnvJasmine.jsDir + "ajaxDemo.js");
 
-// This is the test code.
-describe("AjaxDemo", function () {
-    it("calls the addDataToDOM function on success", function () {
-        TwitterWidget.makeRequest(); // Make the AJAX call
-
-        spyOn(TwitterWidget, "addDataToDOM"); // Add a spy to the callback
-
-        mostRecentAjaxRequest().response({status: 200, responseText: "foo"}); // Mock the response
-
-        expect(TwitterWidget.addDataToDOM).toHaveBeenCalledWith("foo");
+describe('greetUser', function () {
+    it('calls greeter on success', function () {
+        NS.greetUser(1);
+        spyOn(NS, 'greeter');
+        mostRecentAjaxRequest().response({
+            status: 200,
+            responseText: {"name":"Trevor"}
+        });
+        expect(NS.greeter).toHaveBeenCalledWith('Trevor');
     });
 });
Index: pyenvjasmine/envjasmine/specs/callbackDemo.spec.js
===================================================================
--- pyenvjasmine/envjasmine/specs/callbackDemo.spec.js	(revision 19)
+++ pyenvjasmine/envjasmine/specs/callbackDemo.spec.js	(revision 19)
@@ -0,0 +1,20 @@
+EnvJasmine.load(EnvJasmine.jsDir + "callbackDemo.js");
+
+describe('runIfTrevor', function () {
+    it('calls callback if Trevor', function () {
+        var callback = jasmine.createSpy();
+
+        NS.runIfTrevor('Trevor', callback);
+        expect(callback).toHaveBeenCalledWith('Trevor');
+    });
+
+    it('throws an error and does not call callback if not Trevor', function () {
+        var callback = jasmine.createSpy();
+
+        expect(function () {
+            NS.runIfTrevor('Orson', callback);
+        }).toThrow('You aren\'t Trevor!');
+
+        expect(callback).not.toHaveBeenCalled();
+    });
+});
Index: pyenvjasmine/envjasmine/specs/demo.spec.js
===================================================================
--- pyenvjasmine/envjasmine/specs/demo.spec.js	(revision 0)
+++ pyenvjasmine/envjasmine/specs/demo.spec.js	(revision 19)
@@ -1,25 +1,7 @@
-// Load the file to test here.
-//
-// Example:
 EnvJasmine.load(EnvJasmine.jsDir + "demo.js");
 
-// Load mocks for this spec
-EnvJasmine.load(EnvJasmine.mocksDir + "demo.mock.js");
-
-describe("Demo", function () {
-    it("asserts that one plus one equals two", function () {
-        expect(1 + 1 == 2).toEqual(true);
-    });
-
-    it("asserts that 1 + 1 does not equal 3", function () {
-        expect(1 + 1 == 3).toEqual(false);
-    });
-
-    it("asserts that a user has a birthdate", function () {
-        expect(Demo.checkBirthdate(demoUser)).toEqual(true);
-    });
-
-    it("asserts that an ill-formed user has no birthdate", function () {
-        expect(Demo.checkBirthdate(badUser)).toEqual(false);
+describe('greeter', function () {
+    it('greets me', function () {
+        expect(NS.greeter('Trevor')).toEqual('Hello Trevor!');
     });
 });
Index: pyenvjasmine/envjasmine/specs/notCoveredDemo.spec.js
===================================================================
--- pyenvjasmine/envjasmine/specs/notCoveredDemo.spec.js	(revision 19)
+++ pyenvjasmine/envjasmine/specs/notCoveredDemo.spec.js	(revision 19)
@@ -0,0 +1,1 @@
+EnvJasmine.load(EnvJasmine.jsDir + "notCoveredDemo.js");
