Skip to content
This repository
Browse code

Add jasmine for JS testing

  • Loading branch information...
commit 2fad9b1c1a324ca8b1537b39c694673d98b0d868 1 parent 5ee02e5
Matt Vague authored
1  Gemfile
@@ -38,4 +38,5 @@ group :test do
38 38 gem 'launchy'
39 39 gem 'jslint_on_rails', '~> 1.0.6'
40 40 gem 'guard-rspec'
  41 + gem 'jasmine'
41 42 end
11 Rakefile
... ... @@ -1,8 +1,8 @@
1 1 require "bundler"
  2 +require 'rake'
2 3 Bundler.setup
3 4 Bundler::GemHelper.install_tasks
4 5
5   -require 'rake'
6 6
7 7 def cmd(command)
8 8 puts command
@@ -16,3 +16,12 @@ FileList['tasks/**/*.rake'].each { |task| import task }
16 16
17 17 # Run the specs & cukes
18 18 task :default => :test
  19 +
  20 +begin
  21 + require 'jasmine'
  22 + load 'jasmine/tasks/jasmine.rake'
  23 +rescue LoadError
  24 + task :jasmine do
  25 + abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
  26 + end
  27 +end
3  spec/javascripts/helpers/SpecHelper.js
... ... @@ -0,0 +1,3 @@
  1 +beforeEach(function() {
  2 + window.inject = $.jasmine.inject;
  3 +});
108 spec/javascripts/helpers/vendor/jasmine-fixture-0.0.5.js
... ... @@ -0,0 +1,108 @@
  1 +/* jasmine-fixture Makes injecting HTML snippets into the DOM easy & clean!
  2 + * site: https://github.com/searls/jasmine-fixture */
  3 +(function($){
  4 + var originalJasmineFixture = window.jasmineFixture,
  5 + defaultConfiguration = {
  6 + el:'div',
  7 + cssClass:'',
  8 + id:'',
  9 + text: '',
  10 + html: '',
  11 + defaultAttribute: 'class',
  12 + attrs: {}
  13 + };
  14 +
  15 + window.jasmineFixture = function($) {
  16 + var isReady = false,
  17 + rootId = 'specContainer',
  18 + defaults = $.extend({},defaultConfiguration);
  19 +
  20 + $.jasmine = {
  21 + inject: function(arg,context) {
  22 + if(isReady !== true) init();
  23 + var parent = context ? context : $('#'+rootId),
  24 + $toInject;
  25 + if(itLooksLikeHtml(arg)) {
  26 + $toInject = $(arg);
  27 + } else {
  28 + var config = $.extend({},defaults,arg,{ userString: arg });
  29 + $toInject = $('<'+config.el+'></'+config.el+'>');
  30 + applyAttributes($toInject,config);
  31 + injectContents($toInject,config);
  32 + }
  33 + return $toInject.appendTo(parent);
  34 + },
  35 + configure: function(config) {
  36 + $.extend(defaults,config);
  37 + },
  38 + restoreDefaults: function(){
  39 + defaults = $.extend({},defaultConfiguration);
  40 + },
  41 + noConflict: function() {
  42 + window.jasmineFixture = originalJasmineFixture;
  43 + return this;
  44 + }
  45 + };
  46 +
  47 + $.fn.inject = function(html){
  48 + return $.jasmine.inject(html,$(this));
  49 + };
  50 +
  51 + var applyAttributes = function($html,config) {
  52 + var attrs = $.extend({},{
  53 + id: config.id,
  54 + 'class': config['class'] || config.cssClass
  55 + }, config.attrs);
  56 + if(isString(config.userString)) {
  57 + attrs[config.defaultAttribute] = config.userString;
  58 + }
  59 + for(var key in attrs) {
  60 + if(attrs[key]) {
  61 + $html.attr(key,attrs[key]);
  62 + }
  63 + }
  64 + };
  65 +
  66 + var injectContents = function($el,config){
  67 + if(config.text && config.html) {
  68 + throw "Error: because they conflict, you may only configure inject() to set `html` or `text`, not both! \n\nHTML was: "+config.html+" \n\n Text was: "+config.text
  69 + } else if(config.text) {
  70 + $el.text(config.text);
  71 + } else if(config.html) {
  72 + $el.html(config.html);
  73 + }
  74 + }
  75 +
  76 + var itLooksLikeHtml = function(arg) {
  77 + return isString(arg) && arg.indexOf('<') !== -1
  78 + };
  79 +
  80 + var isString = function(arg) {
  81 + return arg && arg.constructor === String;
  82 + };
  83 +
  84 + var init = function() {
  85 + $('body').append('<div id="'+rootId+'"></div>');
  86 + isReady = true;
  87 + };
  88 +
  89 + var tidyUp = function() {
  90 + $('#'+rootId).remove();
  91 + isReady = false;
  92 + };
  93 +
  94 + $(function(jQuery){
  95 + init();
  96 + });
  97 + afterEach(function(){
  98 + tidyUp();
  99 + });
  100 +
  101 + return $.jasmine;
  102 + };
  103 +
  104 + if(jQuery) {
  105 + var jasmineFixture = window.jasmineFixture(jQuery);
  106 + window.inject = window.inject || jasmineFixture.inject;
  107 + }
  108 +})(jQuery);
288 spec/javascripts/helpers/vendor/jasmine-jquery.js
... ... @@ -0,0 +1,288 @@
  1 +var readFixtures = function() {
  2 + return jasmine.getFixtures().proxyCallTo_('read', arguments);
  3 +};
  4 +
  5 +var preloadFixtures = function() {
  6 + jasmine.getFixtures().proxyCallTo_('preload', arguments);
  7 +};
  8 +
  9 +var loadFixtures = function() {
  10 + jasmine.getFixtures().proxyCallTo_('load', arguments);
  11 +};
  12 +
  13 +var setFixtures = function(html) {
  14 + jasmine.getFixtures().set(html);
  15 +};
  16 +
  17 +var sandbox = function(attributes) {
  18 + return jasmine.getFixtures().sandbox(attributes);
  19 +};
  20 +
  21 +var spyOnEvent = function(selector, eventName) {
  22 + jasmine.JQuery.events.spyOn(selector, eventName);
  23 +}
  24 +
  25 +jasmine.getFixtures = function() {
  26 + return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
  27 +};
  28 +
  29 +jasmine.Fixtures = function() {
  30 + this.containerId = 'jasmine-fixtures';
  31 + this.fixturesCache_ = {};
  32 + this.fixturesPath = 'spec/javascripts/fixtures';
  33 +};
  34 +
  35 +jasmine.Fixtures.prototype.set = function(html) {
  36 + this.cleanUp();
  37 + this.createContainer_(html);
  38 +};
  39 +
  40 +jasmine.Fixtures.prototype.preload = function() {
  41 + this.read.apply(this, arguments);
  42 +};
  43 +
  44 +jasmine.Fixtures.prototype.load = function() {
  45 + this.cleanUp();
  46 + this.createContainer_(this.read.apply(this, arguments));
  47 +};
  48 +
  49 +jasmine.Fixtures.prototype.read = function() {
  50 + var htmlChunks = [];
  51 +
  52 + var fixtureUrls = arguments;
  53 + for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
  54 + htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
  55 + }
  56 +
  57 + return htmlChunks.join('');
  58 +};
  59 +
  60 +jasmine.Fixtures.prototype.clearCache = function() {
  61 + this.fixturesCache_ = {};
  62 +};
  63 +
  64 +jasmine.Fixtures.prototype.cleanUp = function() {
  65 + jQuery('#' + this.containerId).remove();
  66 +};
  67 +
  68 +jasmine.Fixtures.prototype.sandbox = function(attributes) {
  69 + var attributesToSet = attributes || {};
  70 + return jQuery('<div id="sandbox" />').attr(attributesToSet);
  71 +};
  72 +
  73 +jasmine.Fixtures.prototype.createContainer_ = function(html) {
  74 + var container;
  75 + if(html instanceof jQuery) {
  76 + container = jQuery('<div id="' + this.containerId + '" />');
  77 + container.html(html);
  78 + } else {
  79 + container = '<div id="' + this.containerId + '">' + html + '</div>'
  80 + }
  81 + jQuery('body').append(container);
  82 +};
  83 +
  84 +jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
  85 + if (typeof this.fixturesCache_[url] == 'undefined') {
  86 + this.loadFixtureIntoCache_(url);
  87 + }
  88 + return this.fixturesCache_[url];
  89 +};
  90 +
  91 +jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
  92 + var self = this;
  93 + var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
  94 + jQuery.ajax({
  95 + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
  96 + cache: false,
  97 + dataType: 'html',
  98 + url: url,
  99 + success: function(data) {
  100 + self.fixturesCache_[relativeUrl] = data;
  101 + },
  102 + error: function(jqXHR, status, errorThrown) {
  103 + throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
  104 + }
  105 + });
  106 +};
  107 +
  108 +jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
  109 + return this[methodName].apply(this, passedArguments);
  110 +};
  111 +
  112 +
  113 +jasmine.JQuery = function() {};
  114 +
  115 +jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
  116 + return jQuery('<div/>').append(html).html();
  117 +};
  118 +
  119 +jasmine.JQuery.elementToString = function(element) {
  120 + return jQuery('<div />').append(element.clone()).html();
  121 +};
  122 +
  123 +jasmine.JQuery.matchersClass = {};
  124 +
  125 +(function(namespace) {
  126 + var data = {
  127 + spiedEvents: {},
  128 + handlers: []
  129 + };
  130 +
  131 + namespace.events = {
  132 + spyOn: function(selector, eventName) {
  133 + var handler = function(e) {
  134 + data.spiedEvents[[selector, eventName]] = e;
  135 + };
  136 + jQuery(selector).bind(eventName, handler);
  137 + data.handlers.push(handler);
  138 + },
  139 +
  140 + wasTriggered: function(selector, eventName) {
  141 + return !!(data.spiedEvents[[selector, eventName]]);
  142 + },
  143 +
  144 + cleanUp: function() {
  145 + data.spiedEvents = {};
  146 + data.handlers = [];
  147 + }
  148 + }
  149 +})(jasmine.JQuery);
  150 +
  151 +(function(){
  152 + var jQueryMatchers = {
  153 + toHaveClass: function(className) {
  154 + return this.actual.hasClass(className);
  155 + },
  156 +
  157 + toBeVisible: function() {
  158 + return this.actual.is(':visible');
  159 + },
  160 +
  161 + toBeHidden: function() {
  162 + return this.actual.is(':hidden');
  163 + },
  164 +
  165 + toBeSelected: function() {
  166 + return this.actual.is(':selected');
  167 + },
  168 +
  169 + toBeChecked: function() {
  170 + return this.actual.is(':checked');
  171 + },
  172 +
  173 + toBeEmpty: function() {
  174 + return this.actual.is(':empty');
  175 + },
  176 +
  177 + toExist: function() {
  178 + return this.actual.size() > 0;
  179 + },
  180 +
  181 + toHaveAttr: function(attributeName, expectedAttributeValue) {
  182 + return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
  183 + },
  184 +
  185 + toHaveId: function(id) {
  186 + return this.actual.attr('id') == id;
  187 + },
  188 +
  189 + toHaveHtml: function(html) {
  190 + return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
  191 + },
  192 +
  193 + toHaveText: function(text) {
  194 + if (text && jQuery.isFunction(text.test)) {
  195 + return text.test(this.actual.text());
  196 + } else {
  197 + return this.actual.text() == text;
  198 + }
  199 + },
  200 +
  201 + toHaveValue: function(value) {
  202 + return this.actual.val() == value;
  203 + },
  204 +
  205 + toHaveData: function(key, expectedValue) {
  206 + return hasProperty(this.actual.data(key), expectedValue);
  207 + },
  208 +
  209 + toBe: function(selector) {
  210 + return this.actual.is(selector);
  211 + },
  212 +
  213 + toContain: function(selector) {
  214 + return this.actual.find(selector).size() > 0;
  215 + },
  216 +
  217 + toBeDisabled: function(selector){
  218 + return this.actual.is(':disabled');
  219 + },
  220 +
  221 + // tests the existence of a specific event binding
  222 + toHandle: function(eventName) {
  223 + var events = this.actual.data("events");
  224 + return events && events[eventName].length > 0;
  225 + },
  226 +
  227 + // tests the existence of a specific event binding + handler
  228 + toHandleWith: function(eventName, eventHandler) {
  229 + var stack = this.actual.data("events")[eventName];
  230 + var i;
  231 + for (i = 0; i < stack.length; i++) {
  232 + if (stack[i].handler == eventHandler) {
  233 + return true;
  234 + }
  235 + }
  236 + return false;
  237 + }
  238 + };
  239 +
  240 + var hasProperty = function(actualValue, expectedValue) {
  241 + if (expectedValue === undefined) {
  242 + return actualValue !== undefined;
  243 + }
  244 + return actualValue == expectedValue;
  245 + };
  246 +
  247 + var bindMatcher = function(methodName) {
  248 + var builtInMatcher = jasmine.Matchers.prototype[methodName];
  249 +
  250 + jasmine.JQuery.matchersClass[methodName] = function() {
  251 + if (this.actual instanceof jQuery) {
  252 + var result = jQueryMatchers[methodName].apply(this, arguments);
  253 + this.actual = jasmine.JQuery.elementToString(this.actual);
  254 + return result;
  255 + }
  256 +
  257 + if (builtInMatcher) {
  258 + return builtInMatcher.apply(this, arguments);
  259 + }
  260 +
  261 + return false;
  262 + };
  263 + };
  264 +
  265 + for(var methodName in jQueryMatchers) {
  266 + bindMatcher(methodName);
  267 + }
  268 +})();
  269 +
  270 +beforeEach(function() {
  271 + this.addMatchers(jasmine.JQuery.matchersClass);
  272 + this.addMatchers({
  273 + toHaveBeenTriggeredOn: function(selector) {
  274 + this.message = function() {
  275 + return [
  276 + "Expected event " + this.actual + " to have been triggered on" + selector,
  277 + "Expected event " + this.actual + " not to have been triggered on" + selector
  278 + ];
  279 + };
  280 + return jasmine.JQuery.events.wasTriggered(selector, this.actual);
  281 + }
  282 + })
  283 +});
  284 +
  285 +afterEach(function() {
  286 + jasmine.getFixtures().cleanUp();
  287 + jasmine.JQuery.events.cleanUp();
  288 +});
2,915 spec/javascripts/helpers/vendor/sinon-1.2.0.js
... ... @@ -0,0 +1,2915 @@
  1 +/**
  2 + * Sinon.JS 1.2.0, 2011/09/27
  3 + *
  4 + * @author Christian Johansen (christian@cjohansen.no)
  5 + *
  6 + * (The BSD License)
  7 + *
  8 + * Copyright (c) 2010-2011, Christian Johansen, christian@cjohansen.no
  9 + * All rights reserved.
  10 + *
  11 + * Redistribution and use in source and binary forms, with or without modification,
  12 + * are permitted provided that the following conditions are met:
  13 + *
  14 + * * Redistributions of source code must retain the above copyright notice,
  15 + * this list of conditions and the following disclaimer.
  16 + * * Redistributions in binary form must reproduce the above copyright notice,
  17 + * this list of conditions and the following disclaimer in the documentation
  18 + * and/or other materials provided with the distribution.
  19 + * * Neither the name of Christian Johansen nor the names of his contributors
  20 + * may be used to endorse or promote products derived from this software
  21 + * without specific prior written permission.
  22 + *
  23 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  24 + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  25 + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  26 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  27 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  29 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  30 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  31 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  32 + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33 + */
  34 +
  35 +"use strict";
  36 +/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/
  37 +/*global module, require, __dirname, document*/
  38 +/**
  39 + * Sinon core utilities. For internal use only.
  40 + *
  41 + * @author Christian Johansen (christian@cjohansen.no)
  42 + * @license BSD
  43 + *
  44 + * Copyright (c) 2010-2011 Christian Johansen
  45 + */
  46 +
  47 +var sinon = (function () {
  48 + var div = typeof document != "undefined" && document.createElement("div");
  49 +
  50 + function isNode(obj) {
  51 + var success = false;
  52 +
  53 + try {
  54 + obj.appendChild(div);
  55 + success = div.parentNode == obj;
  56 + } catch (e) {
  57 + return false;
  58 + } finally {
  59 + try {
  60 + obj.removeChild(div);
  61 + } catch (e) {}
  62 + }
  63 +
  64 + return success;
  65 + }
  66 +
  67 + function isElement(obj) {
  68 + return div && obj && obj.nodeType === 1 && isNode(obj);
  69 + }
  70 +
  71 + return {
  72 + wrapMethod: function wrapMethod(object, property, method) {
  73 + if (!object) {
  74 + throw new TypeError("Should wrap property of object");
  75 + }
  76 +
  77 + if (typeof method != "function") {
  78 + throw new TypeError("Method wrapper should be function");
  79 + }
  80 +
  81 + var wrappedMethod = object[property];
  82 + var type = typeof wrappedMethod;
  83 +
  84 + if (type != "function") {
  85 + throw new TypeError("Attempted to wrap " + type + " property " + property +
  86 + " as function");
  87 + }
  88 +
  89 + if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
  90 + throw new TypeError("Attempted to wrap " + property + " which is already wrapped");
  91 + }
  92 +
  93 + if (wrappedMethod.calledBefore) {
  94 + var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
  95 + throw new TypeError("Attempted to wrap " + property + " which is already " + verb);
  96 + }
  97 +
  98 + var owned = object.hasOwnProperty(property);
  99 + object[property] = method;
  100 + method.displayName = property;
  101 +
  102 + method.restore = function () {
  103 + if(owned) {
  104 + object[property] = wrappedMethod;
  105 + } else {
  106 + delete object[property];
  107 + }
  108 + };
  109 +
  110 + method.restore.sinon = true;
  111 +
  112 + return method;
  113 + },
  114 +
  115 + extend: function extend(target) {
  116 + for (var i = 1, l = arguments.length; i < l; i += 1) {
  117 + for (var prop in arguments[i]) {
  118 + if (arguments[i].hasOwnProperty(prop)) {
  119 + target[prop] = arguments[i][prop];
  120 + }
  121 +
  122 + // DONT ENUM bug, only care about toString
  123 + if (arguments[i].hasOwnProperty("toString") &&
  124 + arguments[i].toString != target.toString) {
  125 + target.toString = arguments[i].toString;
  126 + }
  127 + }
  128 + }
  129 +
  130 + return target;
  131 + },
  132 +
  133 + create: function create(proto) {
  134 + var F = function () {};
  135 + F.prototype = proto;
  136 + return new F();
  137 + },
  138 +
  139 + deepEqual: function deepEqual(a, b) {
  140 + if (typeof a != "object" || typeof b != "object") {
  141 + return a === b;
  142 + }
  143 +
  144 + if (isElement(a) || isElement(b)) {
  145 + return a === b;
  146 + }
  147 +
  148 + if (a === b) {
  149 + return true;
  150 + }
  151 +
  152 + if (Object.prototype.toString.call(a) == "[object Array]") {
  153 + if (a.length !== b.length) {
  154 + return false;
  155 + }
  156 +
  157 + for (var i = 0, l = a.length; i < l; i += 1) {
  158 + if (!deepEqual(a[i], b[i])) {
  159 + return false;
  160 + }
  161 + }
  162 +
  163 + return true;
  164 + }
  165 +
  166 + var prop, aLength = 0, bLength = 0;
  167 +
  168 + for (prop in a) {
  169 + aLength += 1;
  170 +
  171 + if (!deepEqual(a[prop], b[prop])) {
  172 + return false;
  173 + }
  174 + }
  175 +
  176 + for (prop in b) {
  177 + bLength += 1;
  178 + }
  179 +
  180 + if (aLength != bLength) {
  181 + return false;
  182 + }
  183 +
  184 + return true;
  185 + },
  186 +
  187 + functionName: function functionName(func) {
  188 + var name = func.displayName || func.name;
  189 +
  190 + // Use function decomposition as a last resort to get function
  191 + // name. Does not rely on function decomposition to work - if it
  192 + // doesn't debugging will be slightly less informative
  193 + // (i.e. toString will say 'spy' rather than 'myFunc').
  194 + if (!name) {
  195 + var matches = func.toString().match(/function ([^\s\(]+)/);
  196 + name = matches && matches[1];
  197 + }
  198 +
  199 + return name;
  200 + },
  201 +
  202 + functionToString: function toString() {
  203 + if (this.getCall && this.callCount) {
  204 + var thisValue, prop, i = this.callCount;
  205 +
  206 + while (i--) {
  207 + thisValue = this.getCall(i).thisValue;
  208 +
  209 + for (prop in thisValue) {
  210 + if (thisValue[prop] === this) {
  211 + return prop;
  212 + }
  213 + }
  214 + }
  215 + }
  216 +
  217 + return this.displayName || "sinon fake";
  218 + },
  219 +
  220 + getConfig: function (custom) {
  221 + var config = {};
  222 + custom = custom || {};
  223 + var defaults = sinon.defaultConfig;
  224 +
  225 + for (var prop in defaults) {
  226 + if (defaults.hasOwnProperty(prop)) {
  227 + config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
  228 + }
  229 + }
  230 +
  231 + return config;
  232 + },
  233 +
  234 + format: function (val) {
  235 + return "" + val;
  236 + },
  237 +
  238 + defaultConfig: {
  239 + injectIntoThis: true,
  240 + injectInto: null,
  241 + properties: ["spy", "stub", "mock", "clock", "server", "requests"],
  242 + useFakeTimers: true,
  243 + useFakeServer: true
  244 + },
  245 +
  246 + timesInWords: function timesInWords(count) {
  247 + return count == 1 && "once" ||
  248 + count == 2 && "twice" ||
  249 + count == 3 && "thrice" ||
  250 + (count || 0) + " times";
  251 + },
  252 +
  253 + calledInOrder: function (spies) {
  254 + for (var i = 1, l = spies.length; i < l; i++) {
  255 + if (!spies[i - 1].calledBefore(spies[i])) {
  256 + return false;
  257 + }
  258 + }
  259 +
  260 + return true;
  261 + },
  262 +
  263 + orderByFirstCall: function (spies) {
  264 + return spies.sort(function (a, b) {
  265 + // uuid, won't ever be equal
  266 + return a.getCall(0).callId < b.getCall(0).callId ? -1 : 1;
  267 + });
  268 + }
  269 + };
  270 +}());
  271 +
  272 +if (typeof module == "object" && typeof require == "function") {
  273 + module.exports = sinon;
  274 + module.exports.spy = require("./sinon/spy");
  275 + module.exports.stub = require("./sinon/stub");
  276 + module.exports.mock = require("./sinon/mock");
  277 + module.exports.collection = require("./sinon/collection");
  278 + module.exports.assert = require("./sinon/assert");
  279 + module.exports.sandbox = require("./sinon/sandbox");
  280 + module.exports.test = require("./sinon/test");
  281 + module.exports.testCase = require("./sinon/test_case");
  282 + module.exports.assert = require("./sinon/assert");
  283 +}
  284 +
  285 +/* @depend ../sinon.js */
  286 +/*jslint eqeqeq: false, onevar: false, plusplus: false*/
  287 +/*global module, require, sinon*/
  288 +/**
  289 + * Spy functions
  290 + *
  291 + * @author Christian Johansen (christian@cjohansen.no)
  292 + * @license BSD
  293 + *
  294 + * Copyright (c) 2010-2011 Christian Johansen
  295 + */
  296 +
  297 +(function (sinon) {
  298 + var commonJSModule = typeof module == "object" && typeof require == "function";
  299 + var spyCall;
  300 + var callId = 0;
  301 + var push = [].push;
  302 +
  303 + if (!sinon && commonJSModule) {
  304 + sinon = require("../sinon");
  305 + }
  306 +
  307 + if (!sinon) {
  308 + return;
  309 + }
  310 +
  311 + function spy(object, property) {
  312 + if (!property && typeof object == "function") {
  313 + return spy.create(object);
  314 + }
  315 +
  316 + if (!object || !property) {
  317 + return spy.create(function () {});
  318 + }
  319 +
  320 + var method = object[property];
  321 + return sinon.wrapMethod(object, property, spy.create(method));
  322 + }
  323 +
  324 + sinon.extend(spy, (function () {
  325 + var slice = Array.prototype.slice;
  326 +
  327 + function delegateToCalls(api, method, matchAny, actual, notCalled) {
  328 + api[method] = function () {
  329 + if (!this.called) {
  330 + return !!notCalled;
  331 + }
  332 +
  333 + var currentCall;
  334 + var matches = 0;
  335 +
  336 + for (var i = 0, l = this.callCount; i < l; i += 1) {
  337 + currentCall = this.getCall(i);
  338 +
  339 + if (currentCall[actual || method].apply(currentCall, arguments)) {
  340 + matches += 1;
  341 +
  342 + if (matchAny) {
  343 + return true;
  344 + }
  345 + }
  346 + }
  347 +
  348 + return matches === this.callCount;
  349 + };
  350 + }
  351 +
  352 + function matchingFake(fakes, args, strict) {
  353 + if (!fakes) {
  354 + return;
  355 + }
  356 +
  357 + var alen = args.length;
  358 +
  359 + for (var i = 0, l = fakes.length; i < l; i++) {
  360 + if (fakes[i].matches(args, strict)) {
  361 + return fakes[i];
  362 + }
  363 + }
  364 + }
  365 +
  366 + var uuid = 0;
  367 +
  368 + // Public API
  369 + var spyApi = {
  370 + reset: function () {
  371 + this.called = false;
  372 + this.calledOnce = false;
  373 + this.calledTwice = false;
  374 + this.calledThrice = false;
  375 + this.callCount = 0;
  376 + this.args = [];
  377 + this.returnValues = [];
  378 + this.thisValues = [];
  379 + this.exceptions = [];
  380 + this.callIds = [];
  381 + },
  382 +
  383 + create: function create(func) {
  384 + var name;
  385 +
  386 + if (typeof func != "function") {
  387 + func = function () {};
  388 + } else {
  389 + name = sinon.functionName(func);
  390 + }
  391 +
  392 + function proxy() {
  393 + return proxy.invoke(func, this, slice.call(arguments));
  394 + }
  395 +
  396 + sinon.extend(proxy, spy);
  397 + delete proxy.create;
  398 + sinon.extend(proxy, func);
  399 +
  400 + proxy.reset();
  401 + proxy.prototype = func.prototype;
  402 + proxy.displayName = name || "spy";
  403 + proxy.toString = sinon.functionToString;
  404 + proxy._create = sinon.spy.create;
  405 + proxy.id = "spy#" + uuid++;
  406 +
  407 + return proxy;
  408 + },
  409 +
  410 + invoke: function invoke(func, thisValue, args) {
  411 + var matching = matchingFake(this.fakes, args);
  412 + var exception, returnValue;
  413 + this.called = true;
  414 + this.callCount += 1;
  415 + this.calledOnce = this.callCount == 1;
  416 + this.calledTwice = this.callCount == 2;
  417 + this.calledThrice = this.callCount == 3;
  418 + push.call(this.thisValues, thisValue);
  419 + push.call(this.args, args);
  420 + push.call(this.callIds, callId++);
  421 +
  422 + try {
  423 + if (matching) {
  424 + returnValue = matching.invoke(func, thisValue, args);
  425 + } else {
  426 + returnValue = (this.func || func).apply(thisValue, args);
  427 + }
  428 + } catch (e) {
  429 + push.call(this.returnValues, undefined);
  430 + exception = e;
  431 + throw e;
  432 + } finally {
  433 + push.call(this.exceptions, exception);
  434 + }
  435 +
  436 + push.call(this.returnValues, returnValue);
  437 +
  438 + return returnValue;
  439 + },
  440 +
  441 + getCall: function getCall(i) {
  442 + if (i < 0 || i >= this.callCount) {
  443 + return null;
  444 + }
  445 +
  446 + return spyCall.create(this, this.thisValues[i], this.args[i],
  447 + this.returnValues[i], this.exceptions[i],
  448 + this.callIds[i]);
  449 + },
  450 +
  451 + calledBefore: function calledBefore(spyFn) {
  452 + if (!this.called) {
  453 + return false;
  454 + }
  455 +
  456 + if (!spyFn.called) {
  457 + return true;
  458 + }
  459 +
  460 + return this.callIds[0] < spyFn.callIds[0];
  461 + },
  462 +
  463 + calledAfter: function calledAfter(spyFn) {
  464 + if (!this.called || !spyFn.called) {
  465 + return false;
  466 + }
  467 +
  468 + return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];
  469 + },
  470 +
  471 + withArgs: function () {
  472 + var args = slice.call(arguments);
  473 +
  474 + if (this.fakes) {
  475 + var match = matchingFake(this.fakes, args, true);
  476 +
  477 + if (match) {
  478 + return match;
  479 + }
  480 + } else {
  481 + this.fakes = [];
  482 + }
  483 +
  484 + var original = this;
  485 + var fake = this._create();
  486 + fake.matchingAguments = args;
  487 + push.call(this.fakes, fake);
  488 +
  489 + fake.withArgs = function () {
  490 + return original.withArgs.apply(original, arguments);
  491 + };
  492 +
  493 + return fake;
  494 + },
  495 +
  496 + matches: function (args, strict) {
  497 + var margs = this.matchingAguments;
  498 +
  499 + if (margs.length <= args.length &&
  500 + sinon.deepEqual(margs, args.slice(0, margs.length))) {
  501 + return !strict || margs.length == args.length;
  502 + }
  503 + },
  504 +
  505 + printf: function (format) {
  506 + var spy = this;
  507 + var args = [].slice.call(arguments, 1);
  508 + var formatter;
  509 +
  510 + return (format || "").replace(/%(.)/g, function (match, specifyer) {
  511 + formatter = spyApi.formatters[specifyer];
  512 +
  513 + if (typeof formatter == "function") {
  514 + return formatter.call(null, spy, args);
  515 + } else if (!isNaN(parseInt(specifyer), 10)) {
  516 + return sinon.format(args[specifyer - 1]);
  517 + }
  518 +
  519 + return "%" + specifyer;
  520 + });
  521 + }
  522 + };
  523 +
  524 + delegateToCalls(spyApi, "calledOn", true);
  525 + delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn");
  526 + delegateToCalls(spyApi, "calledWith", true);
  527 + delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith");
  528 + delegateToCalls(spyApi, "calledWithExactly", true);
  529 + delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly");
  530 + delegateToCalls(spyApi, "neverCalledWith", false, "notCalledWith", true);
  531 + delegateToCalls(spyApi, "threw", true);
  532 + delegateToCalls(spyApi, "alwaysThrew", false, "threw");
  533 + delegateToCalls(spyApi, "returned", true);
  534 + delegateToCalls(spyApi, "alwaysReturned", false, "returned");
  535 + delegateToCalls(spyApi, "calledWithNew", true);
  536 + delegateToCalls(spyApi, "alwaysCalledWithNew", false, "calledWithNew");
  537 +
  538 + spyApi.formatters = {
  539 + "c": function (spy) {
  540 + return sinon.timesInWords(spy.callCount);
  541 + },
  542 +
  543 + "n": function (spy) {
  544 + return spy.toString();
  545 + },
  546 +
  547 + "C": function (spy) {
  548 + var calls = [];
  549 +
  550 + for (var i = 0, l = spy.callCount; i < l; ++i) {
  551 + push.call(calls, " " + spy.getCall(i).toString());
  552 + }
  553 +
  554 + return calls.length > 0 ? "\n" + calls.join("\n") : "";
  555 + },
  556 +
  557 + "t": function (spy) {
  558 + var objects = [];
  559 +
  560 + for (var i = 0, l = spy.callCount; i < l; ++i) {
  561 + push.call(objects, sinon.format(spy.thisValues[i]));
  562 + }
  563 +
  564 + return objects.join(", ");
  565 + },
  566 +
  567 + "*": function (spy, args) {
  568 + return args.join(", ");
  569 + }
  570 + };
  571 +
  572 + return spyApi;
  573 + }()));
  574 +
  575 + spyCall = (function () {
  576 + return {
  577 + create: function create(spy, thisValue, args, returnValue, exception, id) {
  578 + var proxyCall = sinon.create(spyCall);
  579 + delete proxyCall.create;
  580 + proxyCall.proxy = spy;
  581 + proxyCall.thisValue = thisValue;
  582 + proxyCall.args = args;
  583 + proxyCall.returnValue = returnValue;
  584 + proxyCall.exception = exception;
  585 + proxyCall.callId = typeof id == "number" && id || callId++;
  586 +
  587 + return proxyCall;
  588 + },
  589 +
  590 + calledOn: function calledOn(thisValue) {
  591 + return this.thisValue === thisValue;
  592 + },
  593 +
  594 + calledWith: function calledWith() {
  595 + for (var i = 0, l = arguments.length; i < l; i += 1) {
  596 + if (!sinon.deepEqual(arguments[i], this.args[i])) {
  597 + return false;
  598 + }
  599 + }
  600 +
  601 + return true;
  602 + },
  603 +
  604 + calledWithExactly: function calledWithExactly() {
  605 + return arguments.length == this.args.length &&
  606 + this.calledWith.apply(this, arguments);
  607 + },
  608 +
  609 + notCalledWith: function notCalledWith() {
  610 + for (var i = 0, l = arguments.length; i < l; i += 1) {
  611 + if (!sinon.deepEqual(arguments[i], this.args[i])) {
  612 + return true;
  613 + }
  614 + }
  615 + return false;
  616 + },
  617 +
  618 + returned: function returned(value) {
  619 + return this.returnValue === value;
  620 + },
  621 +
  622 + threw: function threw(error) {
  623 + if (typeof error == "undefined" || !this.exception) {
  624 + return !!this.exception;
  625 + }
  626 +
  627 + if (typeof error == "string") {
  628 + return this.exception.name == error;
  629 + }
  630 +
  631 + return this.exception === error;
  632 + },
  633 +
  634 + calledWithNew: function calledWithNew(thisValue) {
  635 + return this.thisValue instanceof this.proxy;
  636 + },
  637 +
  638 + calledBefore: function (other) {
  639 + return this.callId < other.callId;
  640 + },
  641 +
  642 + calledAfter: function (other) {
  643 + return this.callId > other.callId;
  644 + },
  645 +
  646 + toString: function () {
  647 + var callStr = this.proxy.toString() + "(";
  648 + var args = [];
  649 +
  650 + for (var i = 0, l = this.args.length; i < l; ++i) {
  651 + push.call(args, sinon.format(this.args[i]));
  652 + }
  653 +
  654 + callStr = callStr + args.join(", ") + ")";
  655 +
  656 + if (typeof this.returnValue != "undefined") {
  657 + callStr += " => " + sinon.format(this.returnValue);
  658 + }
  659 +
  660 + if (this.exception) {
  661 + callStr += " !" + this.exception.name;
  662 +
  663 + if (this.exception.message) {
  664 + callStr += "(" + this.exception.message + ")";
  665 + }
  666 + }
  667 +
  668 + return callStr;
  669 + }
  670 + };
  671 + }());
  672 +
  673 + spy.spyCall = spyCall;
  674