Sinon.js — mock-библиотека для JavaScript

от автора

Sinon.js — это mock-библиотека для JavaScript, которую можно использовать с любым тестовым фреймворком. Она предоставляет функции для эмуляции и проверки требуемого поведения в JavaScript. В библиотеке представлено три вида тестирования с spy, stub и mock. В данном посте мы рассмотрим документацию по API Sinon.js вместе с кратким введением в концепцию методов данной библиотеки.


Spy.

Spy(шпион) — это функция, которая записывает аргументы, возвращаемое значение, исходное значение и выданные ошибки (если таковые были) для всех вызовов. Тестовый шпион может быть анонимной функцией или может быть создан поверх существующей функции.

Тестовые шпионы полезны, чтобы проверять обратные вызовы и как определенные функции/методы используются по всей системе во время тестов. Следующий упрощенный пример показывает, как использовать шпионов, чтобы протестировать обработку функцией обратного вызова:

"test should call subscribers on publish": function () {     var callback = sinon.spy();     PubSub.subscribe("message", callback);      PubSub.publishSync("message");      assertTrue(callback.called); } 

Sinon.spy может также следить за существующими функциями. При выполнении исходная функция будет вести себя столь же нормально, но у Вас будет доступ к данным про все вызовы. Ниже следует немного абстрактный пример.

{     setUp: function () {         sinon.spy(jQuery, "ajax");     },      tearDown: function () {         jQuery.ajax.restore(); // Unwraps the spy     },      "test should inspect jQuery.getJSON's usage of jQuery.ajax": function () {         jQuery.getJSON("/some/resource");          assert(jQuery.ajax.calledOnce);         assertEquals("/some/resource", jQuery.ajax.getCall(0).args[0].url);         assertEquals("json", jQuery.ajax.getCall(0).args[0].dataType);     } } 

Создание шпионов: sinon.spy ()

var spy = sinon.spy();
Задает анонимную функцию, которая записывает аргументы текущего значения, сообщения про ошибки и аргументы возвращенного значения для всех вызовов.
var spy = sinon.spy(myFunc);
Создает шпиона поверх готовой функции.
var spy = sinon.spy(object, "method");
Создает шпиона для object.method и заменяет исходный метод. Он действует точно так же, как исходный вариант во всех случаях. Первоначальный метод может быть восстановлен с помощью object.method.restore ().

Spy API

Spy обеспечивают широкий интерфейс для контроля процесса их использования. Приведенные выше примеры показали логическое свойство calledOnce, а также метод getCall и аргументы возвращаемого объекта. Есть три способа проверки данных вызовов.

Наиболее предпочтительный подход заключается в использовании метода calledWith, так как он избавляет Ваш тест от излишней конкретики. Вам ведь не всегда нужно знать сколько раз выполнялся вызов функции, иногда бывает достаточно данных возвращенного значения.

"test should call subscribers with message as first argument" : function () {     var message = 'an example message';     var spy = sinon.spy();      PubSub.subscribe(message, spy);     PubSub.publishSync(message, "some payload");      assert(spy.calledWith(message)); } 

Если Вы хотите больше конкретики, можете непосредственно проверить первый параметр первого вызова. Следующие способы помогут Вам сделать это:

"test should call subscribers with message as first argument" : function () {     var message = 'an example message';     var spy = sinon.spy();      PubSub.subscribe(message, spy);     PubSub.publishSync(message, "some payload");      assertEquals(message, spy.args[0][0]); } 
"test should call subscribers with message as first argument" : function () {     var message = 'an example message';     var spy = sinon.spy();      PubSub.subscribe(message, spy);     PubSub.publishSync(message, "some payload");      assertEquals(message, spy.getCall(0).args[0]); } 

Первый пример использует двумерный массив аргументов непосредственно на шпионе, в то время как второй пример выбирает первое значение вызова, а затем обращается к его аргументу. Какой способ использовать — является вопросом предпочтения, но рекомендуется использовать подход spy.calledWith (arg1, arg2...), если нет необходимости делать тесты более подробным.

Spy API

Spy-объект или объект возвращенный из sinon.spy(). При слежении за существующими методами с sinon.spy (object, method), следующие свойства и методы также доступны для использования на object.method.

spy.withArgs(arg1[, arg2, ...]);
Создает шпиона, который записывает вызовы только тогда, когда полученные аргументы соответствуют переданным для withArgs.

"should call method once with each argument": function () {     var object = { method: function () {} };     var spy = sinon.spy(object, "method");     spy.withArgs(42);     spy.withArgs(1);      object.method(42);     object.method(1);      assert(spy.withArgs(42).calledOnce);     assert(spy.withArgs(1).calledOnce); } 

spy.callCount
Отображает число зарегистрированных вызовов.
spy.called
Предоставляет подтверждение, если spy был вызван хотя бы раз.
spy.calledOnce
Предоставляет подтверждение, если spy был вызван только один раз.
spy.calledTwice
Предоставляет подтверждение, если spy был вызван только два раз.
spy.calledThrice
Предоставляет подтверждение, если spy был вызван только три раза.
spy.firstCall
Первый вызов.
spy.secondCall
Второй вызов.
spy.thirdCall
Третий вызов.
spy.lastCall
Последний вызов.
spy.calledBefore(anotherSpy);
Выдает подтверждение, если шпион был вызван прежде чем anotherSpy.
spy.calledAfter(anotherSpy);
Выдает подтверждение, если шпион был вызван после anotherSpy.
spy.calledOn(obj);
Выдает подтверждение, если шпион был вызван хотя бы раз с obj как this.
spy.alwaysCalledOn(obj);
Выдает подтверждение, если шпион всегда вызывали с obj как this.
spy.calledWith(arg1, arg2, ...);
Выдает подтверждение, если шпион вызван, по крайней мере один раз с предоставленными аргументами. Может быть выполнен даже при частичном совпадении, Sinon проверяет только предоставленные аргументы против фактических аргументов, таким образом получил совпадение с предусмотренными аргументами (в тех же местах) выдаст подтверждение.
spy.alwaysCalledWith(arg1, arg2, ...);
Выдает подтверждение, если шпион всегда вызывается с предусмотренными аргументами (и частично с другими).
spy.alwaysCalledWithExactly(arg1, arg2, ...);
Выдает подтверждение, если шпион всегда вызывается в полном соответсвии с предусмотренными аргументами.
spy.calledWithMatch(arg1, arg2, ...);
Выдает подтверждение, если шпион всегда вызывается с предусмотренными аргументами (и частично с другими). Ведет себя точно так же как spy.calledWith(sinon.match(arg1), sinon.match(arg2), ...).
spy.alwaysCalledWithMatch(arg1, arg2, ...);
Выдает подтверждение, если шпион всегда вызывается с предусмотренными аргументами. Ведет себя точно так же как spy.alwaysCalledWith(sinon.match(arg1), sinon.match(arg2), ...).
spy.calledWithNew();
Выдает подтверждение, если spy/stub были вызваны новым оператором. Помните, что это результат на основе значения этого объекта и прототипа функции шпиона, это может дать ложноположительный результат, если вы активно возвращаете правильный вид объекта.
spy.neverCalledWith(arg1, arg2, ...);
Выдает подтверждение, если spy/stub никогда не вызывались с предусмотренными аргументами.
spy.neverCalledWithMatch(arg1, arg2, ...);
Выдает подтверждение, если spy/stub никогда не вызывались с большим значением предусмотренного аргументами. Ведет себя точно так же как spy.neverCalledWith(sinon.match(arg1), sinon.match(arg2), ...)..
spy.threw();
Выдает подтверждение, если шпион выдавал исключение, по крайней мере, один раз.
spy.threw("TypeError");
Выдает подтверждение, если шпион выдал исключение для указанного типа, хотя бы один раз.
spy.threw(obj);
Выдает подтверждение, если шпион выдал исключение для указанного объекта, по крайней мере один раз.
spy.alwaysThrew();
Выдает подтверждение, если шпион всегда выдавал исключение.
spy.alwaysThrew("TypeError");
Выдает подтверждение, если шпион всегда выдавал исключение для указанного типа.
spy.alwaysThrew(obj);
Выдает подтверждение, если шпион всегда выдавал исключения для указанного объекта.
spy.returned(obj);
Выдает подтверждение, если шпион возвратил предусмотренное значение, по крайней мере, один раз. Использует глубокое сравнение для объектов и массивов. Используйте spy.returned (sinon.match.same (obj)) для строгого сравнения.
spy.alwaysReturned(obj);
Выдает подтверждение, если шпион всегда возвращает предусмотренное значение.
var spyCall = spy.getCall(n);
Выдает энный [call](#spycall). Предоставляет доступ к отдельным вызовам, помогает более детально проверять поведение, если шпион называется более чем один раз. Пример:

sinon.spy(jQuery, "ajax"); jQuery.ajax("/stuffs"); var spyCall = jQuery.ajax.getCall(0);  assertEquals("/stuffs", spyCall.args[0]); 

spy.thisValues
В области для объекта spy.thisValues[0] представлен объект для первого вызова.
spy.args
Массив аргументов spy.args [0] — это массив с аргументами полученными в первом вызове.
spy.exceptions
В массиве для выданных исключений spy.exceptions[0] представлены исключения, выданные первым вызовом. Если вызов не выдавал ошибку, то значение в поле .exceptions будет ‘undefined’.
spy.returnValues
Массив возвращенных значений, spy.returnValues[0] — возвращенное значение первого вызова. Если вызов вернул не точное значение поля в .returnValues будет ‘undefined’.
spy.reset()
Сбрасывает состояние шпиона.
spy.printf(format string", [arg1, arg2, ...])`
Выдает переданный формат последовательности с выполнением следующих замен:

  • #n: имя шпиона («spy» по умолчанию)
  • #с: сколько раз шпион вызвали в словах (“once”, “twice”, и т.д.)
  • #С: список последовательных вызовов шпиона, каждый вызов помечается префиксом новой строки и четырьмя пробелами
  • #t: разграниченный запятыми список значений, с которыми был вызван шпион
  • #: n отформатированное значение энного аргумента переданного printf
  • #*: разделенный запятыми список из (не форматированной последовательности) аргументов переданных printf

Индивидуальные вызовы шпиона

var spyCall = spy.getCall(n)
Предоставляет энный [call](#spycall). Доступ к отдельным вызовам помогает с более подробной проверкой поведения, когда шпиона вызывают несколько раз. Пример:

sinon.spy(jQuery, "ajax"); jQuery.ajax("/stuffs"); var spyCall = jQuery.ajax.getCall(0);  assertEquals("/stuffs", spyCall.args[0]); 

spyCall.calledOn(obj);
Предоставляет подтверждение, если объект был актуальным для этого вызова.
spyCall.calledWith(arg1, arg2, ...);
Предоставляет подтверждение, если вызов получил предусмотренные аргументы (возможно подобные).
spyCall.calledWithExactly(arg1, arg2, ...);
Предоставляет подтверждение, если вызов получил предусмотренные аргументы и никакие другие.
spyCall.calledWithMatch(arg1, arg2, ...);
Предоставляет подтверждение, если вызов получил предусмотренные аргументы (возможно подобные). Ведет себя точно так же как spyCall.calledWith (sinon.match (arg1), sinon.match (arg2)...).
spyCall.notCalledWith(arg1, arg2, ...);
Предоставляет подтверждение, если вызов не получил предусмотренные аргументы.
spyCall.notCalledWithMatch(arg1, arg2, ...);
Предоставляет подтверждение, если вызов не получил предусмотренные аргументы. Ведет себя точно так же как spyCall.notCalledWith(sinon.match(arg1), sinon.match(arg2), ...).;
spyCall.threw();
Предоставляет подтверждение, если вызов выдал ошибку.
spyCall.threw(TypeError");
Предоставляет подтверждение, если вызов выдал ошибку предусмотренного типа.
spyCall.threw(obj);
Предоставляет подтверждение, если вызов выдал ошибку для предусмотренного объекта.
spyCall.thisValue
Вызовы значения this.
spyCall.args
Массив полученных аргументов.
spyCall.exception
Выданное исключение (ошибка), если таковы имеются.
spyCall.returnValue
Возвращенное значение.

Stubs

Stubs (заглушки) — это функции с предварительно запрограммированным поведением. Они полностью поддерживают API шпионов в дополнение к методам, которые могут быть использованы для изменения поведения stubs.

Как и шпионы, заглушки могут быть анонимными или обертывать существующие функции. При обертывание существующей функции с заглушкой, оригинальная функция не вызывается.

Использовать заглушки стоит в тех случаях, когда вы хотите:

  1. Управлять поведением теста и заставить код выдать ошибку в определенный момент. Примеры включают принудительные методы выдачи ошибки, чтобы проверить ее обработку.
  2. Если Вы хотите предотвратить непосредственный вызов определенного метода (возможно, из-за того, что это вызывает нежелательное поведение, например, XMLHttpRequest или аналогичное).

Следующий пример — это еще один тест PubSubJS от Моргана Родерика (Morgan Roderick), который показывает, как создать анонимную заглушку, выдающую ошибку при вызове.

"test should call all subscribers, even if there are exceptions" : function(){     var message = 'an example message';     var error = 'an example error message';     var stub = sinon.stub().throws();     var spy1 = sinon.spy();     var spy2 = sinon.spy();      PubSub.subscribe(message, stub);     PubSub.subscribe(message, spy1);     PubSub.subscribe(message, spy2);      PubSub.publishSync(message, undefined);      assert(spy1.called);     assert(spy2.called);     assert(stub.calledBefore(spy1)); } 

Обратите внимание, что заглушки также включены в интерфейс шпиона. Тест проверяет, что все необходимые обратные вызовы были вызваны и заглушка выдавала ошибку для текущего вызова перед новым вызовом.

Определение поведение stubs на последовательных вызовах

Многократные вызовы определенных методов поведения, таких как returns или throws переопределяют поведение заглушки. Начиная с версии Sinon 1.8 Вы можете использовать метод <codeonCall</code, чтобы получит различную реакцию от заглушки при последовательных вызовах.

Обратите внимание, что в Sinon версии 1.5 и 1.7 множественные вызовы для yields* и callsArg* — это семейство методов определения поведенческого ряда во время последовательных вызовов.

Stub API

var stub = sinon.stub();
Создает анонимную функцию заглушки.
var stub = sinon.stub(object, "method");
Заменяет object.method с функцией заглушки. Исходная функция может быть восстановлена, вызвав object.method.restore (); или stub.restore ();. Ошибка может быть выдана, если свойства не относятся к функции — это сделано для избежания опечаток при использовании заглушки.
var stub = sinon.stub(object, "method", func);
Заменяет object.method и func, обернутые шпионом. Обычно используется object.method.restore (); для восстановления исходного метода.
var stub = sinon.stub(obj);
Заглушка для указанного объекта. Обратите внимание, что метод индивидуальных заглушек — это лучшая практика для применения на объектах, которые Вы не понимаете или тех, которые управляют всеми методами (например в зависимости библиотеки). Метод индивидуальных заглушек тестирует поведение объекта более точно и менее восприимчиво к неожиданному поведению во время изменений кода объекта.

Если Вы хотите создать stub-объект для MyConstructor, но не хотите, чтобы конструктор был вызван, используйте эту служебную функцию:

var stub = sinon.createStubInstance(MyConstructor) 

stub.withArgs(arg1[, arg2, ...]);
Метод заглушки исключительно для указанных аргументов. Этот метод полезен для получения большей выразительности в ваших утверждениях, где Вы можете получить доступ к шпиону с тем же вызовом. Также полезно создать заглушку, которая может действовать иначе, в ответ на различные параметры.

"test should stub method differently based on arguments": function () {     var callback = sinon.stub();     callback.withArgs(42).returns(1);     callback.withArgs(1).throws("TypeError");      callback(); // No return value, no exception     callback(42); // Returns 1     callback(1); // Throws TypeError } 

stub.onCall(n);
Добавлен в Sinon.JS 1.8.
Определяет поведение заглушки на энном вызове. Полезный для тестирования последовательных взаимодействий.

"test should stub method differently on consecutive calls": function () {     var callback = sinon.stub();     callback.onCall(0).returns(1);     callback.onCall(1).returns(2);     callback.returns(3);      callback(); // Returns 1     callback(); // Returns 2     callback(); // All following calls return 3 } 

Есть способы такие как onFirstCall, onSecondCall, onThirdCall, чтобы сделать чтение определенных заглушек более естественным.
`onCall` может быть объединен со всеми методами определяющими поведение в этом разделе. В частности может использоваться вместе с 'withArgs'.

"test should stub method differently on consecutive calls with certain argument": function () {     var callback = sinon.stub();     callback.withArgs(42)         .onFirstCall().returns(1)         .onSecondCall().returns(2);     callback.returns(0);      callback(1); // Returns 0     callback(42); // Returns 1     callback(1); // Returns 0     callback(42); // Returns 2     callback(1); // Returns 0     callback(42); // Returns 0 } 

Обратите внимание, как поведение заглушки для аргумента 42 возвращается к поведению по умолчанию, как только вызовы больше не были определены.
stub.onFirstCall();
Псевдоним для stub.onCall (0);
stub.onSecondCall();
Псевдоним для stub.onCall (1);
stub.onThirdCall();
Псевдоним для stub.onCall (2);
stub.returns(obj);
Заставляет заглушку вернуть указанный объект.
stub.returnsArg(index);
Заставляет заглушку возвращать аргумент с указанным индексом. stub.callsArg (0); заставляет заглушку возвращать первичный аргумент.
stub.returnsThis();
Заглушка возвращает значение this.
stub.throws();
Заставляет заглушку выдавать исключение (Ошибку).
stub.throws("TypeError");
Заставляет заглушку выдавать ошибку заданого типа.
stub.throws(obj);
Заставляет заглушку выдать ошибку для заданного объекта.
stub.callsArg(index);
Заставляет заглушку вызывать аргумент по указанному индексу, как функцию обратного вызова. stub.callsArg (0); заставляет заглушку вызывать первичный аргумент, как обратный вызов.
stub.callsArgOn(index, context);
Выполняется точно так же как и stub.callsArg(index);, но с дополнительным параметром для передачи this контекста.
stub.callsArgWith(index, arg1, arg2, ...);
Выполняется так же как и callsArg, но с аргументами для передачи в функцию обратного вызова.
stub.callsArgOnWith(index, context, arg1, arg2, ...);
Выполняется точно так же как и stub.callsArgWith(index, arg1, arg2, ...);, но с дополнительным параметром для передачи this контекста.
stub.yields([arg1, arg2, ...])
Ведет себя почти так же как callsArg. Заставляет заглушку вызывать первый обратный вызов, который он получает с указанными аргументами (если таковые имеются). Если метод принимает больше одного обратного вызова, Вам нужно использовать callsArg, чтобы заглушка имела возможность вызывать другие обратные вызовы отличные от первого.
stub.yieldsOn(context, [arg1, arg2, ...])
Выполняется точно так же как и stub.yieldsOn(context, [arg1, arg2, ...]);, но с дополнительным параметром для передачи this контекста.
stub.yieldsTo(property, [arg1, arg2, ...])
Заставляет шпиона вызывать обратный вызов, переданный как свойство объекта шпиону. Так же как и yields, yieldsTo захватывает первый соответствующий аргумент, находит обратный вызов и вызывает его с (дополнительными) аргументами.
stub.yieldsToOn(property, context, [arg1, arg2, ...])
Выполняется точно так же как и stub.yieldsTo(property, [arg1, arg2, ...]);, но с дополнительным параметром для передачи this контекста.

"test should fake successful ajax request": function () {     sinon.stub(jQuery, "ajax").yieldsTo("success", [1, 2, 3]);      jQuery.ajax({         success: function (data) {             assertEquals([1, 2, 3], data);         }     }); } 

stub.yield([arg1, arg2, ...])
Вызывает обратные вызовы переданные к заглушке с указанными параметрами. Если заглушку никогда не вызывали с указанными аргументами функции, yield выдаст ошибку, с псевдонимом invokeCallback.
stub.yieldTo(callback, [arg1, arg2, ...])
Вызывает обратные вызовы, переданные как свойство объекта шпиону. Так же как и yields, yieldsTo захватывает первый соответствующий аргумент, находит обратный вызов и вызывает его с (дополнительными) аргументами.
stub.callArg(argNum)
Так же как и yield, но с точным номером аргумента определяющий обратный вызов который нужно заново вызвать. Полезно использовать, если функция вызвана больше чем с одним обратным вызовом, и просто вызвать первый обратных вызов не требуется.

"calling the last callback": function () {     var callback = sinon.stub();     callback(function () {         console.log("Success!");     }, function () {         console.log("Oh noes!");     });      callback.callArg(1); // Logs "Oh noes!" } 

stub.callArgWith(argNum, [arg1, arg2, ...])
Подобно stub.callArg(argNum), но с аргументами.
stub.callsArgAsync(index);
Выполняет то же самое, что и соответствующий синхронный дубликат, но с отложенным обратным вызовом (который выполняется не сразу, а после короткого тайм-аута и в другом “потоке”).

stub.callsArgAsync(index);
stub.callsArgOnAsync(index, context);
stub.callsArgWithAsync(index, arg1, arg2, ...);
stub.callsArgOnWithAsync(index, context, arg1, arg2, ...);
stub.yieldsAsync([arg1, arg2, ...])
stub.yieldsOnAsync(context, [arg1, arg2, ...])
stub.yieldsToAsync(property, [arg1, arg2, ...])
stub.yieldsToOnAsync(property, context, [arg1, arg2, ...])
Выполняются точно так же как и их соответствующие синхронные дубликаты, но с отложенным обратным вызовом (который выполняется а сразу, но после короткого тайм-аута и в другом “потоке”)

Mocks

Mocks (имитация) являются имитационным методами (как шпионы) с предварительно запрограммированным поведением (как заглушка), а также предварительно запрограммированным ожиданием. Имитация не сможет выполнить тест, если она не будет использована именно так, как ожидалось.

Имитации могут быть использованы только для метода под тест. В каждом юнит-тестировании должен быть хотя бы один блок в процессе тестирования. Чтобы контролировать, как используется данный блок и задавать ожидаемый результат (в отличие от утверждений полученных постфактум), используйте имитацию.

Имитации комплектуются заданными Вами ожиданиями от теста, что может привести к провалу всего теста. Таким образом тест навязывает детали выполнения. Существует простое правило: если Вы не будете добавлять утверждение для некоторых вызовов, не выполняйте имитацию для них. Лучше используйте вместо этого заглушку. В любом случаи у Вас не должно быть больше одной имитации (возможно с несколькими заданными ожиданиями) в одном тесте.

Ожидания могут использовать API как шпиона так и заглушки.

Чтобы увидеть, как имитация выглядеть в Sinon.JS, вот один из примеров PubSubJS. На этот раз с использованием метода обратного вызова и как с помощью имитации проверяется его поведение:

"test should call all subscribers when exceptions": function () {     var myAPI = { method: function () {} };      var spy = sinon.spy();     var mock = sinon.mock(myAPI);     mock.expects("method").once().throws();      PubSub.subscribe("message", myAPI.method);     PubSub.subscribe("message", spy);     PubSub.publishSync("message", undefined);      mock.verify();     assert(spy.calledOnce); } 

Mocks API

var mock = sinon.mock(obj);
Создает имитацию для указанного объекта. Сам объект не изменяется, но обертывает mock-обект для установки ожиданий на методах объекта.
var expectation = mock.expects("method");
Переопределяет obj.method с функцией имитации и возвращает его.
mock.restore();
Восстанавливает все методы имитации.
mock.verify();
Приводит поиск указанной имитации. Если указанная имитация не была обнаружена, выдается сообщение про ошибку. Кроме того, помогает восстанавливать методы имитации.

Expectations

Все методы expectations (ожидание) возвращают ожидание. Это означает, что Вы можете объединить их в цепочку. Типичное использование:

sinon.mock(jQuery).expects("ajax").atLeast(2).atMost(5); jQuery.ajax.verify(); 

Expectations API

var expectation = sinon.expectation.create([methodName]);
Создает ожидание без mock=объекта, в основном это анонимная функция имитации. Название метода это дополнение и используется в сообщениях об ошибках, чтобы сделать их более читаемыми.
var expectation = sinon.mock();
Точно так же как и var expectation = sinon.expectation.create([methodName]);
expectation.atLeast(number);
Определяет минимальное количество ожидаемых вызовов.
expectation.atMost(number);
Определяет максимальное количество ожидаемых вызовов.
expectation.never();
Ожидается, что указанный метод никогда не будет вызван.
expectation.once();
Ожидается, что указанный метод будет вызван только один раз.
expectation.twice();
Ожидается, что указанный метод будет вызван два раза.
expectation.thrice();
Ожидается, что указанный метод будет вызван три раза.
expectation.exactly(number);
Ожидается, что указанный метод будет вызван точное количество раз.
expectation.withArgs(arg1, arg2, ...);
Ожидается, что метод будет вызван с заданными аргументами (возможно подобными).
expectation.withExactArgs(arg1, arg2, ...);
Ожидается, что метод будет вызван с заданными аргументами и никакими другими.
expectation.on(obj);
Ожидается, что метод будет вызван с obj как this.
expectation.verify();
Проверяет все ожидание и выдает ошибку, если указанное ожидание не было найдено.

Fake timers

Поддельные таймеры — синхронная реализация setTimeout и помощников Sinon.JS, которые могут перезаписать глобальные функции и позволят вам более легко тестировать код используя их.

Чтобы использовать поддельные таймеры с IE Вам нужно установить sinon-ie-1.17.2 сразу же после установки sinon-1.17.2.js.

Для автономного использования поддельных таймеров рекомендуется использовать lolex пакет. Это обеспечивает тот же функциональный набор и был извлечен ранее из Sinon.JS.

{     setUp: function () {         this.clock = sinon.useFakeTimers();     },      tearDown: function () {         this.clock.restore();     },      "test should animate element over 500ms" : function(){         var el = jQuery("<div></div>");         el.appendTo(document.body);          el.animate({ height: "200px", width: "200px" });         this.clock.tick(510);          assertEquals("200px", el.css("height"));         assertEquals("200px", el.css("width"));     } } 

Fake timers API

var clock = sinon.useFakeTimers();
Sinon заменить глобальный setTimeout, setInterval, clearTimeout, clearInterval и даты в пользовательской реализации, которые связаны с возвращаемым часовым объектом. Запускает часы в эпоху UNIX с нулевой отметки времени.
var clock = sinon.useFakeTimers(now);
Выполняет все то же самое, что и метод приведенный выше, но вместо запуска часов с нулевой отметкой часы запускаются с указанной отметкой в поле (now).
var clock = sinon.useFakeTimers([now, ]prop1, prop2, ...);
Устанавливает временную отметку часов и имена поддельных функций. Возможные функции — setTimeout, clearTimeout, setInterval, clearInterval, и дата. Может также быть вызван без временной метки.
clock.tick(ms);
Временная отметка выставляется перед ms и указывается в миллисекундах. Все таймеры с временем старта, попадающим в указанный диапазон времени будут вызваны.
clock.restore();
Восстановить поддельные методы. Вызвать например tearDown

Fake XMLHttpRequest

Предоставляет поддельную реализацию XMLHttpRequest и обеспечивает несколько интерфейсов для управления объектами, создаваемыми им. Также подделывает собственный XMLHttpRequest и ActiveXObject. Помогает с тестированием запросов, выполненных с XHR.

Чтобы использовать XHR с IE Вам нужно установить sinon-ie-1.17.2 сразу же после установки sinon-1.17.2.js

Поддельный сервер и XHR могут использоваться абсолютно автономно при условии, что загружена sinon-server-1.17.2. При использовании поддельного сервера в IE вам также нужен sinon-1.17.2. Загрузите его после sinon-server-1.17.2.

{     setUp: function () {         this.xhr = sinon.useFakeXMLHttpRequest();         var requests = this.requests = [];          this.xhr.onCreate = function (xhr) {             requests.push(xhr);         };     },      tearDown: function () {         this.xhr.restore();     },      "test should fetch comments from server" : function () {         var callback = sinon.spy();         myLib.getCommentsFor("/some/article", callback);         assertEquals(1, this.requests.length);          this.requests[0].respond(200, { "Content-Type": "application/json" },                                  '[{ "id": 12, "comment": "Hey there" }]');         assert(callback.calledWith([{ id: 12, comment: "Hey there" }]));     } } 

sinon.useFakeXMLHttpRequest

var xhr = sinon.useFakeXMLHttpRequest();
Sinon заменяет родной XMLHttpRequest обект в браузере поддерживающим данный объект по умолчанию, который не отправит актуальный запрос запрос. В браузерах, которые поддерживают ActiveXObject, данный конструктор заменяется и поддельные объекты возвращаются для progID XMLHTTP. Другие progID, такие как XMLDOM, остаются нетронутыми.

Родной объект XMLHttpRequest будет доступен в sinon.xhr. XMLHttpRequest.

xhr.onCreate = function (xhr) {};
Присваивает функцию onCreate свойству возвращенного объекта от useFakeXMLHttpRequest ()</code. Это точно такой же <code>FakeXMLHttpRequest только подписанный Вами. Ниже можно ознакомится с API для поддельных объектов XHR. Используя данный обозреватель означает, что Вы все еще можете охватить созданные объекты например jQuery.ajax (или другие абстракции/фреймворки)
xhr.restore();
Восстанавливает исходную функцию(и).

FakeXMLHttpRequest

String request.url
URL-адрес устанавливается на объекте запроса.
String request.method
Метод запроса как последовательность.
Object request.requestHeaders
Вышеуказанный объект для всех заголовков запроса, т.е.:

{     "Accept": "text/html, */*",     "Connection": "keep-alive" } 

String request.requestBody
Запрос тела.
int request.status
Состояние запроса. Будет не определен, если указанный запрос не был обработан.
String request.statusText
Выполняется, если метод ответа был вызван.
boolean request.async
Будет ли запрос асинхронным или нет.
String request.username
Имя пользователя, если таковое имеется.
String request.password
Пароль, если таковой имеется.
Document request.responseXML
При использовании ответа, данное свойство заполняется с анализом документа если указанный заголовок совпадает.
String request.getResponseHeader(header);
Предоставляет ответ к запросу с указанным заголовком, если у ответа был такой же заголовок как и у запроса.
Object request.getAllResponseHeaders();
Все заголовки ответа будут представлены в виде объекта.

Фильтрованные запросов

При использовании Sinon.JS для макетов или частичного интегрированного/функционального тестирования, Вы скорее всего захотите подделать только некоторые запросы в это же время позволяя другим запросам идти на внутренний сервер. С помощью фильтрации FakeXMLHttpRequest (реализована в Sinon 1.3.0) Вы можете использовать следующие методы:

Методы

FakeXMLHttpRequest.useFilters
По умолчанию все запросы ложные. Sinon проверяет добавленные фильтры, если запрос должен быть не ложным его нужно добавить в фильтр.
FakeXMLHttpRequest.addFilter(fn)
Так добавляется запрос, который решает, каким должен быть запрос — поддельным или нет. Фильтр вызывается тогда, когда xhr.open вызван с теми же параметрами (метод, URL, асинхронность, имя пользователя, пароль). Если фильтр выдаст подтверждение, запрос не будет заменен поддельным.

Имитация ответов сервера

Имитация

request.setResponseHeaders(object);
Устанавливает заголовок ответа, например, { "Content-Type": "text/html", /* ... */ }, обновляет свойство readyState и запускает onreadystatechange.
request.setResponseBody(body);
Устанавливает ответ тела, обновляет свойство readyState и запускает onreadystatechange. Кроме того заполняет responseXML с проанализированным документом, если заголовки ответа совпадают.
request.respond(status, headers, body);
Вызывает два вышеупомянутых метода и устанавливает статус и свойства для statusText. Статус должно быть числовым, поиск статуса для текста осуществляется в sinon. FakeXMLHttpRequest.statusCodes.
Boolean request.autoRespond
Когда установлено требование подтвердить запрос, это вынуждает сервер давать ответ на входящий запрос после тайм-аута.Тайм-аут по умолчанию составляет 10 мс, но Вы можете изменить его с помощью свойства autoRespondAfter.

Обратите внимание на то, что эта функция предназначена, чтобы помочь во время разработки макета и не подходит для использования в тестах.

Number request.autoRespondAfter
Когда автоответ подтвержден, ответ на него будет дан после указанного числа миллисекунд. По умолчанию значение равно 10 мс.

FakeServe

API высокого уровня для манипулирования инстансами FakeXMLHttpRequest

{     setUp: function () {         this.server = sinon.fakeServer.create();     },      tearDown: function () {         this.server.restore();     },      "test should fetch comments from server" : function () {         this.server.respondWith("GET", "/some/article/comments.json",             [200, { "Content-Type": "application/json" },              '[{ "id": 12, "comment": "Hey there" }]']);          var callback = sinon.spy();         myLib.getCommentsFor("/some/article", callback);         this.server.respond();          sinon.assert.calledWith(callback, [{ id: 12, comment: "Hey there" }]);     } } 

API FakeServe

var server = sinon.fakeServer.create([config]);
Создает новый сервер. Эта функция также вызывает sinon.useFakeXMLHttpRequest (). Создает/принимает дополнительные свойства для настройки ложного сервер.
var server = sinon.fakeServerWithClock.create();
Создает сервер, который управляет поддельными таймерами. Это полезно при тестировании объектов XHR, созданных с помощью, например, jQuery 1.3.x а не обычный onreadystatechange. jQuery 1.3.x использует таймер, чтобы опросить объект относительно завершения
server.configure(config)
Конфигурирует поддельный сервер. Ниже будут описаны для параметров конфигурации.
server.respondWith(response);
Ответ может быть трех типов:

  • Строка, представляющая тело ответа
  • Массив с статусами, заголовками и телом ответа, например, [200, { "Content-Type": "text/html", "Content-Length": 2 }, "OK"]
  • Функция.

Статус по умолчанию задан 200 и заголовки не заданы по умолчанию. Общий ответ по умолчанию [404, {}, ""]

Если ответ является функцией, то он будет предан объекту запроса. Вам нужно вручную вызвать ответ для того, чтобы завершить данный запрос.

server.respondWith(url, response);
Отвечает на все запросы к данному URL, например, /posts/1
server.respondWith(method, url, response);
Отвечает на все запросы метода к данному URL с данным ответом. Метод — глагол HTTP.
server.respondWith(urlRegExp, response);
URL может быть регулярным выражением, например, /\/post\//\d+. Если ответ будет функцией он будет передан в группы постоянных выражений вмести с объектом XMLHttpRequest.

server.respondWith(/\/todo-items\/(\d+)/, function (xhr, id) {  xhr.respond(200, { ?Content-Type?: ?application/json? }, ?[{ ?id?: ? + id + ? }]?); }); 

server.respondWith(method, urlRegExp, response);
Отвечает на все запросы к URL-адресам, которые соответствуют регулярным выражениям.
server.respond();
Вызывает все асинхронные запросы с очередями, чтобы получить ответ. Если ни один из ответов не добавлен через respondWith соответсвенно, ответ по умолчанию [404, {}, ""]. На синхронные запросы ответ поступает сразу, поэтому удостоверьтесь, что respondWith вызвали заранее. Если вызов произведен с аргументами, respondWith будет вызван с теми же параметрами прежде чем ответить на запрос.
server.autoRespond = true;
Если установлено подтверждение — это заставляет сервер автоматически ответит на любой запрос после тайм-аута. Тайм-аут по умолчанию составляет 10 мс, но Вы можете изменить его через свойство autoRespondAfter. Обратите внимание на то, что эта функция предназначена помогать во время разработки макета и не подходит для использования в тестах. Для синхронных непосредственных ответов используйте respondImmediately вместо этого.
server.autoRespondAfter = ms;
Заставляет сервер автоматически отвечать на входящие запросы после тайм-аута.
server.respondImmediately = true;
Если подтверждено, сервер ответит на каждый запрос сразу и синхронно. Поскольку это синхронный метод и непосредственно он не подходит для моделирования фактической сетевой задержки в тестах или макетах. Чтобы моделировать сетевую задержку с непроизвольными реакциями, см. server.autoRespond и server.autoRespondAfter.
Boolean `server.fakeHTTPMethods`
Если установлено подтверждение, сервер найдет method параметр в теле POST и распознает его как фактический метод. Поддерживает образцы, характерные для приложений Ruby и Rails. Для пользовательского поддельного метода HTTP переопределите запрос server.getHTTPMethod.
server.getHTTPMethod(request)
Используется, чтобы определить метод HTTP, используемый с указанным запросом. По умолчанию этот метод просто возвращает request.method. Когда server.fakeHTTPMethods будет подтвержден, метод возвратит значение параметра _method, если метод будет в POST. Этот метод может быть переопределен для поддержки пользовательского поведения.
server.restore();
Восстанавливает родной XHR конструктор.

Параметры поддельного сервера

Эти параметры являются свойствами объекта сервера и могут быть установлены непосредственно на:

server.autoRespond = true 

Вы также можете передать параметры с буквальным объектом в fakeServer.create и .configure.

boolean autoRespond
Заставляет сервер автоматически ответить на любой запрос после тайм-аута. Тайм-аут по умолчанию составляет 10 мс, но Вы можете изменить его через свойство autoRespondAfter. Обратите внимание на то, что эта функция предназначена, чтобы помочь во время разработки макета и не подходит для использования в тестах. Для синхронных непосредственных ответов используйте моментальный ответ вместо этого.
int autoRespondAfter (ms)
Заставляет сервер автоматически отвечать на входящие запросы после указанного тайм-аута.
boolean respondImmediately
Сервер ответит на каждый запрос сразу и синхронно. Поскольку это синхронный метод, он не подходит для моделирования фактической сетевой задержки в тестах или макетах. Чтобы моделировать сетевую задержку с непроизвольными реакциями, см. server.autoRespond и server.autoRespondAfter.
boolean fakeHTTPMethods
Сервер найдет _method параметр в теле POST и распознает это как фактический метод. Поддерживает образцы, характерные для приложений Ruby и Rails. Для пользовательского поддельного метода HTTP переопределите запрос server.getHTTPMethod.

JSON-P

JSON-P не использует запросы Ajax, которые связаны с поддельным сервером. Запрос JSON-P фактически создает элемент сценария и вставляет его в документ. В данном случае нет возможности разумно подделать запросы автоматически. Самый лучший вариант — это использовать заглушку для jQuery в этом случае.

sinon.stub(jQuery, "ajax"); sinon.assert.calledOnce(jQuery.ajax); 

В принципе, можно создать поддельный сервер, который отслеживал бы обращения к JQuery и заменял любые запросы к jQuery.ajax, когда используется JSON-P. Но представленный выше способ значительно проще и вероятность возможной ошибки значительно ниже.

Assertions

Sinon.JS поставляется с набором утверждений, которые отображают большинство методов верификации поведение и свойств шпионов и заглушек. Преимущество использования утверждений состоит в том, что неудавшиеся ожидания на заглушках и шпионах могут быть выражены непосредственно как отказы утверждения с подробными сообщениями об ошибках.

Чтобы убедиться, что утверждения нормально интегрируются с тестовой базы, Вы должны настроить либо sinon.assert.fail или sinon.assert.failException и прочитать про sinon.assert.expose и sinon.assert.pass.

Утверждения могут использоваться или со шпионами, или с заглушками.

"test should call subscribers with message as first argument" : function () {     var message = "an example message";     var spy = sinon.spy();      PubSub.subscribe(message, spy);     PubSub.publishSync(message, "some payload");      sinon.assert.calledOnce(spy);     sinon.assert.calledWith(spy, message); } 

Assertions API

sinon.assert.fail(message)
При вызове этого метода, утверждение выдает ошибку. По умолчанию он выдает ошибку типа sinon.assert.failException. Если Ваша платформа тестирования ищет ошибки утверждений, проверяя на определенные ошибки, Вы можете просто переопределить вид выданной ошибки.
sinon.assert.failException
Значения по умолчанию для ошибок утверждения.
sinon.assert.pass(assertion)
Вызывается каждый раз, когда передается утверждение. Реализация по умолчанию ничего не делает.
sinon.assert.notCalled(spy)
Срабатывает, если шпиона никогда не вызывали.
sinon.assert.called(spy)
Срабатывает, если шпион вызывали хотя бы один раз.
sinon.assert.calledOnce(spy)
Срабатывает, если шпион вызывали только один раз.
sinon.assert.calledTwice()
Срабатывает, если шпион вызывали только два раза.
sinon.assert.calledThrice()
Срабатывает, если шпиона вызывали только три раза.
sinon.assert.callCount(spy, num)
Пропуска шпиона назвали ровно п раз.
sinon.assert.callOrder(spy1, spy2, ...)
Срабатывает, если указанные шпионы были вызваны в указанном порядке.
sinon.assert.calledOn(spy, obj)
Срабатывает, если шпион вызывали с объектом как значение this.
sinon.assert.alwaysCalledOn(spy, obj)
Срабатывает, если шпиона всегда вызывали с объектом как значение this.
sinon.assert.calledWith(spy, arg1, arg2, ...)
Срабатывает, если шпиона вызывается вместе с указанными аргументами (частично другими).
sinon.assert.alwaysCalledWith(spy, arg1, arg2, ...)
Срабатывает, если шпиона всегда вызывается вместе с указанными аргументами (частично другими).
sinon.assert.neverCalledWith(spy, arg1, arg2, ...)
Срабатывает, если шпиона никогда не вызывали с указанными аргументами.
sinon.assert.calledWithExactly(spy, arg1, arg2, ...)
Срабатывает, если шпиона вызывали с точно указанными аргументами и никакими другими.
sinon.assert.alwaysCalledWithExactly(spy, arg1, arg2, ...)
Срабатывает, если шпиона всегда вызывали с точно указанными аргументами и никакими другими.
sinon.assert.calledWithMatch(spy, arg1, arg2, ...)
Срабатывает, если шпион вызывается с соответствующими аргументами. Ведет себя точно так же как sinon.assert.calledWith(spy, sinon.match(arg1), sinon.match(arg2), ...).
sinon.assert.alwaysCalledWithMatch(spy, arg1, arg2, ...)
Срабатывает, если шпион всегда вызывается с соответствующими аргументами. Ведет себя точно так же как sinon.assert.alwaysCalledWith(spy, sinon.match(arg1), sinon.match(arg2), ...).
sinon.assert.neverCalledWithMatch(spy, arg1, arg2, ...)
Срабатывает, если шпион никогда не вызывался с соответствующими аргументами. Ведет себя точно так же как sinon.assert.neverCalledWith(spy, sinon.match(arg1), sinon.match(arg2), ...).
sinon.assert.threw(spy, exception)
Срабатывает, если шпион выдавал указанное исключение (ошибку). Исключение может быть строкой, обозначающей его тип, или фактический объект. Утверждение сработает, если указан хотя бы один параметр и шпион хотя бы раз выдавал исключение.
sinon.assert.alwaysThrew(spy, exception)
Так же как и упомянутое выше, но только если шпион всегда выдавал указанное исключение.
sinon.assert.expose(object, options)
Представляет утверждение в виде указанного объекта для более просто интеграции с тестовой платформой. Например, JsTestDriver использует глобальные утверждения, чтобы сделать утверждения Sinon.JS совместимыми с ним Вы можете сделать:

sinon.assert.expose(this); 

Предоставит Вам assertCalled(spy),assertCallOrder(spy1, spy2, …) и т.д.

Метод применяет вышеуказанные необязательные варианты объекта в двух вариантах. Prefix — префикс, чтобы предоставить утверждение. По умолчанию это assert, таким образом sinon.assert.called становится target.assertCalled. При передаче пустой строки, представленный метод будет target.called. Вторая опция, includeFail является истиной по умолчанию и копирует сбои и свойства failException.

Matchers

Вычислители могут быть переданы в качестве аргументов spy.calledWith, spy.returned и соответствующим функциям sinon.assert и spy.withArgs.
Вычислители могут предоставлять как обобщенную, так и более конкретизированную информацию про ожидаемое значение.

"test should assert fuzzy": function () {     var book = {         pages: 42,         author: "cjno"     };     var spy = sinon.spy();      spy(book);      sinon.assert.calledWith(spy, sinon.match({ author: "cjno" }));     sinon.assert.calledWith(spy, sinon.match.has("pages", 42)); } 

"test should stub method differently based on argument types": function () {     var callback = sinon.stub();     callback.withArgs(sinon.match.string).returns(true);     callback.withArgs(sinon.match.number).throws("TypeError");      callback("abc"); // Returns true     callback(123); // Throws TypeError } 

Matchers API

sinon.match(number)
Требует, чтобы значение было равно указанному числу.
sinon.match(string)
Требует, чтобы значение было последовательностью и ожидание было представлено подстрокой.
sinon.match(regexp)
Требует, чтобы значение было последовательностью и соответствовало указанному регулярному выражению.
sinon.match(object)
Требует, чтобы значение было не нулем или неопределенный но имело те же свойства которые ожидались. Поддерживает вставки вычислителей.
sinon.match(function)
Смотрите пользовательские вычислители.
sinon.match.any
Соответствие чему либо.
sinon.match.defined
Требует, чтобы значение было определено.
sinon.match.truthy
Требует, чтобы значение было правдивым.
sinon.match.falsy
Требует, чтобы значение было фальшивым.
sinon.match.bool
Требует, чтобы значение было логической переменной.
sinon.match.number
Требует, чтобы значение было числом.
sinon.match.string
Требует чтобы значение было последовательностью.
sinon.match.object
Требует чтобы значение было объектом.
sinon.match.func
Требует, чтобы значение было функцией.
sinon.match.array
Требует, чтобы значение было массивом.
sinon.match.regexp
Требует, чтобы значение было регулярным выражением.
sinon.match.date
Требует, чтобы значение было объектом даты.
sinon.match.same(ref)
Требует значение, которое строго равно ref
sinon.match.typeOf(type)
Требует, чтобы значение имело указанный тип, где тип может быть одним из undefined, null, boolean, number, string, object, function, array, regexp или date.
sinon.match.instanceOf(type)
Требует, чтобы значение было экземпляром указанного типа.
sinon.match.has(property[, expectation])
Требуется значение, чтобы определило данное свойство. Свойства могут быть унаследованы с помощью цепочки прототипов.
sinon.match.hasOwn(property[, expectation])
Действует так же как и sinon.match.has, но свойство должно быть определено самим значением. Унаследованные свойства игнорируются.

Сочетание вычислителей

Все реализации вычислителей «и» и «или». Позволяет логически объединить несколько вычислителей. В результате новый вычислителей, который требует обоих (и) или один из вычислителей (или) возвращать подтверждение.

var stringOrNumber = sinon.match.string.or(sinon.match.number);  var bookWithPages = sinon.match.instanceOf(Book).and(sinon.match.has("pages")); 

Пользовательские вычислители

Пользовательские вычислители создаются с sinon.match фактором, который принимает тестовую функцию и необязательное сообщение. Тестовая функция принимает значение как единственный параметр, возвращает true, если значение соответствует ожиданию и ‘false’ — если иначе. Последовательные сообщения используется, чтобы генерировать сообщение об ошибке в случае, когда значение не соответствует ожиданию.

var trueIsh = sinon.match(function (value) {     return !!value; }, "trueIsh"); 

Sandboxes

Песочницы упрощают работу с имитациями, которые должны быть восстановлены и/или проверены. Если Вы используете поддельные таймеры, фальсифицируете XHR, или Вы используете заглушки/шпионы на глобально доступных свойствах, Вам лучше использовать песочницу, чтобы упростить очистку.

"test using sinon.test sandbox": sinon.test(function () {     var myAPI = { method: function () {} };     this.mock(myAPI).expects("method").once();      PubSub.subscribe("message", myAPI.method);     PubSub.publishSync("message", undefined); }) 

Sandbox API

var sandbox = sinon.sandbox.create();
Создает объект sandbox.
var sandbox = sinon.sandbox.create(config);
‘sinon.sandbox.create (config)’ метод — главным образом функция интеграции, и как конечному пользователю Sinon.JS Вам, вероятно, он не будет нужен.

Создает предварительно сконфигурированный объект sandbox. Конфигурация может дать sandbox команду включать поддельные таймеры, поддельный сервер, и команды по взаимодействию с ними. Конфигурация по умолчанию выглядит приблизительно так:

sinon.defaultConfig = {     // ...     injectInto: null,     properties: ["spy", "stub", "mock", "clock", "server", "requests"],     useFakeTimers: true,     useFakeServer: true } 

  • injectInto
    Методы sandbox могут быть введены в другой объект для удобства использования. Конфигурационная опция «injectInto» может добавлять свойства к указанному объекту. Как правило для `sinon.test` устанавливается таким образом, что он является` this` значением в данной тестовой функции.
  • properties
    Какие свойства ввести. Обратите внимание на то, что просто именования «server» здесь не достаточно. Чтобы свойство ‘сервера’, обнаруживались в целевом объекте, Вам также нужно установить ‘useFakeServer’ для подтверждения.
  • useFakeTimers
    Если ‘true’, у sandbox будет свойство ‘часов’. Может также быть массив свойств таймера для имитации.
  • useFakeServer
    Если `true`, `server` и свойства `requests` добавлены к sandbox. Может также быть объектом для использования поддельного сервера. Значение по умолчанию — ‘sinon.fakeServer’, но если Вы используете jQuery 1.3.x или какую то другую библиотеку, которая не устанавливает ‘onreadystatechange’ обработчик XHR по молчанию, Вы можете сделать следующее:
    sinon.config = {     useFakeServer: sinon.fakeServerWithClock }; 

sandbox.spy();
Работает точно как sinon.spy, только также добавляет возвращенного шпиона к внутреннему набору имитаций для простого восстановления через sandbox.restore ().
sandbox.stub();
Работает почти точно как sinon.stub, только также добавляет возвращенные заглушки к внутреннему набору фальшивок для простого восстановления через sandbox.restore (). Метод sandbox stub может также использоваться, чтобы заглушить свойства любого вида. Это полезно, если Вы хотите переопределить свойство объекта на время теста, и восстановить свойства по умолчанию когда тест будет завершен.
sandbox.mock();
Работает точно как sinon.mock, только также добавляет возвращенную имитацию к внутреннему набору фальшивок для простого восстановления через sandbox.restore ().
sandbox.useFakeTimers();
Связывают объект часов с sandbox, таким образом, чтобы можно было быстро востановить при вызове sandbox.restore (). Доступ через sandbox.clock.
sandbox.useFakeXMLHttpRequest();
Имитирует XHR и связывает полученный объект с sandbox, таким образом, чтобы можно было быстро восстановить при вызове sandbox.restore (). Доступ запрашивает через sandbox.requests.
sandbox.useFakeServer();
Имитирует XHR и связывает серверный объект с sandbox, таким образом, чтобы можно было быстро восстановить при вызове sandbox.restore (). Доступ запрашивает через sandbox.requests и сервер запрашивается через sandbox.server.
sandbox.restore();
Восстановления все имитации созданы через sandbox.

Метод тестирования

Метод обертывания испытаний в sinon.test позволяет Sinon.JS автоматически создавать и управлять sandbox. Поведение функции может быть сконфигурировано через sinon.config.
var wrappedFn = sinon.test(fn);

Работает функцыя wrappedFn точно так же как исходная. Кроме того, объект sandbox создается и автоматически восстанавливается, когда функция заканчивает вызов. По умолчанию spy, stub и mock свойства sandbox связаны с любым объектом на функции, таким образом, Вы можете сделать this.spy () (stub и mock), и это будет работать точно так же как sandbox.spy () (stub и mock), так же не требует ручного восстановления.

{     injectIntoThis: true,     injectInto: null,     properties: ["spy", "stub", "mock", "clock", "server", "requests"],     useFakeTimers: true,     useFakeServer: true } 

Просто установите sinon.config, чтобы переопределить любые из них, например:

sinon.config = {     useFakeTimers: false,     useFakeServer: false } 

В таком случае значения по умолчанию используются для несуществующих свойств. Кроме того, sandbox и тесты не будут иметь автоматического доступа к поддельным таймерам и поддельному серверу при использовании этой конфигурации.

sinon.config

Конфигурация управляет тем как Sinon связывает свойства при использовании sinon.test. Конфигурация по умолчанию похожа на:

Boolean injectIntoThis
Вызывает свойство, которое будет введено в этом объекте для тестирования. По умолчанию подтверждено.
Object injectInto
Объект, чтобы связать свойства. Если это null (по умолчанию) и injectIntoThis false (не по умолчанию), свойства передаются в качестве аргументов для функции тестирования.
Array properties
Свойства для предоставления. Значение по умолчанию — все: [spy, stub, mock, clock, server, requests]. Однако последние три свойства предоставляются, если следующие два параметра конфигурации — подтверждены.
Boolean useFakeTimers
Выбирает таймеры, которые будут имитироваться и позволяют свойству часов быть представленным. По умолчанию всегда подтверждено.
Boolean useFakeServer
Имитирует XHR и сервер, который будет создаваться и позволяет серверу и запрашиваемым свойствам быть представленными. Значение по умолчанию — подтверждено.

Тестовый сценарий

Если Вам нужно более одного метода sinon.test для одного тестирования, Вы можете использовать sinon.testCase, который ведет себя так же, как упаковка для каждого теста в sinon.test с одним исключением:` Настройки setUp и tearDown могут совместно использовать имитации.

var obj = sinon.testCase({});

Sinon.JS утилиты

Sinon.JS имеет несколько утилит, используемых внутренне. Если рассматриваемый метод не задокументирован здесь, его нельзя считать частью общедоступного API.

Утилиты API

sinon.createStubInstance(constructor)
Создает новый объект с заданной функцией, как прототип и для всех реализованных функций. Указанная функция конструктора не вызывается. Смотрите так же API заглушки.
sinon.format(object)
Форматирует объект для адекватной читаемости в сообщениях об ошибке. Метод должен возвратить последовательность.
sinon.log(string)
Регистрирует внутренние ошибки, полезные для отладки.

Исходный код для установки, Вы можете найти перейдя по ссылке или в спойлере представленном ниже.

Исходный код.

(function (root, factory) {
‘use strict’;
if (typeof define === ‘function’ && define.amd) {
define(‘sinon’, [], function () {
return (root.sinon = factory());
});
} else if (typeof exports === ‘object’) {
module.exports = factory();
} else {
root.sinon = factory();
}
}(this, function () {
‘use strict’;
var samsam, formatio, lolex;
(function () {
function define(mod, deps, fn) {
if (mod == «samsam») {
samsam = deps();
} else if (typeof deps === «function» && mod.length === 0) {
lolex = deps();
} else if (typeof fn === «function») {
formatio = fn(samsam);
}
}
define.amd = {};
((typeof define === «function» && define.amd && function (m) { define(«samsam», m); }) ||
(typeof module === «object» &&
function (m) { module.exports = m(); }) || // Node
function (m) { this.samsam = m(); } // Browser globals
)(function () {
var o = Object.prototype;
var div = typeof document !== «undefined» && document.createElement(«div»);

function isNaN(value) {
// Unlike global isNaN, this avoids type coercion
// typeof check avoids IE host object issues, hat tip to
// lodash
var val = value; // JsLint thinks value !== value is «weird»
return typeof value === «number» && value !== val;
}

function getClass(value) {
// Returns the internal [[Class]] by calling Object.prototype.toString
// with the provided value as this. Return value is a string, naming the
// internal class, e.g. «Array»
return o.toString.call(value).split(/[ \]]/)[1];
}

/**
* name samsam.isArguments
* param Object object
*
* Returns «true« if «object« is an «arguments« object,
* «false« otherwise.
*/
function isArguments(object) {
if (getClass(object) === ‘Arguments’) { return true; }
if (typeof object !== «object» || typeof object.length !== «number» ||
getClass(object) === «Array») {
return false;
}
if (typeof object.callee == «function») { return true; }
try {
object[object.length] = 6;
delete object[object.length];
} catch (e) {
return true;
}
return false;
}

/**
* name samsam.isElement
* param Object object
*
* Returns «true« if «object« is a DOM element node. Unlike
* Underscore.js/lodash, this function will return «false« if «object«
* is an *element-like* object, i.e. a regular object with a «nodeType«
* property that holds the value «1«.
*/
function isElement(object) {
if (!object || object.nodeType !== 1 || !div) { return false; }
try {
object.appendChild(div);
object.removeChild(div);
} catch (e) {
return false;
}
return true;
}

/**
* name samsam.keys
* param Object object
*
* Return an array of own property names.
*/
function keys(object) {
var ks = [], prop;
for (prop in object) {
if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }
}
return ks;
}

/**
* name samsam.isDate
* param Object value
*
* Returns true if the object is a «Date«, or *date-like*. Duck typing
* of date objects work by checking that the object has a «getTime«
* function whose return value equals the return value from the object’s
* «valueOf«.
*/
function isDate(value) {
return typeof value.getTime == «function» &&
value.getTime() == value.valueOf();
}

/**
* name samsam.isNegZero
* param Object value
*
* Returns «true« if «value« is «-0«.
*/
function isNegZero(value) {
return value === 0 && 1 / value === -Infinity;
}

/**
* name samsam.equal
* param Object obj1
* param Object obj2
*
* Returns «true« if two objects are strictly equal. Compared to
* «===« there are two exceptions:
*
* — NaN is considered equal to NaN
* — -0 and +0 are not considered equal
*/
function identical(obj1, obj2) {
if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
}
}

/**
* name samsam.deepEqual
* param Object obj1
* param Object obj2
*
* Deep equal comparison. Two values are «deep equal» if:
*
* — They are equal, according to samsam.identical
* — They are both date objects representing the same time
* — They are both arrays containing elements that are all deepEqual
* — They are objects with the same set of properties, and each property
* in «obj1« is deepEqual to the corresponding property in «obj2«
*
* Supports cyclic objects.
*/
function deepEqualCyclic(obj1, obj2) {

// used for cyclic comparison
// contain already visited objects
var objects1 = [],
objects2 = [],
// contain pathes (position in the object structure)
// of the already visited objects
// indexes same as in objects arrays
paths1 = [],
paths2 = [],
// contains combinations of already compared objects
// in the manner: { "$1[‘ref’]$2[‘ref’]": true }
compared = {};

/**
* used to check, if the value of a property is an object
* (cyclic logic is only needed for objects)
* only needed for cyclic logic
*/
function isObject(value) {

if (typeof value === ‘object’ && value !== null &&
!(value instanceof Boolean) &&
!(value instanceof Date) &&
!(value instanceof Number) &&
!(value instanceof RegExp) &&
!(value instanceof String)) {

return true;
}

return false;
}

/**
* returns the index of the given object in the
* given objects array, -1 if not contained
* only needed for cyclic logic
*/
function getIndex(objects, obj) {

var i;
for (i = 0; i < objects.length; i++) {
if (objects[i] === obj) {
return i;
}
}

return -1;
}

// does the recursion for the deep equal check
return (function deepEqual(obj1, obj2, path1, path2) {
var type1 = typeof obj1;
var type2 = typeof obj2;

// == null also matches undefined
if (obj1 === obj2 ||
isNaN(obj1) || isNaN(obj2) ||
obj1 == null || obj2 == null ||
type1 !== «object» || type2 !== «object») {

return identical(obj1, obj2);
}

// Elements are only equal if identical(expected, actual)
if (isElement(obj1) || isElement(obj2)) { return false; }

var isDate1 = isDate(obj1), isDate2 = isDate(obj2);
if (isDate1 || isDate2) {
if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
return false;
}
}

if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
if (obj1.toString() !== obj2.toString()) { return false; }
}

var class1 = getClass(obj1);
var class2 = getClass(obj2);
var keys1 = keys(obj1);
var keys2 = keys(obj2);

if (isArguments(obj1) || isArguments(obj2)) {
if (obj1.length !== obj2.length) { return false; }
} else {
if (type1 !== type2 || class1 !== class2 ||
keys1.length !== keys2.length) {
return false;
}
}

var key, i, l,
// following vars are used for the cyclic logic
value1, value2,
isObject1, isObject2,
index1, index2,
newPath1, newPath2;

for (i = 0, l = keys1.length; i < l; i++) {
key = keys1[i];
if (!o.hasOwnProperty.call(obj2, key)) {
return false;
}

// Start of the cyclic logic

value1 = obj1[key];
value2 = obj2[key];

isObject1 = isObject(value1);
isObject2 = isObject(value2);

// determine, if the objects were already visited
// (it’s faster to check for isObject first, than to
// get -1 from getIndex for non objects)
index1 = isObject1? getIndex(objects1, value1): -1;
index2 = isObject2? getIndex(objects2, value2): -1;

// determine the new pathes of the objects
// — for non cyclic objects the current path will be extended
// by current property name
// — for cyclic objects the stored path is taken
newPath1 = index1 !== -1
? paths1[index1]
: path1 + ‘[‘ + JSON.stringify(key) + ‘]’;
newPath2 = index2 !== -1
? paths2[index2]
: path2 + ‘[‘ + JSON.stringify(key) + ‘]’;

// stop recursion if current objects are already compared
if (compared[newPath1 + newPath2]) {
return true;
}

// remember the current objects and their pathes
if (index1 === -1 && isObject1) {
objects1.push(value1);
paths1.push(newPath1);
}
if (index2 === -1 && isObject2) {
objects2.push(value2);
paths2.push(newPath2);
}

// remember that the current objects are already compared
if (isObject1 && isObject2) {
compared[newPath1 + newPath2] = true;
}

// End of cyclic logic

// neither value1 nor value2 is a cycle
// continue with next level
if (!deepEqual(value1, value2, newPath1, newPath2)) {
return false;
}
}

return true;

}(obj1, obj2, ‘$1’, ‘$2’));
}

var match;

function arrayContains(array, subset) {
if (subset.length === 0) { return true; }
var i, l, j, k;
for (i = 0, l = array.length; i < l; ++i) {
if (match(array[i], subset[0])) {
for (j = 0, k = subset.length; j < k; ++j) {
if (!match(array[i + j], subset[j])) { return false; }
}
return true;
}
}
return false;
}

/**
* name samsam.match
* param Object object
* param Object matcher
*
* Compare arbitrary value «object« with matcher.
*/
match = function match(object, matcher) {
if (matcher && typeof matcher.test === «function») {
return matcher.test(object);
}

if (typeof matcher === «function») {
return matcher(object) === true;
}

if (typeof matcher === «string») {
matcher = matcher.toLowerCase();
var notNull = typeof object === «string» || !!object;
return notNull &&
(String(object)).toLowerCase().indexOf(matcher) >= 0;
}

if (typeof matcher === «number») {
return matcher === object;
}

if (typeof matcher === «boolean») {
return matcher === object;
}

if (typeof(matcher) === «undefined») {
return typeof(object) === «undefined»;
}

if (matcher === null) {
return object === null;
}

if (getClass(object) === «Array» && getClass(matcher) === «Array») {
return arrayContains(object, matcher);
}

if (matcher && typeof matcher === «object») {
if (matcher === object) {
return true;
}
var prop;
for (prop in matcher) {
var value = object[prop];
if (typeof value === «undefined» &&
typeof object.getAttribute === «function») {
value = object.getAttribute(prop);
}
if (matcher[prop] === null || typeof matcher[prop] === ‘undefined’) {
if (value !== matcher[prop]) {
return false;
}
} else if (typeof value === «undefined» || !match(value, matcher[prop])) {
return false;
}
}
return true;
}

throw new Error(«Matcher was not a string, a number, a » +
«function, a boolean or an object»);
};

return {
isArguments: isArguments,
isElement: isElement,
isDate: isDate,
isNegZero: isNegZero,
identical: identical,
deepEqual: deepEqualCyclic,
match: match,
keys: keys
};
});
((typeof define === «function» && define.amd && function (m) {
define(«formatio», [«samsam»], m);
}) || (typeof module === «object» && function (m) {
module.exports = m(require(«samsam»));
}) || function (m) { this.formatio = m(this.samsam); }
)(function (samsam) {

var formatio = {
excludeConstructors: [«Object», /^.$/],
quoteStrings: true,
limitChildrenCount: 0
};

var hasOwn = Object.prototype.hasOwnProperty;

var specialObjects = [];
if (typeof global !== «undefined») {
specialObjects.push({ object: global, value: "[object global]" });
}
if (typeof document !== «undefined») {
specialObjects.push({
object: document,
value: "[object HTMLDocument]"
});
}
if (typeof window !== «undefined») {
specialObjects.push({ object: window, value: "[object Window]" });
}

function functionName(func) {
if (!func) { return ""; }
if (func.displayName) { return func.displayName; }
if (func.name) { return func.name; }
var matches = func.toString().match(/function\s+([^\(]+)/m);
return (matches && matches[1]) || "";
}

function constructorName(f, object) {
var name = functionName(object && object.constructor);
var excludes = f.excludeConstructors ||
formatio.excludeConstructors || [];

var i, l;
for (i = 0, l = excludes.length; i < l; ++i) {
if (typeof excludes[i] === «string» && excludes[i] === name) {
return "";
} else if (excludes[i].test && excludes[i].test(name)) {
return "";
}
}

return name;
}

function isCircular(object, objects) {
if (typeof object !== «object») { return false; }
var i, l;
for (i = 0, l = objects.length; i < l; ++i) {
if (objects[i] === object) { return true; }
}
return false;
}

function ascii(f, object, processed, indent) {
if (typeof object === «string») {
var qs = f.quoteStrings;
var quote = typeof qs !== «boolean» || qs;
return processed || quote? ‘"’ + object + ‘"’: object;
}

if (typeof object === «function» && !(object instanceof RegExp)) {
return ascii.func(object);
}

processed = processed || [];

if (isCircular(object, processed)) { return "[Circular]"; }

if (Object.prototype.toString.call(object) === "[object Array]") {
return ascii.array.call(f, object, processed);
}

if (!object) { return String((1/object) === -Infinity? "-0": object); }
if (samsam.isElement(object)) { return ascii.element(object); }

if (typeof object.toString === «function» &&
object.toString !== Object.prototype.toString) {
return object.toString();
}

var i, l;
for (i = 0, l = specialObjects.length; i < l; i++) {
if (object === specialObjects[i].object) {
return specialObjects[i].value;
}
}

return ascii.object.call(f, object, processed, indent);
}

ascii.func = function (func) {
return «function » + functionName(func) + "() {}";
};

ascii.array = function (array, processed) {
processed = processed || [];
processed.push(array);
var pieces = [];
var i, l;
l = (this.limitChildrenCount > 0)?
Math.min(this.limitChildrenCount, array.length): array.length;

for (i = 0; i < l; ++i) {
pieces.push(ascii(this, array[i], processed));
}

if(l < array.length)
pieces.push("[… " + (array.length — l) + " more elements]");

return "[" + pieces.join(", ") + "]";
};

ascii.object = function (object, processed, indent) {
processed = processed || [];
processed.push(object);
indent = indent || 0;
var pieces = [], properties = samsam.keys(object).sort();
var length = 3;
var prop, str, obj, i, k, l;
l = (this.limitChildrenCount > 0)?
Math.min(this.limitChildrenCount, properties.length): properties.length;

for (i = 0; i < l; ++i) {
prop = properties[i];
obj = object[prop];

if (isCircular(obj, processed)) {
str = "[Circular]";
} else {
str = ascii(this, obj, processed, indent + 2);
}

str = (/\s/.test(prop)? ‘"’ + prop + ‘"’: prop) + ": " + str;
length += str.length;
pieces.push(str);
}

var cons = constructorName(this, object);
var prefix = cons? "[" + cons + "] ": "";
var is = "";
for (i = 0, k = indent; i < k; ++i) { is += " "; }

if(l < properties.length)
pieces.push("[… " + (properties.length — l) + " more elements]");

if (length + indent > 80) {
return prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" +
is + "}";
}
return prefix + "{ " + pieces.join(", ") + " }";
};

ascii.element = function (element) {
var tagName = element.tagName.toLowerCase();
var attrs = element.attributes, attr, pairs = [], attrName, i, l, val;

for (i = 0, l = attrs.length; i < l; ++i) {
attr = attrs.item(i);
attrName = attr.nodeName.toLowerCase().replace(«html:», "");
val = attr.nodeValue;
if (attrName !== «contenteditable» || val !== «inherit») {
if (!!val) { pairs.push(attrName + "=\"" + val + "\""); }
}
}

var formatted = "<" + tagName + (pairs.length > 0? " ": "");
var content = element.innerHTML;

if (content.length > 20) {
content = content.substr(0, 20) + "[…]";
}

var res = formatted + pairs.join(" ") + ">" + content +
"</" + tagName + ">";

return res.replace(/ contentEditable=«inherit»/, "");
};

function Formatio(options) {
for (var opt in options) {
this[opt] = options[opt];
}
}

Formatio.prototype = {
functionName: functionName,

configure: function (options) {
return new Formatio(options);
},

constructorName: function (object) {
return constructorName(this, object);
},

ascii: function (object, processed, indent) {
return ascii(this, object, processed, indent);
}
};

return Formatio.prototype;
});
!function(e){if(«object»==typeof exports&&«undefined»!=typeof module)module.exports=e();else if(«function»==typeof define&&define.amd)define([],e);else{var f;«undefined»!=typeof window?f=window:«undefined»!=typeof global?f=global:«undefined»!=typeof self&&(f=self),f.lolex=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==«function»&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(«Cannot find module ‘»+o+"’");throw f.code=«MODULE_NOT_FOUND»,f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==«function»&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
/*global global, window*/
/**
* author Christian Johansen (christian@cjohansen.no) and contributors
* @license BSD
*
* Copyright 2010-2014 Christian Johansen
*/

(function (global) {

// Make properties writable in IE, as per
// www.adequatelygood.com/Replacing-setTimeout-Globally.html
// JSLint being anal
var glbl = global;

global.setTimeout = glbl.setTimeout;
global.clearTimeout = glbl.clearTimeout;
global.setInterval = glbl.setInterval;
global.clearInterval = glbl.clearInterval;
global.Date = glbl.Date;

// setImmediate is not a standard function
// avoid adding the prop to the window object if not present
if(‘setImmediate’ in global) {
global.setImmediate = glbl.setImmediate;
global.clearImmediate = glbl.clearImmediate;
}

// node expects setTimeout/setInterval to return a fn object w/ .ref()/.unref()
// browsers, a number.
// see github.com/cjohansen/Sinon.JS/pull/436

var NOOP = function () { return undefined; };
var timeoutResult = setTimeout(NOOP, 0);
var addTimerReturnsObject = typeof timeoutResult === «object»;
clearTimeout(timeoutResult);

var NativeDate = Date;
var uniqueTimerId = 1;

/**
* Parse strings like «01:10:00» (meaning 1 hour, 10 minutes, 0 seconds) into
* number of milliseconds. This is used to support human-readable strings passed
* to clock.tick()
*/
function parseTime(str) {
if (!str) {
return 0;
}

var strings = str.split(":");
var l = strings.length, i = l;
var ms = 0, parsed;

if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
throw new Error(«tick only understands numbers and ‘h:m:s’»);
}

while (i—) {
parsed = parseInt(strings[i], 10);

if (parsed >= 60) {
throw new Error(«Invalid time » + str);
}

ms += parsed * Math.pow(60, (l — i — 1));
}

return ms * 1000;
}

/**
* Used to grok the `now` parameter to createClock.
*/
function getEpoch(epoch) {
if (!epoch) { return 0; }
if (typeof epoch.getTime === «function») { return epoch.getTime(); }
if (typeof epoch === «number») { return epoch; }
throw new TypeError(«now should be milliseconds since UNIX epoch»);
}

function inRange(from, to, timer) {
return timer && timer.callAt >= from && timer.callAt <= to;
}

function mirrorDateProperties(target, source) {
var prop;
for (prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}

// set special now implementation
if (source.now) {
target.now = function now() {
return target.clock.now;
};
} else {
delete target.now;
}

// set special toSource implementation
if (source.toSource) {
target.toSource = function toSource() {
return source.toSource();
};
} else {
delete target.toSource;
}

// set special toString implementation
target.toString = function toString() {
return source.toString();
};

target.prototype = source.prototype;
target.parse = source.parse;
target.UTC = source.UTC;
target.prototype.toUTCString = source.prototype.toUTCString;

return target;
}

function createDate() {
function ClockDate(year, month, date, hour, minute, second, ms) {
// Defensive and verbose to avoid potential harm in passing
// explicit undefined when user does not pass argument
switch (arguments.length) {
case 0:
return new NativeDate(ClockDate.clock.now);
case 1:
return new NativeDate(year);
case 2:
return new NativeDate(year, month);
case 3:
return new NativeDate(year, month, date);
case 4:
return new NativeDate(year, month, date, hour);
case 5:
return new NativeDate(year, month, date, hour, minute);
case 6:
return new NativeDate(year, month, date, hour, minute, second);
default:
return new NativeDate(year, month, date, hour, minute, second, ms);
}
}

return mirrorDateProperties(ClockDate, NativeDate);
}

function addTimer(clock, timer) {
if (timer.func === undefined) {
throw new Error(«Callback must be provided to timer calls»);
}

if (!clock.timers) {
clock.timers = {};
}

timer.id = uniqueTimerId++;
timer.createdAt = clock.now;
timer.callAt = clock.now + (timer.delay || (clock.duringTick? 1: 0));

clock.timers[timer.id] = timer;

if (addTimerReturnsObject) {
return {
id: timer.id,
ref: NOOP,
unref: NOOP
};
}

return timer.id;
}

function compareTimers(a, b) {
// Sort first by absolute timing
if (a.callAt < b.callAt) {
return -1;
}
if (a.callAt > b.callAt) {
return 1;
}

// Sort next by immediate, immediate timers take precedence
if (a.immediate && !b.immediate) {
return -1;
}
if (!a.immediate && b.immediate) {
return 1;
}

// Sort next by creation time, earlier-created timers take precedence
if (a.createdAt < b.createdAt) {
return -1;
}
if (a.createdAt > b.createdAt) {
return 1;
}

// Sort next by id, lower-id timers take precedence
if (a.id < b.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}

// As timer ids are unique, no fallback `0` is necessary
}

function firstTimerInRange(clock, from, to) {
var timers = clock.timers,
timer = null,
id,
isInRange;

for (id in timers) {
if (timers.hasOwnProperty(id)) {
isInRange = inRange(from, to, timers[id]);

if (isInRange && (!timer || compareTimers(timer, timers[id]) === 1)) {
timer = timers[id];
}
}
}

return timer;
}

function callTimer(clock, timer) {
var exception;

if (typeof timer.interval === «number») {
clock.timers[timer.id].callAt += timer.interval;
} else {
delete clock.timers[timer.id];
}

try {
if (typeof timer.func === «function») {
timer.func.apply(null, timer.args);
} else {
eval(timer.func);
}
} catch (e) {
exception = e;
}

if (!clock.timers[timer.id]) {
if (exception) {
throw exception;
}
return;
}

if (exception) {
throw exception;
}
}

function timerType(timer) {
if (timer.immediate) {
return «Immediate»;
} else if (typeof timer.interval !== «undefined») {
return «Interval»;
} else {
return «Timeout»;
}
}

function clearTimer(clock, timerId, ttype) {
if (!timerId) {
// null appears to be allowed in most browsers, and appears to be
// relied upon by some libraries, like Bootstrap carousel
return;
}

if (!clock.timers) {
clock.timers = [];
}

// in Node, timerId is an object with .ref()/.unref(), and
// its .id field is the actual timer id.
if (typeof timerId === «object») {
timerId = timerId.id;
}

if (clock.timers.hasOwnProperty(timerId)) {
// check that the ID matches a timer of the correct type
var timer = clock.timers[timerId];
if (timerType(timer) === ttype) {
delete clock.timers[timerId];
} else {
throw new Error(«Cannot clear timer: timer created with set» + ttype + "() but cleared with clear" + timerType(timer) + "()");
}
}
}

function uninstall(clock, target) {
var method,
i,
l;

for (i = 0, l = clock.methods.length; i < l; i++) {
method = clock.methods[i];

if (target[method].hadOwnProperty) {
target[method] = clock["_" + method];
} else {
try {
delete target[method];
} catch (ignore) {}
}
}

// Prevent multiple executions which will completely remove these props
clock.methods = [];
}

function hijackMethod(target, method, clock) {
var prop;

clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(target, method);
clock["_" + method] = target[method];

if (method === «Date») {
var date = mirrorDateProperties(clock[method], target[method]);
target[method] = date;
} else {
target[method] = function () {
return clock[method].apply(clock, arguments);
};

for (prop in clock[method]) {
if (clock[method].hasOwnProperty(prop)) {
target[method][prop] = clock[method][prop];
}
}
}

target[method].clock = clock;
}

var timers = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setImmediate: global.setImmediate,
clearImmediate: global.clearImmediate,
setInterval: setInterval,
clearInterval: clearInterval,
Date: Date
};

var keys = Object.keys || function (obj) {
var ks = [],
key;

for (key in obj) {
if (obj.hasOwnProperty(key)) {
ks.push(key);
}
}

return ks;
};

exports.timers = timers;

function createClock(now) {
var clock = {
now: getEpoch(now),
timeouts: {},
Date: createDate()
};

clock.Date.clock = clock;

clock.setTimeout = function setTimeout(func, timeout) {
return addTimer(clock, {
func: func,
args: Array.prototype.slice.call(arguments, 2),
delay: timeout
});
};

clock.clearTimeout = function clearTimeout(timerId) {
return clearTimer(clock, timerId, «Timeout»);
};

clock.setInterval = function setInterval(func, timeout) {
return addTimer(clock, {
func: func,
args: Array.prototype.slice.call(arguments, 2),
delay: timeout,
interval: timeout
});
};

clock.clearInterval = function clearInterval(timerId) {
return clearTimer(clock, timerId, «Interval»);
};

clock.setImmediate = function setImmediate(func) {
return addTimer(clock, {
func: func,
args: Array.prototype.slice.call(arguments, 1),
immediate: true
});
};

clock.clearImmediate = function clearImmediate(timerId) {
return clearTimer(clock, timerId, «Immediate»);
};

clock.tick = function tick(ms) {
ms = typeof ms === «number»? ms: parseTime(ms);
var tickFrom = clock.now, tickTo = clock.now + ms, previous = clock.now;
var timer = firstTimerInRange(clock, tickFrom, tickTo);
var oldNow;

clock.duringTick = true;

var firstException;
while (timer && tickFrom <= tickTo) {
if (clock.timers[timer.id]) {
tickFrom = clock.now = timer.callAt;
try {
oldNow = clock.now;
callTimer(clock, timer);
// compensate for any setSystemTime() call during timer callback
if (oldNow !== clock.now) {
tickFrom += clock.now — oldNow;
tickTo += clock.now — oldNow;
previous += clock.now — oldNow;
}
} catch (e) {
firstException = firstException || e;
}
}

timer = firstTimerInRange(clock, previous, tickTo);
previous = tickFrom;
}

clock.duringTick = false;
clock.now = tickTo;

if (firstException) {
throw firstException;
}

return clock.now;
};

clock.reset = function reset() {
clock.timers = {};
};

clock.setSystemTime = function setSystemTime(now) {
// determine time difference
var newNow = getEpoch(now);
var difference = newNow — clock.now;

// update ‘system clock’
clock.now = newNow;

// update timers and intervals to keep them stable
for (var id in clock.timers) {
if (clock.timers.hasOwnProperty(id)) {
var timer = clock.timers[id];
timer.createdAt += difference;
timer.callAt += difference;
}
}
};

return clock;
}
exports.createClock = createClock;

exports.install = function install(target, now, toFake) {
var i,
l;

if (typeof target === «number») {
toFake = now;
now = target;
target = null;
}

if (!target) {
target = global;
}

var clock = createClock(now);

clock.uninstall = function () {
uninstall(clock, target);
};

clock.methods = toFake || [];

if (clock.methods.length === 0) {
clock.methods = keys(timers);
}

for (i = 0, l = clock.methods.length; i < l; i++) {
hijackMethod(target, clock.methods[i], clock);
}

return clock;
};

}(global || this));

}).call(this,typeof global !== «undefined»? global: typeof self !== «undefined»? self: typeof window !== «undefined»? window: {})
},{}]},{},[1])(1)
});
})();
var define;
/**
* Sinon core utilities. For internal use only.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
var sinon = (function () {
«use strict»;
// eslint-disable-line no-unused-vars

var sinonModule;
var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
sinonModule = module.exports = require("./sinon/util/core");
require("./sinon/extend");
require("./sinon/walk");
require("./sinon/typeOf");
require("./sinon/times_in_words");
require("./sinon/spy");
require("./sinon/call");
require("./sinon/behavior");
require("./sinon/stub");
require("./sinon/mock");
require("./sinon/collection");
require("./sinon/assert");
require("./sinon/sandbox");
require("./sinon/test");
require("./sinon/test_case");
require("./sinon/match");
require("./sinon/format");
require("./sinon/log_error");
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
sinonModule = module.exports;
} else {
sinonModule = {};
}

return sinonModule;
}());

/**
* depend ../../sinon.js
*/
/**
* Sinon core utilities. For internal use only.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

var div = typeof document !== «undefined» && document.createElement(«div»);
var hasOwn = Object.prototype.hasOwnProperty;

function isDOMNode(obj) {
var success = false;

try {
obj.appendChild(div);
success = div.parentNode === obj;
} catch (e) {
return false;
} finally {
try {
obj.removeChild(div);
} catch (e) {
// Remove failed, not much we can do about that
}
}

return success;
}

function isElement(obj) {
return div && obj && obj.nodeType === 1 && isDOMNode(obj);
}

function isFunction(obj) {
return typeof obj === «function» || !!(obj && obj.constructor && obj.call && obj.apply);
}

function isReallyNaN(val) {
return typeof val === «number» && isNaN(val);
}

function mirrorProperties(target, source) {
for (var prop in source) {
if (!hasOwn.call(target, prop)) {
target[prop] = source[prop];
}
}
}

function isRestorable(obj) {
return typeof obj === «function» && typeof obj.restore === «function» && obj.restore.sinon;
}

// Cheap way to detect if we have ES5 support.
var hasES5Support = «keys» in Object;

function makeApi(sinon) {
sinon.wrapMethod = function wrapMethod(object, property, method) {
if (!object) {
throw new TypeError(«Should wrap property of object»);
}

if (typeof method !== «function» && typeof method !== «object») {
throw new TypeError(«Method wrapper should be a function or a property descriptor»);
}

function checkWrappedMethod(wrappedMethod) {
var error;

if (!isFunction(wrappedMethod)) {
error = new TypeError(«Attempted to wrap » + (typeof wrappedMethod) + " property " +
property + " as function");
} else if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
error = new TypeError(«Attempted to wrap » + property + " which is already wrapped");
} else if (wrappedMethod.calledBefore) {
var verb = wrappedMethod.returns? «stubbed»: «spied on»;
error = new TypeError(«Attempted to wrap » + property + " which is already " + verb);
}

if (error) {
if (wrappedMethod && wrappedMethod.stackTrace) {
error.stack += "\n—————\n" + wrappedMethod.stackTrace;
}
throw error;
}
}

var error, wrappedMethod, i;

// IE 8 does not support hasOwnProperty on the window object and Firefox has a problem
// when using hasOwn.call on objects from other frames.
var owned = object.hasOwnProperty? object.hasOwnProperty(property): hasOwn.call(object, property);

if (hasES5Support) {
var methodDesc = (typeof method === «function»)? {value: method}: method;
var wrappedMethodDesc = sinon.getPropertyDescriptor(object, property);

if (!wrappedMethodDesc) {
error = new TypeError(«Attempted to wrap » + (typeof wrappedMethod) + " property " +
property + " as function");
} else if (wrappedMethodDesc.restore && wrappedMethodDesc.restore.sinon) {
error = new TypeError(«Attempted to wrap » + property + " which is already wrapped");
}
if (error) {
if (wrappedMethodDesc && wrappedMethodDesc.stackTrace) {
error.stack += "\n—————\n" + wrappedMethodDesc.stackTrace;
}
throw error;
}

var types = sinon.objectKeys(methodDesc);
for (i = 0; i < types.length; i++) {
wrappedMethod = wrappedMethodDesc[types[i]];
checkWrappedMethod(wrappedMethod);
}

mirrorProperties(methodDesc, wrappedMethodDesc);
for (i = 0; i < types.length; i++) {
mirrorProperties(methodDesc[types[i]], wrappedMethodDesc[types[i]]);
}
Object.defineProperty(object, property, methodDesc);
} else {
wrappedMethod = object[property];
checkWrappedMethod(wrappedMethod);
object[property] = method;
method.displayName = property;
}

method.displayName = property;

// Set up a stack trace which can be used later to find what line of
// code the original method was created on.
method.stackTrace = (new Error(«Stack Trace for original»)).stack;

method.restore = function () {
// For prototype properties try to reset by delete first.
// If this fails (ex: localStorage on mobile safari) then force a reset
// via direct assignment.
if (!owned) {
// In some cases `delete` may throw an error
try {
delete object[property];
} catch (e) {} // eslint-disable-line no-empty
// For native code functions `delete` fails without throwing an error
// on Chrome < 43, PhantomJS, etc.
} else if (hasES5Support) {
Object.defineProperty(object, property, wrappedMethodDesc);
}

// Use strict equality comparison to check failures then force a reset
// via direct assignment.
if (object[property] === method) {
object[property] = wrappedMethod;
}
};

method.restore.sinon = true;

if (!hasES5Support) {
mirrorProperties(method, wrappedMethod);
}

return method;
};

sinon.create = function create(proto) {
var F = function () {};
F.prototype = proto;
return new F();
};

sinon.deepEqual = function deepEqual(a, b) {
if (sinon.match && sinon.match.isMatcher(a)) {
return a.test(b);
}

if (typeof a !== «object» || typeof b !== «object») {
return isReallyNaN(a) && isReallyNaN(b) || a === b;
}

if (isElement(a) || isElement(b)) {
return a === b;
}

if (a === b) {
return true;
}

if ((a === null && b !== null) || (a !== null && b === null)) {
return false;
}

if (a instanceof RegExp && b instanceof RegExp) {
return (a.source === b.source) && (a.global === b.global) &&
(a.ignoreCase === b.ignoreCase) && (a.multiline === b.multiline);
}

var aString = Object.prototype.toString.call(a);
if (aString !== Object.prototype.toString.call(b)) {
return false;
}

if (aString === "[object Date]") {
return a.valueOf() === b.valueOf();
}

var prop;
var aLength = 0;
var bLength = 0;

if (aString === "[object Array]" && a.length !== b.length) {
return false;
}

for (prop in a) {
if (a.hasOwnProperty(prop)) {
aLength += 1;

if (!(prop in b)) {
return false;
}

if (!deepEqual(a[prop], b[prop])) {
return false;
}
}
}

for (prop in b) {
if (b.hasOwnProperty(prop)) {
bLength += 1;
}
}

return aLength === bLength;
};

sinon.functionName = function functionName(func) {
var name = func.displayName || func.name;

// Use function decomposition as a last resort to get function
// name. Does not rely on function decomposition to work — if it
// doesn’t debugging will be slightly less informative
// (i.e. toString will say ‘spy’ rather than ‘myFunc’).
if (!name) {
var matches = func.toString().match(/function ([^\s\(]+)/);
name = matches && matches[1];
}

return name;
};

sinon.functionToString = function toString() {
if (this.getCall && this.callCount) {
var thisValue,
prop;
var i = this.callCount;

while (i—) {
thisValue = this.getCall(i).thisValue;

for (prop in thisValue) {
if (thisValue[prop] === this) {
return prop;
}
}
}
}

return this.displayName || «sinon fake»;
};

sinon.objectKeys = function objectKeys(obj) {
if (obj !== Object(obj)) {
throw new TypeError(«sinon.objectKeys called on a non-object»);
}

var keys = [];
var key;
for (key in obj) {
if (hasOwn.call(obj, key)) {
keys.push(key);
}
}

return keys;
};

sinon.getPropertyDescriptor = function getPropertyDescriptor(object, property) {
var proto = object;
var descriptor;

while (proto && !(descriptor = Object.getOwnPropertyDescriptor(proto, property))) {
proto = Object.getPrototypeOf(proto);
}
return descriptor;
};

sinon.getConfig = function (custom) {
var config = {};
custom = custom || {};
var defaults = sinon.defaultConfig;

for (var prop in defaults) {
if (defaults.hasOwnProperty(prop)) {
config[prop] = custom.hasOwnProperty(prop)? custom[prop]: defaults[prop];
}
}

return config;
};

sinon.defaultConfig = {
injectIntoThis: true,
injectInto: null,
properties: [«spy», «stub», «mock», «clock», «server», «requests»],
useFakeTimers: true,
useFakeServer: true
};

sinon.timesInWords = function timesInWords(count) {
return count === 1 && «once» ||
count === 2 && «twice» ||
count === 3 && «thrice» ||
(count || 0) + " times";
};

sinon.calledInOrder = function (spies) {
for (var i = 1, l = spies.length; i < l; i++) {
if (!spies[i — 1].calledBefore(spies[i]) || !spies[i].called) {
return false;
}
}

return true;
};

sinon.orderByFirstCall = function (spies) {
return spies.sort(function (a, b) {
// uuid, won’t ever be equal
var aCall = a.getCall(0);
var bCall = b.getCall(0);
var aId = aCall && aCall.callId || -1;
var bId = bCall && bCall.callId || -1;

return aId < bId? -1: 1;
});
};

sinon.createStubInstance = function (constructor) {
if (typeof constructor !== «function») {
throw new TypeError(«The constructor should be a function.»);
}
return sinon.stub(sinon.create(constructor.prototype));
};

sinon.restore = function (object) {
if (object !== null && typeof object === «object») {
for (var prop in object) {
if (isRestorable(object[prop])) {
object[prop].restore();
}
}
} else if (isRestorable(object)) {
object.restore();
}
};

return sinon;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports) {
makeApi(exports);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
*/
(function (sinonGlobal) {

function makeApi(sinon) {

// Adapted from developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
var hasDontEnumBug = (function () {
var obj = {
constructor: function () {
return «0»;
},
toString: function () {
return «1»;
},
valueOf: function () {
return «2»;
},
toLocaleString: function () {
return «3»;
},
prototype: function () {
return «4»;
},
isPrototypeOf: function () {
return «5»;
},
propertyIsEnumerable: function () {
return «6»;
},
hasOwnProperty: function () {
return «7»;
},
length: function () {
return «8»;
},
unique: function () {
return «9»;
}
};

var result = [];
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
result.push(obj[prop]());
}
}
return result.join("") !== «0123456789»;
})();

/* Public: Extend target in place with all (own) properties from sources in-order. Thus, last source will
* override properties in previous sources.
*
* target — The Object to extend
* sources — Objects to copy properties from.
*
* Returns the extended target
*/
function extend(target /*, sources */) {
var sources = Array.prototype.slice.call(arguments, 1);
var source, i, prop;

for (i = 0; i < sources.length; i++) {
source = sources[i];

for (prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}

// Make sure we copy (own) toString method even when in JScript with DontEnum bug
// See developer.mozilla.org/en/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
if (hasDontEnumBug && source.hasOwnProperty(«toString») && source.toString !== target.toString) {
target.toString = source.toString;
}
}

return target;
}

sinon.extend = extend;
return sinon.extend;
}

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
module.exports = makeApi(sinon);
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
*/
(function (sinonGlobal) {

function makeApi(sinon) {

function timesInWords(count) {
switch (count) {
case 1:
return «once»;
case 2:
return «twice»;
case 3:
return «thrice»;
default:
return (count || 0) + " times";
}
}

sinon.timesInWords = timesInWords;
return sinon.timesInWords;
}

function loadDependencies(require, exports, module) {
var core = require("./util/core");
module.exports = makeApi(core);
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
*/
/**
* Format functions
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2014 Christian Johansen
*/
(function (sinonGlobal) {

function makeApi(sinon) {
function typeOf(value) {
if (value === null) {
return «null»;
} else if (value === undefined) {
return «undefined»;
}
var string = Object.prototype.toString.call(value);
return string.substring(8, string.length — 1).toLowerCase();
}

sinon.typeOf = typeOf;
return sinon.typeOf;
}

function loadDependencies(require, exports, module) {
var core = require("./util/core");
module.exports = makeApi(core);
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
* depend typeOf.js
*/
/*jslint eqeqeq: false, onevar: false, plusplus: false*/
/*global module, require, sinon*/
/**
* Match functions
*
* author Maximilian Antoni (mail@maxantoni.de)
* @license BSD
*
* Copyright 2012 Maximilian Antoni
*/
(function (sinonGlobal) {

function makeApi(sinon) {
function assertType(value, type, name) {
var actual = sinon.typeOf(value);
if (actual !== type) {
throw new TypeError(«Expected type of » + name + " to be " +
type + ", but was " + actual);
}
}

var matcher = {
toString: function () {
return this.message;
}
};

function isMatcher(object) {
return matcher.isPrototypeOf(object);
}

function matchObject(expectation, actual) {
if (actual === null || actual === undefined) {
return false;
}
for (var key in expectation) {
if (expectation.hasOwnProperty(key)) {
var exp = expectation[key];
var act = actual[key];
if (isMatcher(exp)) {
if (!exp.test(act)) {
return false;
}
} else if (sinon.typeOf(exp) === «object») {
if (!matchObject(exp, act)) {
return false;
}
} else if (!sinon.deepEqual(exp, act)) {
return false;
}
}
}
return true;
}

function match(expectation, message) {
var m = sinon.create(matcher);
var type = sinon.typeOf(expectation);
switch (type) {
case «object»:
if (typeof expectation.test === «function») {
m.test = function (actual) {
return expectation.test(actual) === true;
};
m.message = «match(» + sinon.functionName(expectation.test) + ")";
return m;
}
var str = [];
for (var key in expectation) {
if (expectation.hasOwnProperty(key)) {
str.push(key + ": " + expectation[key]);
}
}
m.test = function (actual) {
return matchObject(expectation, actual);
};
m.message = «match(» + str.join(", ") + ")";
break;
case «number»:
m.test = function (actual) {
// we need type coercion here
return expectation == actual; // eslint-disable-line eqeqeq
};
break;
case «string»:
m.test = function (actual) {
if (typeof actual !== «string») {
return false;
}
return actual.indexOf(expectation) !== -1;
};
m.message = «match(\»" + expectation + "\")";
break;
case «regexp»:
m.test = function (actual) {
if (typeof actual !== «string») {
return false;
}
return expectation.test(actual);
};
break;
case «function»:
m.test = expectation;
if (message) {
m.message = message;
} else {
m.message = «match(» + sinon.functionName(expectation) + ")";
}
break;
default:
m.test = function (actual) {
return sinon.deepEqual(expectation, actual);
};
}
if (!m.message) {
m.message = «match(» + expectation + ")";
}
return m;
}

matcher.or = function (m2) {
if (!arguments.length) {
throw new TypeError(«Matcher expected»);
} else if (!isMatcher(m2)) {
m2 = match(m2);
}
var m1 = this;
var or = sinon.create(matcher);
or.test = function (actual) {
return m1.test(actual) || m2.test(actual);
};
or.message = m1.message + ".or(" + m2.message + ")";
return or;
};

matcher.and = function (m2) {
if (!arguments.length) {
throw new TypeError(«Matcher expected»);
} else if (!isMatcher(m2)) {
m2 = match(m2);
}
var m1 = this;
var and = sinon.create(matcher);
and.test = function (actual) {
return m1.test(actual) && m2.test(actual);
};
and.message = m1.message + ".and(" + m2.message + ")";
return and;
};

match.isMatcher = isMatcher;

match.any = match(function () {
return true;
}, «any»);

match.defined = match(function (actual) {
return actual !== null && actual !== undefined;
}, «defined»);

match.truthy = match(function (actual) {
return !!actual;
}, «truthy»);

match.falsy = match(function (actual) {
return !actual;
}, «falsy»);

match.same = function (expectation) {
return match(function (actual) {
return expectation === actual;
}, «same(» + expectation + ")");
};

match.typeOf = function (type) {
assertType(type, «string», «type»);
return match(function (actual) {
return sinon.typeOf(actual) === type;
}, «typeOf(\»" + type + "\")");
};

match.instanceOf = function (type) {
assertType(type, «function», «type»);
return match(function (actual) {
return actual instanceof type;
}, «instanceOf(» + sinon.functionName(type) + ")");
};

function createPropertyMatcher(propertyTest, messagePrefix) {
return function (property, value) {
assertType(property, «string», «property»);
var onlyProperty = arguments.length === 1;
var message = messagePrefix + "(\"" + property + "\"";
if (!onlyProperty) {
message += ", " + value;
}
message += ")";
return match(function (actual) {
if (actual === undefined || actual === null ||
!propertyTest(actual, property)) {
return false;
}
return onlyProperty || sinon.deepEqual(value, actual[property]);
}, message);
};
}

match.has = createPropertyMatcher(function (actual, property) {
if (typeof actual === «object») {
return property in actual;
}
return actual[property] !== undefined;
}, «has»);

match.hasOwn = createPropertyMatcher(function (actual, property) {
return actual.hasOwnProperty(property);
}, «hasOwn»);

match.bool = match.typeOf(«boolean»);
match.number = match.typeOf(«number»);
match.string = match.typeOf(«string»);
match.object = match.typeOf(«object»);
match.func = match.typeOf(«function»);
match.array = match.typeOf(«array»);
match.regexp = match.typeOf(«regexp»);
match.date = match.typeOf(«date»);

sinon.match = match;
return match;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./typeOf");
module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
*/
/**
* Format functions
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2014 Christian Johansen
*/
(function (sinonGlobal, formatio) {

function makeApi(sinon) {
function valueFormatter(value) {
return "" + value;
}

function getFormatioFormatter() {
var formatter = formatio.configure({
quoteStrings: false,
limitChildrenCount: 250
});

function format() {
return formatter.ascii.apply(formatter, arguments);
}

return format;
}

function getNodeFormatter() {
try {
var util = require(«util»);
} catch (e) {
/* Node, but no util module — would be very old, but better safe than sorry */
}

function format(v) {
var isObjectWithNativeToString = typeof v === «object» && v.toString === Object.prototype.toString;
return isObjectWithNativeToString? util.inspect(v): v;
}

return util? format: valueFormatter;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var formatter;

if (isNode) {
try {
formatio = require(«formatio»);
}
catch (e) {} // eslint-disable-line no-empty
}

if (formatio) {
formatter = getFormatioFormatter();
} else if (isNode) {
formatter = getNodeFormatter();
} else {
formatter = valueFormatter;
}

sinon.format = formatter;
return sinon.format;
}

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
module.exports = makeApi(sinon);
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon, // eslint-disable-line no-undef
typeof formatio === «object» && formatio // eslint-disable-line no-undef
));

/**
* depend util/core.js
* depend match.js
* depend format.js
*/
/**
* Spy calls
*
* author Christian Johansen (christian@cjohansen.no)
* author Maximilian Antoni (mail@maxantoni.de)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
* Copyright 2013 Maximilian Antoni
*/
(function (sinonGlobal) {

var slice = Array.prototype.slice;

function makeApi(sinon) {
function throwYieldError(proxy, text, args) {
var msg = sinon.functionName(proxy) + text;
if (args.length) {
msg += " Received [" + slice.call(args).join(", ") + "]";
}
throw new Error(msg);
}

var callProto = {
calledOn: function calledOn(thisValue) {
if (sinon.match && sinon.match.isMatcher(thisValue)) {
return thisValue.test(this.thisValue);
}
return this.thisValue === thisValue;
},

calledWith: function calledWith() {
var l = arguments.length;
if (l > this.args.length) {
return false;
}
for (var i = 0; i < l; i += 1) {
if (!sinon.deepEqual(arguments[i], this.args[i])) {
return false;
}
}

return true;
},

calledWithMatch: function calledWithMatch() {
var l = arguments.length;
if (l > this.args.length) {
return false;
}
for (var i = 0; i < l; i += 1) {
var actual = this.args[i];
var expectation = arguments[i];
if (!sinon.match || !sinon.match(expectation).test(actual)) {
return false;
}
}
return true;
},

calledWithExactly: function calledWithExactly() {
return arguments.length === this.args.length &&
this.calledWith.apply(this, arguments);
},

notCalledWith: function notCalledWith() {
return !this.calledWith.apply(this, arguments);
},

notCalledWithMatch: function notCalledWithMatch() {
return !this.calledWithMatch.apply(this, arguments);
},

returned: function returned(value) {
return sinon.deepEqual(value, this.returnValue);
},

threw: function threw(error) {
if (typeof error === «undefined» || !this.exception) {
return !!this.exception;
}

return this.exception === error || this.exception.name === error;
},

calledWithNew: function calledWithNew() {
return this.proxy.prototype && this.thisValue instanceof this.proxy;
},

calledBefore: function (other) {
return this.callId < other.callId;
},

calledAfter: function (other) {
return this.callId > other.callId;
},

callArg: function (pos) {
this.args[pos]();
},

callArgOn: function (pos, thisValue) {
this.args[pos].apply(thisValue);
},

callArgWith: function (pos) {
this.callArgOnWith.apply(this, [pos, null].concat(slice.call(arguments, 1)));
},

callArgOnWith: function (pos, thisValue) {
var args = slice.call(arguments, 2);
this.args[pos].apply(thisValue, args);
},

«yield»: function () {
this.yieldOn.apply(this, [null].concat(slice.call(arguments, 0)));
},

yieldOn: function (thisValue) {
var args = this.args;
for (var i = 0, l = args.length; i < l; ++i) {
if (typeof args[i] === «function») {
args[i].apply(thisValue, slice.call(arguments, 1));
return;
}
}
throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);
},

yieldTo: function (prop) {
this.yieldToOn.apply(this, [prop, null].concat(slice.call(arguments, 1)));
},

yieldToOn: function (prop, thisValue) {
var args = this.args;
for (var i = 0, l = args.length; i < l; ++i) {
if (args[i] && typeof args[i][prop] === «function») {
args[i][prop].apply(thisValue, slice.call(arguments, 2));
return;
}
}
throwYieldError(this.proxy, " cannot yield to ‘" + prop +
"’ since no callback was passed.", args);
},

getStackFrames: function () {
// Omit the error message and the two top stack frames in sinon itself:
return this.stack && this.stack.split("\n").slice(3);
},

toString: function () {
var callStr = this.proxy.toString() + "(";
var args = [];

for (var i = 0, l = this.args.length; i < l; ++i) {
args.push(sinon.format(this.args[i]));
}

callStr = callStr + args.join(", ") + ")";

if (typeof this.returnValue !== «undefined») {
callStr += " => " + sinon.format(this.returnValue);
}

if (this.exception) {
callStr += " !" + this.exception.name;

if (this.exception.message) {
callStr += "(" + this.exception.message + ")";
}
}
if (this.stack) {
callStr += this.getStackFrames()[0].replace(/^\s*(?:at\s+|@)?/, " at ");

}

return callStr;
}
};

callProto.invokeCallback = callProto.yield;

function createSpyCall(spy, thisValue, args, returnValue, exception, id, stack) {
if (typeof id !== «number») {
throw new TypeError(«Call id is not a number»);
}
var proxyCall = sinon.create(callProto);
proxyCall.proxy = spy;
proxyCall.thisValue = thisValue;
proxyCall.args = args;
proxyCall.returnValue = returnValue;
proxyCall.exception = exception;
proxyCall.callId = id;
proxyCall.stack = stack;

return proxyCall;
}
createSpyCall.toString = callProto.toString; // used by mocks

sinon.spyCall = createSpyCall;
return createSpyCall;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./match");
require("./format");
module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend times_in_words.js
* depend util/core.js
* depend extend.js
* depend call.js
* depend format.js
*/
/**
* Spy functions
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

function makeApi(sinon) {
var push = Array.prototype.push;
var slice = Array.prototype.slice;
var callId = 0;

function spy(object, property, types) {
if (!property && typeof object === «function») {
return spy.create(object);
}

if (!object && !property) {
return spy.create(function () { });
}

if (types) {
var methodDesc = sinon.getPropertyDescriptor(object, property);
for (var i = 0; i < types.length; i++) {
methodDesc[types[i]] = spy.create(methodDesc[types[i]]);
}
return sinon.wrapMethod(object, property, methodDesc);
}

return sinon.wrapMethod(object, property, spy.create(object[property]));
}

function matchingFake(fakes, args, strict) {
if (!fakes) {
return undefined;
}

for (var i = 0, l = fakes.length; i < l; i++) {
if (fakes[i].matches(args, strict)) {
return fakes[i];
}
}
}

function incrementCallCount() {
this.called = true;
this.callCount += 1;
this.notCalled = false;
this.calledOnce = this.callCount === 1;
this.calledTwice = this.callCount === 2;
this.calledThrice = this.callCount === 3;
}

function createCallProperties() {
this.firstCall = this.getCall(0);
this.secondCall = this.getCall(1);
this.thirdCall = this.getCall(2);
this.lastCall = this.getCall(this.callCount — 1);
}

var vars = «a,b,c,d,e,f,g,h,i,j,k,l»;
function createProxy(func, proxyLength) {
// Retain the function length:
var p;
if (proxyLength) {
eval(«p = (function proxy(» + vars.substring(0, proxyLength * 2 — 1) + // eslint-disable-line no-eval
") { return p.invoke(func, this, slice.call(arguments)); });");
} else {
p = function proxy() {
return p.invoke(func, this, slice.call(arguments));
};
}
p.isSinonProxy = true;
return p;
}

var uuid = 0;

// Public API
var spyApi = {
reset: function () {
if (this.invoking) {
var err = new Error(«Cannot reset Sinon function while invoking it. » +
«Move the call to .reset outside of the callback.»);
err.name = «InvalidResetException»;
throw err;
}

this.called = false;
this.notCalled = true;
this.calledOnce = false;
this.calledTwice = false;
this.calledThrice = false;
this.callCount = 0;
this.firstCall = null;
this.secondCall = null;
this.thirdCall = null;
this.lastCall = null;
this.args = [];
this.returnValues = [];
this.thisValues = [];
this.exceptions = [];
this.callIds = [];
this.stacks = [];
if (this.fakes) {
for (var i = 0; i < this.fakes.length; i++) {
this.fakes[i].reset();
}
}

return this;
},

create: function create(func, spyLength) {
var name;

if (typeof func !== «function») {
func = function () { };
} else {
name = sinon.functionName(func);
}

if (!spyLength) {
spyLength = func.length;
}

var proxy = createProxy(func, spyLength);

sinon.extend(proxy, spy);
delete proxy.create;
sinon.extend(proxy, func);

proxy.reset();
proxy.prototype = func.prototype;
proxy.displayName = name || «spy»;
proxy.toString = sinon.functionToString;
proxy.instantiateFake = sinon.spy.create;
proxy.id = «spy#» + uuid++;

return proxy;
},

invoke: function invoke(func, thisValue, args) {
var matching = matchingFake(this.fakes, args);
var exception, returnValue;

incrementCallCount.call(this);
push.call(this.thisValues, thisValue);
push.call(this.args, args);
push.call(this.callIds, callId++);

// Make call properties available from within the spied function:
createCallProperties.call(this);

try {
this.invoking = true;

if (matching) {
returnValue = matching.invoke(func, thisValue, args);
} else {
returnValue = (this.func || func).apply(thisValue, args);
}

var thisCall = this.getCall(this.callCount — 1);
if (thisCall.calledWithNew() && typeof returnValue !== «object») {
returnValue = thisValue;
}
} catch (e) {
exception = e;
} finally {
delete this.invoking;
}

push.call(this.exceptions, exception);
push.call(this.returnValues, returnValue);
push.call(this.stacks, new Error().stack);

// Make return value and exception available in the calls:
createCallProperties.call(this);

if (exception !== undefined) {
throw exception;
}

return returnValue;
},

named: function named(name) {
this.displayName = name;
return this;
},

getCall: function getCall(i) {
if (i < 0 || i >= this.callCount) {
return null;
}

return sinon.spyCall(this, this.thisValues[i], this.args[i],
this.returnValues[i], this.exceptions[i],
this.callIds[i], this.stacks[i]);
},

getCalls: function () {
var calls = [];
var i;

for (i = 0; i < this.callCount; i++) {
calls.push(this.getCall(i));
}

return calls;
},

calledBefore: function calledBefore(spyFn) {
if (!this.called) {
return false;
}

if (!spyFn.called) {
return true;
}

return this.callIds[0] < spyFn.callIds[spyFn.callIds.length — 1];
},

calledAfter: function calledAfter(spyFn) {
if (!this.called || !spyFn.called) {
return false;
}

return this.callIds[this.callCount — 1] > spyFn.callIds[spyFn.callCount — 1];
},

withArgs: function () {
var args = slice.call(arguments);

if (this.fakes) {
var match = matchingFake(this.fakes, args, true);

if (match) {
return match;
}
} else {
this.fakes = [];
}

var original = this;
var fake = this.instantiateFake();
fake.matchingAguments = args;
fake.parent = this;
push.call(this.fakes, fake);

fake.withArgs = function () {
return original.withArgs.apply(original, arguments);
};

for (var i = 0; i < this.args.length; i++) {
if (fake.matches(this.args[i])) {
incrementCallCount.call(fake);
push.call(fake.thisValues, this.thisValues[i]);
push.call(fake.args, this.args[i]);
push.call(fake.returnValues, this.returnValues[i]);
push.call(fake.exceptions, this.exceptions[i]);
push.call(fake.callIds, this.callIds[i]);
}
}
createCallProperties.call(fake);

return fake;
},

matches: function (args, strict) {
var margs = this.matchingAguments;

if (margs.length <= args.length &&
sinon.deepEqual(margs, args.slice(0, margs.length))) {
return !strict || margs.length === args.length;
}
},

printf: function (format) {
var spyInstance = this;
var args = slice.call(arguments, 1);
var formatter;

return (format || "").replace(/%(.)/g, function (match, specifyer) {
formatter = spyApi.formatters[specifyer];

if (typeof formatter === «function») {
return formatter.call(null, spyInstance, args);
} else if (!isNaN(parseInt(specifyer, 10))) {
return sinon.format(args[specifyer — 1]);
}

return "%" + specifyer;
});
}
};

function delegateToCalls(method, matchAny, actual, notCalled) {
spyApi[method] = function () {
if (!this.called) {
if (notCalled) {
return notCalled.apply(this, arguments);
}
return false;
}

var currentCall;
var matches = 0;

for (var i = 0, l = this.callCount; i < l; i += 1) {
currentCall = this.getCall(i);

if (currentCall[actual || method].apply(currentCall, arguments)) {
matches += 1;

if (matchAny) {
return true;
}
}
}

return matches === this.callCount;
};
}

delegateToCalls(«calledOn», true);
delegateToCalls(«alwaysCalledOn», false, «calledOn»);
delegateToCalls(«calledWith», true);
delegateToCalls(«calledWithMatch», true);
delegateToCalls(«alwaysCalledWith», false, «calledWith»);
delegateToCalls(«alwaysCalledWithMatch», false, «calledWithMatch»);
delegateToCalls(«calledWithExactly», true);
delegateToCalls(«alwaysCalledWithExactly», false, «calledWithExactly»);
delegateToCalls(«neverCalledWith», false, «notCalledWith», function () {
return true;
});
delegateToCalls(«neverCalledWithMatch», false, «notCalledWithMatch», function () {
return true;
});
delegateToCalls(«threw», true);
delegateToCalls(«alwaysThrew», false, «threw»);
delegateToCalls(«returned», true);
delegateToCalls(«alwaysReturned», false, «returned»);
delegateToCalls(«calledWithNew», true);
delegateToCalls(«alwaysCalledWithNew», false, «calledWithNew»);
delegateToCalls(«callArg», false, «callArgWith», function () {
throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
});
spyApi.callArgWith = spyApi.callArg;
delegateToCalls(«callArgOn», false, «callArgOnWith», function () {
throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
});
spyApi.callArgOnWith = spyApi.callArgOn;
delegateToCalls(«yield», false, «yield», function () {
throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
});
// «invokeCallback» is an alias for «yield» since «yield» is invalid in strict mode.
spyApi.invokeCallback = spyApi.yield;
delegateToCalls(«yieldOn», false, «yieldOn», function () {
throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
});
delegateToCalls(«yieldTo», false, «yieldTo», function (property) {
throw new Error(this.toString() + " cannot yield to ‘" + property +
"’ since it was not yet invoked.");
});
delegateToCalls(«yieldToOn», false, «yieldToOn», function (property) {
throw new Error(this.toString() + " cannot yield to ‘" + property +
"’ since it was not yet invoked.");
});

spyApi.formatters = {
c: function (spyInstance) {
return sinon.timesInWords(spyInstance.callCount);
},

n: function (spyInstance) {
return spyInstance.toString();
},

C: function (spyInstance) {
var calls = [];

for (var i = 0, l = spyInstance.callCount; i < l; ++i) {
var stringifiedCall = " " + spyInstance.getCall(i).toString();
if (/\n/.test(calls[i — 1])) {
stringifiedCall = "\n" + stringifiedCall;
}
push.call(calls, stringifiedCall);
}

return calls.length > 0? "\n" + calls.join("\n"): "";
},

t: function (spyInstance) {
var objects = [];

for (var i = 0, l = spyInstance.callCount; i < l; ++i) {
push.call(objects, sinon.format(spyInstance.thisValues[i]));
}

return objects.join(", ");
},

"*": function (spyInstance, args) {
var formatted = [];

for (var i = 0, l = args.length; i < l; ++i) {
push.call(formatted, sinon.format(args[i]));
}

return formatted.join(", ");
}
};

sinon.extend(spy, spyApi);

spy.spyCall = sinon.spyCall;
sinon.spy = spy;

return spy;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var core = require("./util/core");
require("./call");
require("./extend");
require("./times_in_words");
require("./format");
module.exports = makeApi(core);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
* depend extend.js
*/
/**
* Stub behavior
*
* author Christian Johansen (christian@cjohansen.no)
* author Tim Fischbach (mail@timfischbach.de)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

var slice = Array.prototype.slice;
var join = Array.prototype.join;
var useLeftMostCallback = -1;
var useRightMostCallback = -2;

var nextTick = (function () {
if (typeof process === «object» && typeof process.nextTick === «function») {
return process.nextTick;
}

if (typeof setImmediate === «function») {
return setImmediate;
}

return function (callback) {
setTimeout(callback, 0);
};
})();

function throwsException(error, message) {
if (typeof error === «string») {
this.exception = new Error(message || "");
this.exception.name = error;
} else if (!error) {
this.exception = new Error(«Error»);
} else {
this.exception = error;
}

return this;
}

function getCallback(behavior, args) {
var callArgAt = behavior.callArgAt;

if (callArgAt >= 0) {
return args[callArgAt];
}

var argumentList;

if (callArgAt === useLeftMostCallback) {
argumentList = args;
}

if (callArgAt === useRightMostCallback) {
argumentList = slice.call(args).reverse();
}

var callArgProp = behavior.callArgProp;

for (var i = 0, l = argumentList.length; i < l; ++i) {
if (!callArgProp && typeof argumentList[i] === «function») {
return argumentList[i];
}

if (callArgProp && argumentList[i] &&
typeof argumentList[i][callArgProp] === «function») {
return argumentList[i][callArgProp];
}
}

return null;
}

function makeApi(sinon) {
function getCallbackError(behavior, func, args) {
if (behavior.callArgAt < 0) {
var msg;

if (behavior.callArgProp) {
msg = sinon.functionName(behavior.stub) +
" expected to yield to ‘" + behavior.callArgProp +
"’, but no object with such a property was passed.";
} else {
msg = sinon.functionName(behavior.stub) +
" expected to yield, but no callback was passed.";
}

if (args.length > 0) {
msg += " Received [" + join.call(args, ", ") + "]";
}

return msg;
}

return «argument at index » + behavior.callArgAt + " is not a function: " + func;
}

function callCallback(behavior, args) {
if (typeof behavior.callArgAt === «number») {
var func = getCallback(behavior, args);

if (typeof func !== «function») {
throw new TypeError(getCallbackError(behavior, func, args));
}

if (behavior.callbackAsync) {
nextTick(function () {
func.apply(behavior.callbackContext, behavior.callbackArguments);
});
} else {
func.apply(behavior.callbackContext, behavior.callbackArguments);
}
}
}

var proto = {
create: function create(stub) {
var behavior = sinon.extend({}, sinon.behavior);
delete behavior.create;
behavior.stub = stub;

return behavior;
},

isPresent: function isPresent() {
return (typeof this.callArgAt === «number» ||
this.exception ||
typeof this.returnArgAt === «number» ||
this.returnThis ||
this.returnValueDefined);
},

invoke: function invoke(context, args) {
callCallback(this, args);

if (this.exception) {
throw this.exception;
} else if (typeof this.returnArgAt === «number») {
return args[this.returnArgAt];
} else if (this.returnThis) {
return context;
}

return this.returnValue;
},

onCall: function onCall(index) {
return this.stub.onCall(index);
},

onFirstCall: function onFirstCall() {
return this.stub.onFirstCall();
},

onSecondCall: function onSecondCall() {
return this.stub.onSecondCall();
},

onThirdCall: function onThirdCall() {
return this.stub.onThirdCall();
},

withArgs: function withArgs(/* arguments */) {
throw new Error(
«Defining a stub by invoking \»stub.onCall(…).withArgs(…)\" " +
«is not supported. Use \»stub.withArgs(…).onCall(…)\" " +
«to define sequential behavior for calls with certain arguments.»
);
},

callsArg: function callsArg(pos) {
if (typeof pos !== «number») {
throw new TypeError(«argument index is not number»);
}

this.callArgAt = pos;
this.callbackArguments = [];
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

callsArgOn: function callsArgOn(pos, context) {
if (typeof pos !== «number») {
throw new TypeError(«argument index is not number»);
}
if (typeof context !== «object») {
throw new TypeError(«argument context is not an object»);
}

this.callArgAt = pos;
this.callbackArguments = [];
this.callbackContext = context;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

callsArgWith: function callsArgWith(pos) {
if (typeof pos !== «number») {
throw new TypeError(«argument index is not number»);
}

this.callArgAt = pos;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

callsArgOnWith: function callsArgWith(pos, context) {
if (typeof pos !== «number») {
throw new TypeError(«argument index is not number»);
}
if (typeof context !== «object») {
throw new TypeError(«argument context is not an object»);
}

this.callArgAt = pos;
this.callbackArguments = slice.call(arguments, 2);
this.callbackContext = context;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

yields: function () {
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 0);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

yieldsRight: function () {
this.callArgAt = useRightMostCallback;
this.callbackArguments = slice.call(arguments, 0);
this.callbackContext = undefined;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

yieldsOn: function (context) {
if (typeof context !== «object») {
throw new TypeError(«argument context is not an object»);
}

this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = context;
this.callArgProp = undefined;
this.callbackAsync = false;

return this;
},

yieldsTo: function (prop) {
this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 1);
this.callbackContext = undefined;
this.callArgProp = prop;
this.callbackAsync = false;

return this;
},

yieldsToOn: function (prop, context) {
if (typeof context !== «object») {
throw new TypeError(«argument context is not an object»);
}

this.callArgAt = useLeftMostCallback;
this.callbackArguments = slice.call(arguments, 2);
this.callbackContext = context;
this.callArgProp = prop;
this.callbackAsync = false;

return this;
},

throws: throwsException,
throwsException: throwsException,

returns: function returns(value) {
this.returnValue = value;
this.returnValueDefined = true;
this.exception = undefined;

return this;
},

returnsArg: function returnsArg(pos) {
if (typeof pos !== «number») {
throw new TypeError(«argument index is not number»);
}

this.returnArgAt = pos;

return this;
},

returnsThis: function returnsThis() {
this.returnThis = true;

return this;
}
};

function createAsyncVersion(syncFnName) {
return function () {
var result = this[syncFnName].apply(this, arguments);
this.callbackAsync = true;
return result;
};
}

// create asynchronous versions of callsArg* and yields* methods
for (var method in proto) {
// need to avoid creating anotherasync versions of the newly added async methods
if (proto.hasOwnProperty(method) && method.match(/^(callsArg|yields)/) && !method.match(/Async/)) {
proto[method + «Async»] = createAsyncVersion(method);
}
}

sinon.behavior = proto;
return proto;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./extend");
module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
*/
(function (sinonGlobal) {

function makeApi(sinon) {
function walkInternal(obj, iterator, context, originalObj, seen) {
var proto, prop;

if (typeof Object.getOwnPropertyNames !== «function») {
// We explicitly want to enumerate through all of the prototype’s properties
// in this case, therefore we deliberately leave out an own property check.
/* eslint-disable guard-for-in */
for (prop in obj) {
iterator.call(context, obj[prop], prop, obj);
}
/* eslint-enable guard-for-in */

return;
}

Object.getOwnPropertyNames(obj).forEach(function (k) {
if (!seen[k]) {
seen[k] = true;
var target = typeof Object.getOwnPropertyDescriptor(obj, k).get === «function»?
originalObj: obj;
iterator.call(context, target[k], k, target);
}
});

proto = Object.getPrototypeOf(obj);
if (proto) {
walkInternal(proto, iterator, context, originalObj, seen);
}
}

/* Public: walks the prototype chain of an object and iterates over every own property
* name encountered. The iterator is called in the same fashion that Array.prototype.forEach
* works, where it is passed the value, key, and own object as the 1st, 2nd, and 3rd positional
* argument, respectively. In cases where Object.getOwnPropertyNames is not available, walk will
* default to using a simple for..in loop.
*
* obj — The object to walk the prototype chain for.
* iterator — The function to be called on each pass of the walk.
* context — (Optional) When given, the iterator will be called with this object as the receiver.
*/
function walk(obj, iterator, context) {
return walkInternal(obj, iterator, context, obj, {});
}

sinon.walk = walk;
return sinon.walk;
}

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
module.exports = makeApi(sinon);
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
* depend extend.js
* depend spy.js
* depend behavior.js
* depend walk.js
*/
/**
* Stub functions
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

function makeApi(sinon) {
function stub(object, property, func) {
if (!!func && typeof func !== «function» && typeof func !== «object») {
throw new TypeError(«Custom stub should be a function or a property descriptor»);
}

var wrapper;

if (func) {
if (typeof func === «function») {
wrapper = sinon.spy && sinon.spy.create? sinon.spy.create(func): func;
} else {
wrapper = func;
if (sinon.spy && sinon.spy.create) {
var types = sinon.objectKeys(wrapper);
for (var i = 0; i < types.length; i++) {
wrapper[types[i]] = sinon.spy.create(wrapper[types[i]]);
}
}
}
} else {
var stubLength = 0;
if (typeof object === «object» && typeof object[property] === «function») {
stubLength = object[property].length;
}
wrapper = stub.create(stubLength);
}

if (!object && typeof property === «undefined») {
return sinon.stub.create();
}

if (typeof property === «undefined» && typeof object === «object») {
sinon.walk(object || {}, function (value, prop, propOwner) {
// we don’t want to stub things like toString(), valueOf(), etc. so we only stub if the object
// is not Object.prototype
if (
propOwner !== Object.prototype &&
prop !== «constructor» &&
typeof sinon.getPropertyDescriptor(propOwner, prop).value === «function»
) {
stub(object, prop);
}
});

return object;
}

return sinon.wrapMethod(object, property, wrapper);
}

/*eslint-disable no-use-before-define*/
function getParentBehaviour(stubInstance) {
return (stubInstance.parent && getCurrentBehavior(stubInstance.parent));
}

function getDefaultBehavior(stubInstance) {
return stubInstance.defaultBehavior ||
getParentBehaviour(stubInstance) ||
sinon.behavior.create(stubInstance);
}

function getCurrentBehavior(stubInstance) {
var behavior = stubInstance.behaviors[stubInstance.callCount — 1];
return behavior && behavior.isPresent()? behavior: getDefaultBehavior(stubInstance);
}
/*eslint-enable no-use-before-define*/

var uuid = 0;

var proto = {
create: function create(stubLength) {
var functionStub = function () {
return getCurrentBehavior(functionStub).invoke(this, arguments);
};

functionStub.id = «stub#» + uuid++;
var orig = functionStub;
functionStub = sinon.spy.create(functionStub, stubLength);
functionStub.func = orig;

sinon.extend(functionStub, stub);
functionStub.instantiateFake = sinon.stub.create;
functionStub.displayName = «stub»;
functionStub.toString = sinon.functionToString;

functionStub.defaultBehavior = null;
functionStub.behaviors = [];

return functionStub;
},

resetBehavior: function () {
var i;

this.defaultBehavior = null;
this.behaviors = [];

delete this.returnValue;
delete this.returnArgAt;
this.returnThis = false;

if (this.fakes) {
for (i = 0; i < this.fakes.length; i++) {
this.fakes[i].resetBehavior();
}
}
},

onCall: function onCall(index) {
if (!this.behaviors[index]) {
this.behaviors[index] = sinon.behavior.create(this);
}

return this.behaviors[index];
},

onFirstCall: function onFirstCall() {
return this.onCall(0);
},

onSecondCall: function onSecondCall() {
return this.onCall(1);
},

onThirdCall: function onThirdCall() {
return this.onCall(2);
}
};

function createBehavior(behaviorMethod) {
return function () {
this.defaultBehavior = this.defaultBehavior || sinon.behavior.create(this);
this.defaultBehavior[behaviorMethod].apply(this.defaultBehavior, arguments);
return this;
};
}

for (var method in sinon.behavior) {
if (sinon.behavior.hasOwnProperty(method) &&
!proto.hasOwnProperty(method) &&
method !== «create» &&
method !== «withArgs» &&
method !== «invoke») {
proto[method] = createBehavior(method);
}
}

sinon.extend(stub, proto);
sinon.stub = stub;

return stub;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var core = require("./util/core");
require("./behavior");
require("./spy");
require("./extend");
module.exports = makeApi(core);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend times_in_words.js
* depend util/core.js
* depend call.js
* depend extend.js
* depend match.js
* depend spy.js
* depend stub.js
* depend format.js
*/
/**
* Mock functions.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

function makeApi(sinon) {
var push = [].push;
var match = sinon.match;

function mock(object) {
// if (typeof console !== undefined && console.warn) {
// console.warn(«mock will be removed from Sinon.JS v2.0»);
// }

if (!object) {
return sinon.expectation.create(«Anonymous mock»);
}

return mock.create(object);
}

function each(collection, callback) {
if (!collection) {
return;
}

for (var i = 0, l = collection.length; i < l; i += 1) {
callback(collection[i]);
}
}

function arrayEquals(arr1, arr2, compareLength) {
if (compareLength && (arr1.length !== arr2.length)) {
return false;
}

for (var i = 0, l = arr1.length; i < l; i++) {
if (!sinon.deepEqual(arr1[i], arr2[i])) {
return false;
}
}
return true;
}

sinon.extend(mock, {
create: function create(object) {
if (!object) {
throw new TypeError(«object is null»);
}

var mockObject = sinon.extend({}, mock);
mockObject.object = object;
delete mockObject.create;

return mockObject;
},

expects: function expects(method) {
if (!method) {
throw new TypeError(«method is falsy»);
}

if (!this.expectations) {
this.expectations = {};
this.proxies = [];
}

if (!this.expectations[method]) {
this.expectations[method] = [];
var mockObject = this;

sinon.wrapMethod(this.object, method, function () {
return mockObject.invokeMethod(method, this, arguments);
});

push.call(this.proxies, method);
}

var expectation = sinon.expectation.create(method);
push.call(this.expectations[method], expectation);

return expectation;
},

restore: function restore() {
var object = this.object;

each(this.proxies, function (proxy) {
if (typeof object[proxy].restore === «function») {
object[proxy].restore();
}
});
},

verify: function verify() {
var expectations = this.expectations || {};
var messages = [];
var met = [];

each(this.proxies, function (proxy) {
each(expectations[proxy], function (expectation) {
if (!expectation.met()) {
push.call(messages, expectation.toString());
} else {
push.call(met, expectation.toString());
}
});
});

this.restore();

if (messages.length > 0) {
sinon.expectation.fail(messages.concat(met).join("\n"));
} else if (met.length > 0) {
sinon.expectation.pass(messages.concat(met).join("\n"));
}

return true;
},

invokeMethod: function invokeMethod(method, thisValue, args) {
var expectations = this.expectations && this.expectations[method]? this.expectations[method]: [];
var expectationsWithMatchingArgs = [];
var currentArgs = args || [];
var i, available;

for (i = 0; i < expectations.length; i += 1) {
var expectedArgs = expectations[i].expectedArguments || [];
if (arrayEquals(expectedArgs, currentArgs, expectations[i].expectsExactArgCount)) {
expectationsWithMatchingArgs.push(expectations[i]);
}
}

for (i = 0; i < expectationsWithMatchingArgs.length; i += 1) {
if (!expectationsWithMatchingArgs[i].met() &&
expectationsWithMatchingArgs[i].allowsCall(thisValue, args)) {
return expectationsWithMatchingArgs[i].apply(thisValue, args);
}
}

var messages = [];
var exhausted = 0;

for (i = 0; i < expectationsWithMatchingArgs.length; i += 1) {
if (expectationsWithMatchingArgs[i].allowsCall(thisValue, args)) {
available = available || expectationsWithMatchingArgs[i];
} else {
exhausted += 1;
}
}

if (available && exhausted === 0) {
return available.apply(thisValue, args);
}

for (i = 0; i < expectations.length; i += 1) {
push.call(messages, " " + expectations[i].toString());
}

messages.unshift(«Unexpected call: » + sinon.spyCall.toString.call({
proxy: method,
args: args
}));

sinon.expectation.fail(messages.join("\n"));
}
});

var times = sinon.timesInWords;
var slice = Array.prototype.slice;

function callCountInWords(callCount) {
if (callCount === 0) {
return «never called»;
}

return «called » + times(callCount);
}

function expectedCallCountInWords(expectation) {
var min = expectation.minCalls;
var max = expectation.maxCalls;

if (typeof min === «number» && typeof max === «number») {
var str = times(min);

if (min !== max) {
str = «at least » + str + " and at most " + times(max);
}

return str;
}

if (typeof min === «number») {
return «at least » + times(min);
}

return «at most » + times(max);
}

function receivedMinCalls(expectation) {
var hasMinLimit = typeof expectation.minCalls === «number»;
return !hasMinLimit || expectation.callCount >= expectation.minCalls;
}

function receivedMaxCalls(expectation) {
if (typeof expectation.maxCalls !== «number») {
return false;
}

return expectation.callCount === expectation.maxCalls;
}

function verifyMatcher(possibleMatcher, arg) {
var isMatcher = match && match.isMatcher(possibleMatcher);

return isMatcher && possibleMatcher.test(arg) || true;
}

sinon.expectation = {
minCalls: 1,
maxCalls: 1,

create: function create(methodName) {
var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);
delete expectation.create;
expectation.method = methodName;

return expectation;
},

invoke: function invoke(func, thisValue, args) {
this.verifyCallAllowed(thisValue, args);

return sinon.spy.invoke.apply(this, arguments);
},

atLeast: function atLeast(num) {
if (typeof num !== «number») {
throw new TypeError("’" + num + "’ is not number");
}

if (!this.limitsSet) {
this.maxCalls = null;
this.limitsSet = true;
}

this.minCalls = num;

return this;
},

atMost: function atMost(num) {
if (typeof num !== «number») {
throw new TypeError("’" + num + "’ is not number");
}

if (!this.limitsSet) {
this.minCalls = null;
this.limitsSet = true;
}

this.maxCalls = num;

return this;
},

never: function never() {
return this.exactly(0);
},

once: function once() {
return this.exactly(1);
},

twice: function twice() {
return this.exactly(2);
},

thrice: function thrice() {
return this.exactly(3);
},

exactly: function exactly(num) {
if (typeof num !== «number») {
throw new TypeError("’" + num + "’ is not a number");
}

this.atLeast(num);
return this.atMost(num);
},

met: function met() {
return !this.failed && receivedMinCalls(this);
},

verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
if (receivedMaxCalls(this)) {
this.failed = true;
sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));
}

if («expectedThis» in this && this.expectedThis !== thisValue) {
sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +
this.expectedThis);
}

if (!(«expectedArguments» in this)) {
return;
}

if (!args) {
sinon.expectation.fail(this.method + " received no arguments, expected " +
sinon.format(this.expectedArguments));
}

if (args.length < this.expectedArguments.length) {
sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) +
"), expected " + sinon.format(this.expectedArguments));
}

if (this.expectsExactArgCount &&
args.length !== this.expectedArguments.length) {
sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) +
"), expected " + sinon.format(this.expectedArguments));
}

for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {

if (!verifyMatcher(this.expectedArguments[i], args[i])) {
sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
", didn’t match " + this.expectedArguments.toString());
}

if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
", expected " + sinon.format(this.expectedArguments));
}
}
},

allowsCall: function allowsCall(thisValue, args) {
if (this.met() && receivedMaxCalls(this)) {
return false;
}

if («expectedThis» in this && this.expectedThis !== thisValue) {
return false;
}

if (!(«expectedArguments» in this)) {
return true;
}

args = args || [];

if (args.length < this.expectedArguments.length) {
return false;
}

if (this.expectsExactArgCount &&
args.length !== this.expectedArguments.length) {
return false;
}

for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
if (!verifyMatcher(this.expectedArguments[i], args[i])) {
return false;
}

if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
return false;
}
}

return true;
},

withArgs: function withArgs() {
this.expectedArguments = slice.call(arguments);
return this;
},

withExactArgs: function withExactArgs() {
this.withArgs.apply(this, arguments);
this.expectsExactArgCount = true;
return this;
},

on: function on(thisValue) {
this.expectedThis = thisValue;
return this;
},

toString: function () {
var args = (this.expectedArguments || []).slice();

if (!this.expectsExactArgCount) {
push.call(args, "[…]");
}

var callStr = sinon.spyCall.toString.call({
proxy: this.method || «anonymous mock expectation»,
args: args
});

var message = callStr.replace(", […", "[, …") + " " +
expectedCallCountInWords(this);

if (this.met()) {
return «Expectation met: » + message;
}

return «Expected » + message + " (" +
callCountInWords(this.callCount) + ")";
},

verify: function verify() {
if (!this.met()) {
sinon.expectation.fail(this.toString());
} else {
sinon.expectation.pass(this.toString());
}

return true;
},

pass: function pass(message) {
sinon.assert.pass(message);
},

fail: function fail(message) {
var exception = new Error(message);
exception.name = «ExpectationError»;

throw exception;
}
};

sinon.mock = mock;
return mock;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./times_in_words");
require("./call");
require("./extend");
require("./match");
require("./spy");
require("./stub");
require("./format");

module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
* depend spy.js
* depend stub.js
* depend mock.js
*/
/**
* Collections of stubs, spies and mocks.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

var push = [].push;
var hasOwnProperty = Object.prototype.hasOwnProperty;

function getFakes(fakeCollection) {
if (!fakeCollection.fakes) {
fakeCollection.fakes = [];
}

return fakeCollection.fakes;
}

function each(fakeCollection, method) {
var fakes = getFakes(fakeCollection);

for (var i = 0, l = fakes.length; i < l; i += 1) {
if (typeof fakes[i][method] === «function») {
fakes[i][method]();
}
}
}

function compact(fakeCollection) {
var fakes = getFakes(fakeCollection);
var i = 0;
while (i < fakes.length) {
fakes.splice(i, 1);
}
}

function makeApi(sinon) {
var collection = {
verify: function resolve() {
each(this, «verify»);
},

restore: function restore() {
each(this, «restore»);
compact(this);
},

reset: function restore() {
each(this, «reset»);
},

verifyAndRestore: function verifyAndRestore() {
var exception;

try {
this.verify();
} catch (e) {
exception = e;
}

this.restore();

if (exception) {
throw exception;
}
},

add: function add(fake) {
push.call(getFakes(this), fake);
return fake;
},

spy: function spy() {
return this.add(sinon.spy.apply(sinon, arguments));
},

stub: function stub(object, property, value) {
if (property) {
var original = object[property];

if (typeof original !== «function») {
if (!hasOwnProperty.call(object, property)) {
throw new TypeError(«Cannot stub non-existent own property » + property);
}

object[property] = value;

return this.add({
restore: function () {
object[property] = original;
}
});
}
}
if (!property && !!object && typeof object === «object») {
var stubbedObj = sinon.stub.apply(sinon, arguments);

for (var prop in stubbedObj) {
if (typeof stubbedObj[prop] === «function») {
this.add(stubbedObj[prop]);
}
}

return stubbedObj;
}

return this.add(sinon.stub.apply(sinon, arguments));
},

mock: function mock() {
return this.add(sinon.mock.apply(sinon, arguments));
},

inject: function inject(obj) {
var col = this;

obj.spy = function () {
return col.spy.apply(col, arguments);
};

obj.stub = function () {
return col.stub.apply(col, arguments);
};

obj.mock = function () {
return col.mock.apply(col, arguments);
};

return obj;
}
};

sinon.collection = collection;
return collection;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./mock");
require("./spy");
require("./stub");
module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* Fake timer API
* setTimeout
* setInterval
* clearTimeout
* clearInterval
* tick
* reset
* Date
*
* Inspired by jsUnitMockTimeOut from JsUnit
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function () {

function makeApi(s, lol) {
/*global lolex */
var llx = typeof lolex !== «undefined»? lolex: lol;

s.useFakeTimers = function () {
var now;
var methods = Array.prototype.slice.call(arguments);

if (typeof methods[0] === «string») {
now = 0;
} else {
now = methods.shift();
}

var clock = llx.install(now || 0, methods);
clock.restore = clock.uninstall;
return clock;
};

s.clock = {
create: function (now) {
return llx.createClock(now);
}
};

s.timers = {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setImmediate: (typeof setImmediate !== «undefined»? setImmediate: undefined),
clearImmediate: (typeof clearImmediate !== «undefined»? clearImmediate: undefined),
setInterval: setInterval,
clearInterval: clearInterval,
Date: Date
};
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, epxorts, module, lolex) {
var core = require("./core");
makeApi(core, lolex);
module.exports = core;
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module, require(«lolex»));
} else {
makeApi(sinon); // eslint-disable-line no-undef
}
}());

/**
* Minimal Event interface implementation
*
* Original implementation by Sven Fuchs: gist.github.com/995028
* Modifications and tests by Christian Johansen.
*
* author Sven Fuchs (svenfuchs@artweb-design.de)
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2011 Sven Fuchs, Christian Johansen
*/
if (typeof sinon === «undefined») {
this.sinon = {};
}

(function () {

var push = [].push;

function makeApi(sinon) {
sinon.Event = function Event(type, bubbles, cancelable, target) {
this.initEvent(type, bubbles, cancelable, target);
};

sinon.Event.prototype = {
initEvent: function (type, bubbles, cancelable, target) {
this.type = type;
this.bubbles = bubbles;
this.cancelable = cancelable;
this.target = target;
},

stopPropagation: function () {},

preventDefault: function () {
this.defaultPrevented = true;
}
};

sinon.ProgressEvent = function ProgressEvent(type, progressEventRaw, target) {
this.initEvent(type, false, false, target);
this.loaded = progressEventRaw.loaded || null;
this.total = progressEventRaw.total || null;
this.lengthComputable = !!progressEventRaw.total;
};

sinon.ProgressEvent.prototype = new sinon.Event();

sinon.ProgressEvent.prototype.constructor = sinon.ProgressEvent;

sinon.CustomEvent = function CustomEvent(type, customData, target) {
this.initEvent(type, false, false, target);
this.detail = customData.detail || null;
};

sinon.CustomEvent.prototype = new sinon.Event();

sinon.CustomEvent.prototype.constructor = sinon.CustomEvent;

sinon.EventTarget = {
addEventListener: function addEventListener(event, listener) {
this.eventListeners = this.eventListeners || {};
this.eventListeners[event] = this.eventListeners[event] || [];
push.call(this.eventListeners[event], listener);
},

removeEventListener: function removeEventListener(event, listener) {
var listeners = this.eventListeners && this.eventListeners[event] || [];

for (var i = 0, l = listeners.length; i < l; ++i) {
if (listeners[i] === listener) {
return listeners.splice(i, 1);
}
}
},

dispatchEvent: function dispatchEvent(event) {
var type = event.type;
var listeners = this.eventListeners && this.eventListeners[type] || [];

for (var i = 0; i < listeners.length; i++) {
if (typeof listeners[i] === «function») {
listeners[i].call(this, event);
} else {
listeners[i].handleEvent(event);
}
}

return !!event.defaultPrevented;
}
};
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require) {
var sinon = require("./core");
makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require);
} else {
makeApi(sinon); // eslint-disable-line no-undef
}
}());

/**
* depend util/core.js
*/
/**
* Logs errors
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2014 Christian Johansen
*/
(function (sinonGlobal) {

// cache a reference to setTimeout, so that our reference won’t be stubbed out
// when using fake timers and errors will still get logged
// github.com/cjohansen/Sinon.JS/issues/381
var realSetTimeout = setTimeout;

function makeApi(sinon) {

function log() {}

function logError(label, err) {
var msg = label + " threw exception: ";

function throwLoggedError() {
err.message = msg + err.message;
throw err;
}

sinon.log(msg + "[" + err.name + "] " + err.message);

if (err.stack) {
sinon.log(err.stack);
}

if (logError.useImmediateExceptions) {
throwLoggedError();
} else {
logError.setTimeout(throwLoggedError, 0);
}
}

// When set to true, any errors logged will be thrown immediately;
// If set to false, the errors will be thrown in separate execution frame.
logError.useImmediateExceptions = false;

// wrap realSetTimeout with something we can stub in tests
logError.setTimeout = function (func, timeout) {
realSetTimeout(func, timeout);
};

var exports = {};
exports.log = sinon.log = log;
exports.logError = sinon.logError = logError;

return exports;
}

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
module.exports = makeApi(sinon);
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend core.js
* depend ../extend.js
* depend event.js
* depend ../log_error.js
*/
/**
* Fake XDomainRequest object
*/
if (typeof sinon === «undefined») {
this.sinon = {};
}

// wrapper for global
(function (global) {

var xdr = { XDomainRequest: global.XDomainRequest };
xdr.GlobalXDomainRequest = global.XDomainRequest;
xdr.supportsXDR = typeof xdr.GlobalXDomainRequest !== «undefined»;
xdr.workingXDR = xdr.supportsXDR? xdr.GlobalXDomainRequest: false;

function makeApi(sinon) {
sinon.xdr = xdr;

function FakeXDomainRequest() {
this.readyState = FakeXDomainRequest.UNSENT;
this.requestBody = null;
this.requestHeaders = {};
this.status = 0;
this.timeout = null;

if (typeof FakeXDomainRequest.onCreate === «function») {
FakeXDomainRequest.onCreate(this);
}
}

function verifyState(x) {
if (x.readyState !== FakeXDomainRequest.OPENED) {
throw new Error(«INVALID_STATE_ERR»);
}

if (x.sendFlag) {
throw new Error(«INVALID_STATE_ERR»);
}
}

function verifyRequestSent(x) {
if (x.readyState === FakeXDomainRequest.UNSENT) {
throw new Error(«Request not sent»);
}
if (x.readyState === FakeXDomainRequest.DONE) {
throw new Error(«Request done»);
}
}

function verifyResponseBodyType(body) {
if (typeof body !== «string») {
var error = new Error(«Attempted to respond to fake XDomainRequest with » +
body + ", which is not a string.");
error.name = «InvalidBodyException»;
throw error;
}
}

sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
open: function open(method, url) {
this.method = method;
this.url = url;

this.responseText = null;
this.sendFlag = false;

this.readyStateChange(FakeXDomainRequest.OPENED);
},

readyStateChange: function readyStateChange(state) {
this.readyState = state;
var eventName = "";
switch (this.readyState) {
case FakeXDomainRequest.UNSENT:
break;
case FakeXDomainRequest.OPENED:
break;
case FakeXDomainRequest.LOADING:
if (this.sendFlag) {
//raise the progress event
eventName = «onprogress»;
}
break;
case FakeXDomainRequest.DONE:
if (this.isTimeout) {
eventName = «ontimeout»;
} else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
eventName = «onerror»;
} else {
eventName = «onload»;
}
break;
}

// raising event (if defined)
if (eventName) {
if (typeof this[eventName] === «function») {
try {
this[eventName]();
} catch (e) {
sinon.logError(«Fake XHR » + eventName + " handler", e);
}
}
}
},

send: function send(data) {
verifyState(this);

if (!/^(get|head)$/i.test(this.method)) {
this.requestBody = data;
}
this.requestHeaders[«Content-Type»] = «text/plain;charset=utf-8»;

this.errorFlag = false;
this.sendFlag = true;
this.readyStateChange(FakeXDomainRequest.OPENED);

if (typeof this.onSend === «function») {
this.onSend(this);
}
},

abort: function abort() {
this.aborted = true;
this.responseText = null;
this.errorFlag = true;

if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
this.readyStateChange(sinon.FakeXDomainRequest.DONE);
this.sendFlag = false;
}
},

setResponseBody: function setResponseBody(body) {
verifyRequestSent(this);
verifyResponseBodyType(body);

var chunkSize = this.chunkSize || 10;
var index = 0;
this.responseText = "";

do {
this.readyStateChange(FakeXDomainRequest.LOADING);
this.responseText += body.substring(index, index + chunkSize);
index += chunkSize;
} while (index < body.length);

this.readyStateChange(FakeXDomainRequest.DONE);
},

respond: function respond(status, contentType, body) {
// content-type ignored, since XDomainRequest does not carry this
// we keep the same syntax for respond(…) as for FakeXMLHttpRequest to ease
// test integration across browsers
this.status = typeof status === «number»? status: 200;
this.setResponseBody(body || "");
},

simulatetimeout: function simulatetimeout() {
this.status = 0;
this.isTimeout = true;
// Access to this should actually throw an error
this.responseText = undefined;
this.readyStateChange(FakeXDomainRequest.DONE);
}
});

sinon.extend(FakeXDomainRequest, {
UNSENT: 0,
OPENED: 1,
LOADING: 3,
DONE: 4
});

sinon.useFakeXDomainRequest = function useFakeXDomainRequest() {
sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
if (xdr.supportsXDR) {
global.XDomainRequest = xdr.GlobalXDomainRequest;
}

delete sinon.FakeXDomainRequest.restore;

if (keepOnCreate !== true) {
delete sinon.FakeXDomainRequest.onCreate;
}
};
if (xdr.supportsXDR) {
global.XDomainRequest = sinon.FakeXDomainRequest;
}
return sinon.FakeXDomainRequest;
};

sinon.FakeXDomainRequest = FakeXDomainRequest;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("../extend");
require("./event");
require("../log_error");
makeApi(sinon);
module.exports = sinon;
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
} else {
makeApi(sinon); // eslint-disable-line no-undef
}
})(typeof global !== «undefined»? global: self);

/**
* depend core.js
* depend ../extend.js
* depend event.js
* depend ../log_error.js
*/
/**
* Fake XMLHttpRequest object
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal, global) {

function getWorkingXHR(globalScope) {
var supportsXHR = typeof globalScope.XMLHttpRequest !== «undefined»;
if (supportsXHR) {
return globalScope.XMLHttpRequest;
}

var supportsActiveX = typeof globalScope.ActiveXObject !== «undefined»;
if (supportsActiveX) {
return function () {
return new globalScope.ActiveXObject(«MSXML2.XMLHTTP.3.0»);
};
}

return false;
}

var supportsProgress = typeof ProgressEvent !== «undefined»;
var supportsCustomEvent = typeof CustomEvent !== «undefined»;
var supportsFormData = typeof FormData !== «undefined»;
var supportsArrayBuffer = typeof ArrayBuffer !== «undefined»;
var supportsBlob = typeof Blob === «function»;
var sinonXhr = { XMLHttpRequest: global.XMLHttpRequest };
sinonXhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
sinonXhr.GlobalActiveXObject = global.ActiveXObject;
sinonXhr.supportsActiveX = typeof sinonXhr.GlobalActiveXObject !== «undefined»;
sinonXhr.supportsXHR = typeof sinonXhr.GlobalXMLHttpRequest !== «undefined»;
sinonXhr.workingXHR = getWorkingXHR(global);
sinonXhr.supportsCORS = sinonXhr.supportsXHR && «withCredentials» in (new sinonXhr.GlobalXMLHttpRequest());

var unsafeHeaders = {
«Accept-Charset»: true,
«Accept-Encoding»: true,
Connection: true,
«Content-Length»: true,
Cookie: true,
Cookie2: true,
«Content-Transfer-Encoding»: true,
Date: true,
Expect: true,
Host: true,
«Keep-Alive»: true,
Referer: true,
TE: true,
Trailer: true,
«Transfer-Encoding»: true,
Upgrade: true,
«User-Agent»: true,
Via: true
};

// An upload object is created for each
// FakeXMLHttpRequest and allows upload
// events to be simulated using uploadProgress
// and uploadError.
function UploadProgress() {
this.eventListeners = {
progress: [],
load: [],
abort: [],
error: []
};
}

UploadProgress.prototype.addEventListener = function addEventListener(event, listener) {
this.eventListeners[event].push(listener);
};

UploadProgress.prototype.removeEventListener = function removeEventListener(event, listener) {
var listeners = this.eventListeners[event] || [];

for (var i = 0, l = listeners.length; i < l; ++i) {
if (listeners[i] === listener) {
return listeners.splice(i, 1);
}
}
};

UploadProgress.prototype.dispatchEvent = function dispatchEvent(event) {
var listeners = this.eventListeners[event.type] || [];

for (var i = 0, listener; (listener = listeners[i]) != null; i++) {
listener(event);
}
};

// Note that for FakeXMLHttpRequest to work pre ES5
// we lose some of the alignment with the spec.
// To ensure as close a match as possible,
// set responseType before calling open, send or respond;
function FakeXMLHttpRequest() {
this.readyState = FakeXMLHttpRequest.UNSENT;
this.requestHeaders = {};
this.requestBody = null;
this.status = 0;
this.statusText = "";
this.upload = new UploadProgress();
this.responseType = "";
this.response = "";
if (sinonXhr.supportsCORS) {
this.withCredentials = false;
}

var xhr = this;
var events = [«loadstart», «load», «abort», «loadend»];

function addEventListener(eventName) {
xhr.addEventListener(eventName, function (event) {
var listener = xhr[«on» + eventName];

if (listener && typeof listener === «function») {
listener.call(this, event);
}
});
}

for (var i = events.length — 1; i >= 0; i—) {
addEventListener(events[i]);
}

if (typeof FakeXMLHttpRequest.onCreate === «function») {
FakeXMLHttpRequest.onCreate(this);
}
}

function verifyState(xhr) {
if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
throw new Error(«INVALID_STATE_ERR»);
}

if (xhr.sendFlag) {
throw new Error(«INVALID_STATE_ERR»);
}
}

function getHeader(headers, header) {
header = header.toLowerCase();

for (var h in headers) {
if (h.toLowerCase() === header) {
return h;
}
}

return null;
}

// filtering to enable a white-list version of Sinon FakeXhr,
// where whitelisted requests are passed through to real XHR
function each(collection, callback) {
if (!collection) {
return;
}

for (var i = 0, l = collection.length; i < l; i += 1) {
callback(collection[i]);
}
}
function some(collection, callback) {
for (var index = 0; index < collection.length; index++) {
if (callback(collection[index]) === true) {
return true;
}
}
return false;
}
// largest arity in XHR is 5 — XHR#open
var apply = function (obj, method, args) {
switch (args.length) {
case 0: return obj[method]();
case 1: return obj[method](args[0]);
case 2: return obj[method](args[0], args[1]);
case 3: return obj[method](args[0], args[1], args[2]);
case 4: return obj[method](args[0], args[1], args[2], args[3]);
case 5: return obj[method](args[0], args[1], args[2], args[3], args[4]);
}
};

FakeXMLHttpRequest.filters = [];
FakeXMLHttpRequest.addFilter = function addFilter(fn) {
this.filters.push(fn);
};
var IE6Re = /MSIE 6/;
FakeXMLHttpRequest.defake = function defake(fakeXhr, xhrArgs) {
var xhr = new sinonXhr.workingXHR(); // eslint-disable-line new-cap

each([
«open»,
«setRequestHeader»,
«send»,
«abort»,
«getResponseHeader»,
«getAllResponseHeaders»,
«addEventListener»,
«overrideMimeType»,
«removeEventListener»
], function (method) {
fakeXhr[method] = function () {
return apply(xhr, method, arguments);
};
});

var copyAttrs = function (args) {
each(args, function (attr) {
try {
fakeXhr[attr] = xhr[attr];
} catch (e) {
if (!IE6Re.test(navigator.userAgent)) {
throw e;
}
}
});
};

var stateChange = function stateChange() {
fakeXhr.readyState = xhr.readyState;
if (xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
copyAttrs([«status», «statusText»]);
}
if (xhr.readyState >= FakeXMLHttpRequest.LOADING) {
copyAttrs([«responseText», «response»]);
}
if (xhr.readyState === FakeXMLHttpRequest.DONE) {
copyAttrs([«responseXML»]);
}
if (fakeXhr.onreadystatechange) {
fakeXhr.onreadystatechange.call(fakeXhr, { target: fakeXhr });
}
};

if (xhr.addEventListener) {
for (var event in fakeXhr.eventListeners) {
if (fakeXhr.eventListeners.hasOwnProperty(event)) {

/*eslint-disable no-loop-func*/
each(fakeXhr.eventListeners[event], function (handler) {
xhr.addEventListener(event, handler);
});
/*eslint-enable no-loop-func*/
}
}
xhr.addEventListener(«readystatechange», stateChange);
} else {
xhr.onreadystatechange = stateChange;
}
apply(xhr, «open», xhrArgs);
};
FakeXMLHttpRequest.useFilters = false;

function verifyRequestOpened(xhr) {
if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
throw new Error(«INVALID_STATE_ERR — » + xhr.readyState);
}
}

function verifyRequestSent(xhr) {
if (xhr.readyState === FakeXMLHttpRequest.DONE) {
throw new Error(«Request done»);
}
}

function verifyHeadersReceived(xhr) {
if (xhr.async && xhr.readyState !== FakeXMLHttpRequest.HEADERS_RECEIVED) {
throw new Error(«No headers received»);
}
}

function verifyResponseBodyType(body) {
if (typeof body !== «string») {
var error = new Error(«Attempted to respond to fake XMLHttpRequest with » +
body + ", which is not a string.");
error.name = «InvalidBodyException»;
throw error;
}
}

function convertToArrayBuffer(body) {
var buffer = new ArrayBuffer(body.length);
var view = new Uint8Array(buffer);
for (var i = 0; i < body.length; i++) {
var charCode = body.charCodeAt(i);
if (charCode >= 256) {
throw new TypeError(«arraybuffer or blob responseTypes require binary string, » +
«invalid character » + body[i] + " found.");
}
view[i] = charCode;
}
return buffer;
}

function isXmlContentType(contentType) {
return !contentType || /(text\/xml)|(application\/xml)|(\+xml)/.test(contentType);
}

function convertResponseBody(responseType, contentType, body) {
if (responseType === "" || responseType === «text») {
return body;
} else if (supportsArrayBuffer && responseType === «arraybuffer») {
return convertToArrayBuffer(body);
} else if (responseType === «json») {
try {
return JSON.parse(body);
} catch (e) {
// Return parsing failure as null
return null;
}
} else if (supportsBlob && responseType === «blob») {
var blobOptions = {};
if (contentType) {
blobOptions.type = contentType;
}
return new Blob([convertToArrayBuffer(body)], blobOptions);
} else if (responseType === «document») {
if (isXmlContentType(contentType)) {
return FakeXMLHttpRequest.parseXML(body);
}
return null;
}
throw new Error(«Invalid responseType » + responseType);
}

function clearResponse(xhr) {
if (xhr.responseType === "" || xhr.responseType === «text») {
xhr.response = xhr.responseText = "";
} else {
xhr.response = xhr.responseText = null;
}
xhr.responseXML = null;
}

FakeXMLHttpRequest.parseXML = function parseXML(text) {
// Treat empty string as parsing failure
if (text !== "") {
try {
if (typeof DOMParser !== «undefined») {
var parser = new DOMParser();
return parser.parseFromString(text, «text/xml»);
}
var xmlDoc = new window.ActiveXObject(«Microsoft.XMLDOM»);
xmlDoc.async = «false»;
xmlDoc.loadXML(text);
return xmlDoc;
} catch (e) {
// Unable to parse XML — no biggie
}
}

return null;
};

FakeXMLHttpRequest.statusCodes = {
100: «Continue»,
101: «Switching Protocols»,
200: «OK»,
201: «Created»,
202: «Accepted»,
203: «Non-Authoritative Information»,
204: «No Content»,
205: «Reset Content»,
206: «Partial Content»,
207: «Multi-Status»,
300: «Multiple Choice»,
301: «Moved Permanently»,
302: «Found»,
303: «See Other»,
304: «Not Modified»,
305: «Use Proxy»,
307: «Temporary Redirect»,
400: «Bad Request»,
401: «Unauthorized»,
402: «Payment Required»,
403: «Forbidden»,
404: «Not Found»,
405: «Method Not Allowed»,
406: «Not Acceptable»,
407: «Proxy Authentication Required»,
408: «Request Timeout»,
409: «Conflict»,
410: «Gone»,
411: «Length Required»,
412: «Precondition Failed»,
413: «Request Entity Too Large»,
414: «Request-URI Too Long»,
415: «Unsupported Media Type»,
416: «Requested Range Not Satisfiable»,
417: «Expectation Failed»,
422: «Unprocessable Entity»,
500: «Internal Server Error»,
501: «Not Implemented»,
502: «Bad Gateway»,
503: «Service Unavailable»,
504: «Gateway Timeout»,
505: «HTTP Version Not Supported»
};

function makeApi(sinon) {
sinon.xhr = sinonXhr;

sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {
async: true,

open: function open(method, url, async, username, password) {
this.method = method;
this.url = url;
this.async = typeof async === «boolean»? async: true;
this.username = username;
this.password = password;
clearResponse(this);
this.requestHeaders = {};
this.sendFlag = false;

if (FakeXMLHttpRequest.useFilters === true) {
var xhrArgs = arguments;
var defake = some(FakeXMLHttpRequest.filters, function (filter) {
return filter.apply(this, xhrArgs);
});
if (defake) {
return FakeXMLHttpRequest.defake(this, arguments);
}
}
this.readyStateChange(FakeXMLHttpRequest.OPENED);
},

readyStateChange: function readyStateChange(state) {
this.readyState = state;

var readyStateChangeEvent = new sinon.Event(«readystatechange», false, false, this);

if (typeof this.onreadystatechange === «function») {
try {
this.onreadystatechange(readyStateChangeEvent);
} catch (e) {
sinon.logError(«Fake XHR onreadystatechange handler», e);
}
}

switch (this.readyState) {
case FakeXMLHttpRequest.DONE:
if (supportsProgress) {
this.upload.dispatchEvent(new sinon.ProgressEvent(«progress», {loaded: 100, total: 100}));
this.dispatchEvent(new sinon.ProgressEvent(«progress», {loaded: 100, total: 100}));
}
this.upload.dispatchEvent(new sinon.Event(«load», false, false, this));
this.dispatchEvent(new sinon.Event(«load», false, false, this));
this.dispatchEvent(new sinon.Event(«loadend», false, false, this));
break;
}

this.dispatchEvent(readyStateChangeEvent);
},

setRequestHeader: function setRequestHeader(header, value) {
verifyState(this);

if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
throw new Error(«Refused to set unsafe header \»" + header + "\"");
}

if (this.requestHeaders[header]) {
this.requestHeaders[header] += "," + value;
} else {
this.requestHeaders[header] = value;
}
},

// Helps testing
setResponseHeaders: function setResponseHeaders(headers) {
verifyRequestOpened(this);
this.responseHeaders = {};

for (var header in headers) {
if (headers.hasOwnProperty(header)) {
this.responseHeaders[header] = headers[header];
}
}

if (this.async) {
this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
} else {
this.readyState = FakeXMLHttpRequest.HEADERS_RECEIVED;
}
},

// Currently treats ALL data as a DOMString (i.e. no Document)
send: function send(data) {
verifyState(this);

if (!/^(get|head)$/i.test(this.method)) {
var contentType = getHeader(this.requestHeaders, «Content-Type»);
if (this.requestHeaders[contentType]) {
var value = this.requestHeaders[contentType].split(";");
this.requestHeaders[contentType] = value[0] + ";charset=utf-8";
} else if (supportsFormData && !(data instanceof FormData)) {
this.requestHeaders[«Content-Type»] = «text/plain;charset=utf-8»;
}

this.requestBody = data;
}

this.errorFlag = false;
this.sendFlag = this.async;
clearResponse(this);
this.readyStateChange(FakeXMLHttpRequest.OPENED);

if (typeof this.onSend === «function») {
this.onSend(this);
}

this.dispatchEvent(new sinon.Event(«loadstart», false, false, this));
},

abort: function abort() {
this.aborted = true;
clearResponse(this);
this.errorFlag = true;
this.requestHeaders = {};
this.responseHeaders = {};

if (this.readyState > FakeXMLHttpRequest.UNSENT && this.sendFlag) {
this.readyStateChange(FakeXMLHttpRequest.DONE);
this.sendFlag = false;
}

this.readyState = FakeXMLHttpRequest.UNSENT;

this.dispatchEvent(new sinon.Event(«abort», false, false, this));

this.upload.dispatchEvent(new sinon.Event(«abort», false, false, this));

if (typeof this.onerror === «function») {
this.onerror();
}
},

getResponseHeader: function getResponseHeader(header) {
if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
return null;
}

if (/^Set-Cookie2?$/i.test(header)) {
return null;
}

header = getHeader(this.responseHeaders, header);

return this.responseHeaders[header] || null;
},

getAllResponseHeaders: function getAllResponseHeaders() {
if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
return "";
}

var headers = "";

for (var header in this.responseHeaders) {
if (this.responseHeaders.hasOwnProperty(header) &&
!/^Set-Cookie2?$/i.test(header)) {
headers += header + ": " + this.responseHeaders[header] + "\r\n";
}
}

return headers;
},

setResponseBody: function setResponseBody(body) {
verifyRequestSent(this);
verifyHeadersReceived(this);
verifyResponseBodyType(body);
var contentType = this.getResponseHeader(«Content-Type»);

var isTextResponse = this.responseType === "" || this.responseType === «text»;
clearResponse(this);
if (this.async) {
var chunkSize = this.chunkSize || 10;
var index = 0;

do {
this.readyStateChange(FakeXMLHttpRequest.LOADING);

if (isTextResponse) {
this.responseText = this.response += body.substring(index, index + chunkSize);
}
index += chunkSize;
} while (index < body.length);
}

this.response = convertResponseBody(this.responseType, contentType, body);
if (isTextResponse) {
this.responseText = this.response;
}

if (this.responseType === «document») {
this.responseXML = this.response;
} else if (this.responseType === "" && isXmlContentType(contentType)) {
this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
}
this.readyStateChange(FakeXMLHttpRequest.DONE);
},

respond: function respond(status, headers, body) {
this.status = typeof status === «number»? status: 200;
this.statusText = FakeXMLHttpRequest.statusCodes[this.status];
this.setResponseHeaders(headers || {});
this.setResponseBody(body || "");
},

uploadProgress: function uploadProgress(progressEventRaw) {
if (supportsProgress) {
this.upload.dispatchEvent(new sinon.ProgressEvent(«progress», progressEventRaw));
}
},

downloadProgress: function downloadProgress(progressEventRaw) {
if (supportsProgress) {
this.dispatchEvent(new sinon.ProgressEvent(«progress», progressEventRaw));
}
},

uploadError: function uploadError(error) {
if (supportsCustomEvent) {
this.upload.dispatchEvent(new sinon.CustomEvent(«error», {detail: error}));
}
}
});

sinon.extend(FakeXMLHttpRequest, {
UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4
});

sinon.useFakeXMLHttpRequest = function () {
FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
if (sinonXhr.supportsXHR) {
global.XMLHttpRequest = sinonXhr.GlobalXMLHttpRequest;
}

if (sinonXhr.supportsActiveX) {
global.ActiveXObject = sinonXhr.GlobalActiveXObject;
}

delete FakeXMLHttpRequest.restore;

if (keepOnCreate !== true) {
delete FakeXMLHttpRequest.onCreate;
}
};
if (sinonXhr.supportsXHR) {
global.XMLHttpRequest = FakeXMLHttpRequest;
}

if (sinonXhr.supportsActiveX) {
global.ActiveXObject = function ActiveXObject(objId) {
if (objId === «Microsoft.XMLHTTP» || /^Msxml2\.XMLHTTP/i.test(objId)) {

return new FakeXMLHttpRequest();
}

return new sinonXhr.GlobalActiveXObject(objId);
};
}

return FakeXMLHttpRequest;
};

sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("../extend");
require("./event");
require("../log_error");
makeApi(sinon);
module.exports = sinon;
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon, // eslint-disable-line no-undef
typeof global !== «undefined»? global: self
));

/**
* depend fake_xdomain_request.js
* depend fake_xml_http_request.js
* depend ../format.js
* depend ../log_error.js
*/
/**
* The Sinon «server» mimics a web server that receives requests from
* sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
* both synchronously and asynchronously. To respond synchronuously, canned
* answers have to be provided upfront.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function () {

var push = [].push;

function responseArray(handler) {
var response = handler;

if (Object.prototype.toString.call(handler) !== "[object Array]") {
response = [200, {}, handler];
}

if (typeof response[2] !== «string») {
throw new TypeError(«Fake server response body should be string, but was » +
typeof response[2]);
}

return response;
}

var wloc = typeof window !== «undefined»? window.location: {};
var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);

function matchOne(response, reqMethod, reqUrl) {
var rmeth = response.method;
var matchMethod = !rmeth || rmeth.toLowerCase() === reqMethod.toLowerCase();
var url = response.url;
var matchUrl = !url || url === reqUrl || (typeof url.test === «function» && url.test(reqUrl));

return matchMethod && matchUrl;
}

function match(response, request) {
var requestUrl = request.url;

if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
requestUrl = requestUrl.replace(rCurrLoc, "");
}

if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
if (typeof response.response === «function») {
var ru = response.url;
var args = [request].concat(ru && typeof ru.exec === «function»? ru.exec(requestUrl).slice(1): []);
return response.response.apply(response, args);
}

return true;
}

return false;
}

function makeApi(sinon) {
sinon.fakeServer = {
create: function (config) {
var server = sinon.create(this);
server.configure(config);
if (!sinon.xhr.supportsCORS) {
this.xhr = sinon.useFakeXDomainRequest();
} else {
this.xhr = sinon.useFakeXMLHttpRequest();
}
server.requests = [];

this.xhr.onCreate = function (xhrObj) {
server.addRequest(xhrObj);
};

return server;
},
configure: function (config) {
var whitelist = {
«autoRespond»: true,
«autoRespondAfter»: true,
«respondImmediately»: true,
«fakeHTTPMethods»: true
};
var setting;

config = config || {};
for (setting in config) {
if (whitelist.hasOwnProperty(setting) && config.hasOwnProperty(setting)) {
this[setting] = config[setting];
}
}
},
addRequest: function addRequest(xhrObj) {
var server = this;
push.call(this.requests, xhrObj);

xhrObj.onSend = function () {
server.handleRequest(this);

if (server.respondImmediately) {
server.respond();
} else if (server.autoRespond && !server.responding) {
setTimeout(function () {
server.responding = false;
server.respond();
}, server.autoRespondAfter || 10);

server.responding = true;
}
};
},

getHTTPMethod: function getHTTPMethod(request) {
if (this.fakeHTTPMethods && /post/i.test(request.method)) {
var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
return matches? matches[1]: request.method;
}

return request.method;
},

handleRequest: function handleRequest(xhr) {
if (xhr.async) {
if (!this.queue) {
this.queue = [];
}

push.call(this.queue, xhr);
} else {
this.processRequest(xhr);
}
},

log: function log(response, request) {
var str;

str = «Request:\n» + sinon.format(request) + "\n\n";
str += «Response:\n» + sinon.format(response) + "\n\n";

sinon.log(str);
},

respondWith: function respondWith(method, url, body) {
if (arguments.length === 1 && typeof method !== «function») {
this.response = responseArray(method);
return;
}

if (!this.responses) {
this.responses = [];
}

if (arguments.length === 1) {
body = method;
url = method = null;
}

if (arguments.length === 2) {
body = url;
url = method;
method = null;
}

push.call(this.responses, {
method: method,
url: url,
response: typeof body === «function»? body: responseArray(body)
});
},

respond: function respond() {
if (arguments.length > 0) {
this.respondWith.apply(this, arguments);
}

var queue = this.queue || [];
var requests = queue.splice(0, queue.length);

for (var i = 0; i < requests.length; i++) {
this.processRequest(requests[i]);
}
},

processRequest: function processRequest(request) {
try {
if (request.aborted) {
return;
}

var response = this.response || [404, {}, ""];

if (this.responses) {
for (var l = this.responses.length, i = l — 1; i >= 0; i—) {
if (match.call(this, this.responses[i], request)) {
response = this.responses[i].response;
break;
}
}
}

if (request.readyState !== 4) {
this.log(response, request);

request.respond(response[0], response[1], response[2]);
}
} catch (e) {
sinon.logError(«Fake server request processing», e);
}
},

restore: function restore() {
return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
}
};
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./core");
require("./fake_xdomain_request");
require("./fake_xml_http_request");
require("../format");
makeApi(sinon);
module.exports = sinon;
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
} else {
makeApi(sinon); // eslint-disable-line no-undef
}
}());

/**
* depend fake_server.js
* depend fake_timers.js
*/
/**
* Add-on for sinon.fakeServer that automatically handles a fake timer along with
* the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery
* 1.3.x, which does not use xhr object’s onreadystatehandler at all — instead,
* it polls the object for completion with setInterval. Dispite the direct
* motivation, there is nothing jQuery-specific in this file, so it can be used
* in any environment where the ajax implementation depends on setInterval or
* setTimeout.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function () {

function makeApi(sinon) {
function Server() {}
Server.prototype = sinon.fakeServer;

sinon.fakeServerWithClock = new Server();

sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {
if (xhr.async) {
if (typeof setTimeout.clock === «object») {
this.clock = setTimeout.clock;
} else {
this.clock = sinon.useFakeTimers();
this.resetClock = true;
}

if (!this.longestTimeout) {
var clockSetTimeout = this.clock.setTimeout;
var clockSetInterval = this.clock.setInterval;
var server = this;

this.clock.setTimeout = function (fn, timeout) {
server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);

return clockSetTimeout.apply(this, arguments);
};

this.clock.setInterval = function (fn, timeout) {
server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);

return clockSetInterval.apply(this, arguments);
};
}
}

return sinon.fakeServer.addRequest.call(this, xhr);
};

sinon.fakeServerWithClock.respond = function respond() {
var returnVal = sinon.fakeServer.respond.apply(this, arguments);

if (this.clock) {
this.clock.tick(this.longestTimeout || 0);
this.longestTimeout = 0;

if (this.resetClock) {
this.clock.restore();
this.resetClock = false;
}
}

return returnVal;
};

sinon.fakeServerWithClock.restore = function restore() {
if (this.clock) {
this.clock.restore();
}

return sinon.fakeServer.restore.apply(this, arguments);
};
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require) {
var sinon = require("./core");
require("./fake_server");
require("./fake_timers");
makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require);
} else {
makeApi(sinon); // eslint-disable-line no-undef
}
}());

/**
* depend util/core.js
* depend extend.js
* depend collection.js
* depend util/fake_timers.js
* depend util/fake_server_with_clock.js
*/
/**
* Manages fake collections as well as fake utilities such as Sinon’s
* timers and fake XHR implementation in one convenient object.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

function makeApi(sinon) {
var push = [].push;

function exposeValue(sandbox, config, key, value) {
if (!value) {
return;
}

if (config.injectInto && !(key in config.injectInto)) {
config.injectInto[key] = value;
sandbox.injectedKeys.push(key);
} else {
push.call(sandbox.args, value);
}
}

function prepareSandboxFromConfig(config) {
var sandbox = sinon.create(sinon.sandbox);

if (config.useFakeServer) {
if (typeof config.useFakeServer === «object») {
sandbox.serverPrototype = config.useFakeServer;
}

sandbox.useFakeServer();
}

if (config.useFakeTimers) {
if (typeof config.useFakeTimers === «object») {
sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);
} else {
sandbox.useFakeTimers();
}
}

return sandbox;
}

sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {
useFakeTimers: function useFakeTimers() {
this.clock = sinon.useFakeTimers.apply(sinon, arguments);

return this.add(this.clock);
},

serverPrototype: sinon.fakeServer,

useFakeServer: function useFakeServer() {
var proto = this.serverPrototype || sinon.fakeServer;

if (!proto || !proto.create) {
return null;
}

this.server = proto.create();
return this.add(this.server);
},

inject: function (obj) {
sinon.collection.inject.call(this, obj);

if (this.clock) {
obj.clock = this.clock;
}

if (this.server) {
obj.server = this.server;
obj.requests = this.server.requests;
}

obj.match = sinon.match;

return obj;
},

restore: function () {
sinon.collection.restore.apply(this, arguments);
this.restoreContext();
},

restoreContext: function () {
if (this.injectedKeys) {
for (var i = 0, j = this.injectedKeys.length; i < j; i++) {
delete this.injectInto[this.injectedKeys[i]];
}
this.injectedKeys = [];
}
},

create: function (config) {
if (!config) {
return sinon.create(sinon.sandbox);
}

var sandbox = prepareSandboxFromConfig(config);
sandbox.args = sandbox.args || [];
sandbox.injectedKeys = [];
sandbox.injectInto = config.injectInto;
var prop,
value;
var exposed = sandbox.inject({});

if (config.properties) {
for (var i = 0, l = config.properties.length; i < l; i++) {
prop = config.properties[i];
value = exposed[prop] || prop === «sandbox» && sandbox;
exposeValue(sandbox, config, prop, value);
}
} else {
exposeValue(sandbox, config, «sandbox», value);
}

return sandbox;
},

match: sinon.match
});

sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;

return sinon.sandbox;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./extend");
require("./util/fake_server_with_clock");
require("./util/fake_timers");
require("./collection");
module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend util/core.js
* depend sandbox.js
*/
/**
* Test function, sandboxes fakes
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

function makeApi(sinon) {
var slice = Array.prototype.slice;

function test(callback) {
var type = typeof callback;

if (type !== «function») {
throw new TypeError(«sinon.test needs to wrap a test function, got » + type);
}

function sinonSandboxedTest() {
var config = sinon.getConfig(sinon.config);
config.injectInto = config.injectIntoThis && this || config.injectInto;
var sandbox = sinon.sandbox.create(config);
var args = slice.call(arguments);
var oldDone = args.length && args[args.length — 1];
var exception, result;

if (typeof oldDone === «function») {
args[args.length — 1] = function sinonDone(res) {
if (res) {
sandbox.restore();
} else {
sandbox.verifyAndRestore();
}
oldDone(res);
};
}

try {
result = callback.apply(this, args.concat(sandbox.args));
} catch (e) {
exception = e;
}

if (typeof oldDone !== «function») {
if (typeof exception !== «undefined») {
sandbox.restore();
throw exception;
} else {
sandbox.verifyAndRestore();
}
}

return result;
}

if (callback.length) {
return function sinonAsyncSandboxedTest(done) { // eslint-disable-line no-unused-vars
return sinonSandboxedTest.apply(this, arguments);
};
}

return sinonSandboxedTest;
}

test.config = {
injectIntoThis: true,
injectInto: null,
properties: [«spy», «stub», «mock», «clock», «server», «requests»],
useFakeTimers: true,
useFakeServer: true
};

sinon.test = test;
return test;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var core = require("./util/core");
require("./sandbox");
module.exports = makeApi(core);
}

if (isAMD) {
define(loadDependencies);
} else if (isNode) {
loadDependencies(require, module.exports, module);
} else if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(typeof sinon === «object» && sinon || null)); // eslint-disable-line no-undef

/**
* depend util/core.js
* depend test.js
*/
/**
* Test case, sandboxes all test functions
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal) {

function createTest(property, setUp, tearDown) {
return function () {
if (setUp) {
setUp.apply(this, arguments);
}

var exception, result;

try {
result = property.apply(this, arguments);
} catch (e) {
exception = e;
}

if (tearDown) {
tearDown.apply(this, arguments);
}

if (exception) {
throw exception;
}

return result;
};
}

function makeApi(sinon) {
function testCase(tests, prefix) {
if (!tests || typeof tests !== «object») {
throw new TypeError(«sinon.testCase needs an object with test functions»);
}

prefix = prefix || «test»;
var rPrefix = new RegExp("^" + prefix);
var methods = {};
var setUp = tests.setUp;
var tearDown = tests.tearDown;
var testName,
property,
method;

for (testName in tests) {
if (tests.hasOwnProperty(testName) && !/^(setUp|tearDown)$/.test(testName)) {
property = tests[testName];

if (typeof property === «function» && rPrefix.test(testName)) {
method = property;

if (setUp || tearDown) {
method = createTest(property, setUp, tearDown);
}

methods[testName] = sinon.test(method);
} else {
methods[testName] = tests[testName];
}
}
}

return methods;
}

sinon.testCase = testCase;
return testCase;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var core = require("./util/core");
require("./test");
module.exports = makeApi(core);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon // eslint-disable-line no-undef
));

/**
* depend times_in_words.js
* depend util/core.js
* depend match.js
* depend format.js
*/
/**
* Assertions matching the test spy retrieval interface.
*
* author Christian Johansen (christian@cjohansen.no)
* @license BSD
*
* Copyright 2010-2013 Christian Johansen
*/
(function (sinonGlobal, global) {

var slice = Array.prototype.slice;

function makeApi(sinon) {
var assert;

function verifyIsStub() {
var method;

for (var i = 0, l = arguments.length; i < l; ++i) {
method = arguments[i];

if (!method) {
assert.fail(«fake is not a spy»);
}

if (method.proxy && method.proxy.isSinonProxy) {
verifyIsStub(method.proxy);
} else {
if (typeof method !== «function») {
assert.fail(method + " is not a function");
}

if (typeof method.getCall !== «function») {
assert.fail(method + " is not stubbed");
}
}

}
}

function failAssertion(object, msg) {
object = object || global;
var failMethod = object.fail || assert.fail;
failMethod.call(object, msg);
}

function mirrorPropAsAssertion(name, method, message) {
if (arguments.length === 2) {
message = method;
method = name;
}

assert[name] = function (fake) {
verifyIsStub(fake);

var args = slice.call(arguments, 1);
var failed = false;

if (typeof method === «function») {
failed = !method(fake);
} else {
failed = typeof fake[method] === «function»?
!fake[method].apply(fake, args): !fake[method];
}

if (failed) {
failAssertion(this, (fake.printf || fake.proxy.printf).apply(fake, [message].concat(args)));
} else {
assert.pass(name);
}
};
}

function exposedName(prefix, prop) {
return !prefix || /^fail/.test(prop)? prop:
prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);
}

assert = {
failException: «AssertError»,

fail: function fail(message) {
var error = new Error(message);
error.name = this.failException || assert.failException;

throw error;
},

pass: function pass() {},

callOrder: function assertCallOrder() {
verifyIsStub.apply(null, arguments);
var expected = "";
var actual = "";

if (!sinon.calledInOrder(arguments)) {
try {
expected = [].join.call(arguments, ", ");
var calls = slice.call(arguments);
var i = calls.length;
while (i) {
if (!calls[—i].called) {
calls.splice(i, 1);
}
}
actual = sinon.orderByFirstCall(calls).join(", ");
} catch (e) {
// If this fails, we’ll just fall back to the blank string
}

failAssertion(this, «expected » + expected + " to be " +
«called in order but were called as » + actual);
} else {
assert.pass(«callOrder»);
}
},

callCount: function assertCallCount(method, count) {
verifyIsStub(method);

if (method.callCount !== count) {
var msg = «expected %n to be called » + sinon.timesInWords(count) +
" but was called %c%C";
failAssertion(this, method.printf(msg));
} else {
assert.pass(«callCount»);
}
},

expose: function expose(target, options) {
if (!target) {
throw new TypeError(«target is null or undefined»);
}

var o = options || {};
var prefix = typeof o.prefix === «undefined» && «assert» || o.prefix;
var includeFail = typeof o.includeFail === «undefined» || !!o.includeFail;

for (var method in this) {
if (method !== «expose» && (includeFail || !/^(fail)/.test(method))) {
target[exposedName(prefix, method)] = this[method];
}
}

return target;
},

match: function match(actual, expectation) {
var matcher = sinon.match(expectation);
if (matcher.test(actual)) {
assert.pass(«match»);
} else {
var formatted = [
«expected value to match»,
" expected = " + sinon.format(expectation),
" actual = " + sinon.format(actual)
];

failAssertion(this, formatted.join("\n"));
}
}
};

mirrorPropAsAssertion(«called», «expected %n to have been called at least once but was never called»);
mirrorPropAsAssertion(«notCalled», function (spy) {
return !spy.called;
}, «expected %n to not have been called but was called %c%C»);
mirrorPropAsAssertion(«calledOnce», «expected %n to be called once but was called %c%C»);
mirrorPropAsAssertion(«calledTwice», «expected %n to be called twice but was called %c%C»);
mirrorPropAsAssertion(«calledThrice», «expected %n to be called thrice but was called %c%C»);
mirrorPropAsAssertion(«calledOn», «expected %n to be called with %1 as this but was called with %t»);
mirrorPropAsAssertion(
«alwaysCalledOn»,
«expected %n to always be called with %1 as this but was called with %t»
);
mirrorPropAsAssertion(«calledWithNew», «expected %n to be called with new»);
mirrorPropAsAssertion(«alwaysCalledWithNew», «expected %n to always be called with new»);
mirrorPropAsAssertion(«calledWith», «expected %n to be called with arguments %*%C»);
mirrorPropAsAssertion(«calledWithMatch», «expected %n to be called with match %*%C»);
mirrorPropAsAssertion(«alwaysCalledWith», «expected %n to always be called with arguments %*%C»);
mirrorPropAsAssertion(«alwaysCalledWithMatch», «expected %n to always be called with match %*%C»);
mirrorPropAsAssertion(«calledWithExactly», «expected %n to be called with exact arguments %*%C»);
mirrorPropAsAssertion(«alwaysCalledWithExactly», «expected %n to always be called with exact arguments %*%C»);
mirrorPropAsAssertion(«neverCalledWith», «expected %n to never be called with arguments %*%C»);
mirrorPropAsAssertion(«neverCalledWithMatch», «expected %n to never be called with match %*%C»);
mirrorPropAsAssertion(«threw», "%n did not throw exception%C");
mirrorPropAsAssertion(«alwaysThrew», "%n did not always throw exception%C");

sinon.assert = assert;
return assert;
}

var isNode = typeof module !== «undefined» && module.exports && typeof require === «function»;
var isAMD = typeof define === «function» && typeof define.amd === «object» && define.amd;

function loadDependencies(require, exports, module) {
var sinon = require("./util/core");
require("./match");
require("./format");
module.exports = makeApi(sinon);
}

if (isAMD) {
define(loadDependencies);
return;
}

if (isNode) {
loadDependencies(require, module.exports, module);
return;
}

if (sinonGlobal) {
makeApi(sinonGlobal);
}
}(
typeof sinon === «object» && sinon, // eslint-disable-line no-undef
typeof global !== «undefined»? global: self
));

return sinon;
}));

Первоисточник спрятан тут.

ссылка на оригинал статьи http://habrahabr.ru/post/274317/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *