text: "use strict";
  
  var referee = require("@sinonjs/referee");
  var createSpy = require("../lib/sinon/spy");
  var match = require("@sinonjs/samsam").createMatcher;
  var globalContext = typeof global !== "undefined" ? global : window;
  var assert = referee.assert;
  var refute = referee.refute;
  
  function spyCalledTests(method) {
      return function() {
          // eslint-disable-next-line mocha/no-top-level-hooks
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("returns false if spy was not called", function() {
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          it("returns true if spy was called with args", function() {
              this.spy(1, 2, 3);
  
              assert(this.spy[method](1, 2, 3));
          });
  
          it("returns true if called with args at least once", function() {
              this.spy(1, 3, 3);
              this.spy(1, 2, 3);
              this.spy(3, 2, 3);
  
              assert(this.spy[method](1, 2, 3));
          });
  
          it("returns false if not called with args", function() {
              this.spy(1, 3, 3);
              this.spy(2);
              this.spy();
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          it("returns false if not called with undefined", function() {
              this.spy();
  
              assert.isFalse(this.spy[method](undefined));
          });
  
          it("returns true for partial match", function() {
              this.spy(1, 3, 3);
              this.spy(2);
              this.spy();
  
              assert(this.spy[method](1, 3));
          });
  
          it("matchs all arguments individually, not as array", function() {
              this.spy([1, 2, 3]);
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          it("uses matcher", function() {
              this.spy("abc");
  
              assert(this.spy[method](match.typeOf("string")));
          });
  
          it("uses matcher in object", function() {
              this.spy({ some: "abc" });
  
              assert(this.spy[method]({ some: match.typeOf("string") }));
          });
  
          // https://github.com/sinonjs/sinon/issues/1245
          // Using the `calledWithMatch` should work with objects that don't have
          // a hasOwnProperty function.
          describe("when called with an Object without a prototype", function() {
              it("must not throw", function() {
                  var spy = this.spy;
                  var objectWithoutPrototype = Object.create(null);
  
                  objectWithoutPrototype.something = 2;
  
                  spy[method]({
                      foo: 1,
                      objectWithoutPrototype: objectWithoutPrototype
                  });
  
                  refute.exception(function() {
                      spy.calledWithMatch({
                          objectWithoutPrototype: objectWithoutPrototype
                      });
                  });
              });
          });
      };
  }
  
  function spyAlwaysCalledTests(method) {
      return function() {
          // eslint-disable-next-line mocha/no-top-level-hooks, mocha/no-sibling-hooks
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          // eslint-disable-next-line mocha/no-identical-title
          it("returns false if spy was not called", function() {
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          // eslint-disable-next-line mocha/no-identical-title
          it("returns true if spy was called with args", function() {
              this.spy(1, 2, 3);
  
              assert(this.spy[method](1, 2, 3));
          });
  
          it("returns false if called with args only once", function() {
              this.spy(1, 3, 3);
              this.spy(1, 2, 3);
              this.spy(3, 2, 3);
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          // eslint-disable-next-line mocha/no-identical-title
          it("returns false if not called with args", function() {
              this.spy(1, 3, 3);
              this.spy(2);
              this.spy();
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          // eslint-disable-next-line mocha/no-identical-title
          it("returns true for partial match", function() {
              this.spy(1, 3, 3);
  
              assert(this.spy[method](1, 3));
          });
  
          it("returns true for partial match on many calls", function() {
              this.spy(1, 3, 3);
              this.spy(1, 3);
              this.spy(1, 3, 4, 5);
              this.spy(1, 3, 1);
  
              assert(this.spy[method](1, 3));
          });
  
          // eslint-disable-next-line mocha/no-identical-title
          it("matchs all arguments individually, not as array", function() {
              this.spy([1, 2, 3]);
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
      };
  }
  
  function spyNeverCalledTests(method) {
      return function() {
          // eslint-disable-next-line mocha/no-top-level-hooks, mocha/no-sibling-hooks
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("returns true if spy was not called", function() {
              assert(this.spy[method](1, 2, 3));
          });
  
          it("returns false if spy was called with args", function() {
              this.spy(1, 2, 3);
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          it("returns false if called with args at least once", function() {
              this.spy(1, 3, 3);
              this.spy(1, 2, 3);
              this.spy(3, 2, 3);
  
              assert.isFalse(this.spy[method](1, 2, 3));
          });
  
          it("returns true if not called with args", function() {
              this.spy(1, 3, 3);
              this.spy(2);
              this.spy();
  
              assert(this.spy[method](1, 2, 3));
          });
  
          it("returns false for partial match", function() {
              this.spy(1, 3, 3);
              this.spy(2);
              this.spy();
  
              assert.isFalse(this.spy[method](1, 3));
          });
  
          // eslint-disable-next-line mocha/no-identical-title
          it("matchs all arguments individually, not as array", function() {
              this.spy([1, 2, 3]);
  
              assert(this.spy[method](1, 2, 3));
          });
      };
  }
  
  function verifyFunctionName(func, expectedName) {
      var descriptor = Object.getOwnPropertyDescriptor(func, "name");
      if (descriptor && descriptor.configurable) {
          // IE 11 functions don't have a name.
          // Safari 9 has names that are not configurable.
          assert.equals(descriptor.value, expectedName);
          assert.equals(func.name, expectedName);
      }
  }
  
  //eslint-disable-next-line mocha/max-top-level-suites
  describe("spy", function() {
      it("does not throw if called without function", function() {
          refute.exception(function() {
              createSpy();
          });
      });
  
      it("does not throw when calling anonymous spy", function() {
          var spy = createSpy();
  
          refute.exception(spy);
  
          assert(spy.called);
      });
  
      it("returns spy function", function() {
          var func = function() {
              return;
          };
          var spy = createSpy(func);
  
          assert.isFunction(spy);
          refute.same(func, spy);
      });
  
      it("mirrors custom properties on function", function() {
          var func = function() {
              return;
          };
          func.myProp = 42;
          var spy = createSpy(func);
  
          assert.equals(spy.myProp, func.myProp);
      });
  
      it("does not define create method", function() {
          var spy = createSpy();
  
          assert.isUndefined(spy.create);
      });
  
      it("does not overwrite original create property", function() {
          var func = function() {
              return;
          };
          var object = (func.create = {});
          var spy = createSpy(func);
  
          assert.same(spy.create, object);
      });
  
      it("sets up logging arrays", function() {
          var spy = createSpy();
  
          assert.isArray(spy.args);
          assert.isArray(spy.returnValues);
          assert.isArray(spy.thisValues);
          assert.isArray(spy.exceptions);
      });
  
      it("works with getters", function() {
          var object = {
              get property() {
                  return 42;
              }
          };
          var spy = createSpy(object, "property", ["get"]);
  
          assert.equals(object.property, 42);
          assert(spy.get.calledOnce);
      });
  
      it("works with setters", function() {
          var object = {
              get test() {
                  return this.property;
              },
              set test(value) {
                  this.property = value * 2;
              }
          };
          var spy = createSpy(object, "test", ["set"]);
  
          object.test = 42;
          assert(spy.set.calledOnce);
          assert(spy.set.calledWith(42));
  
          assert.equals(object.test, 84);
          assert.equals(object.property, 84);
      });
  
      it("works with setters and getters combined", function() {
          var object = {
              get test() {
                  return this.property;
              },
              set test(value) {
                  this.property = value * 2;
              }
          };
          var spy = createSpy(object, "test", ["get", "set"]);
  
          object.test = 42;
          assert(spy.set.calledOnce);
  
          assert.equals(object.test, 84);
          assert(spy.get.calledOnce);
      });
  
      describe("global.Error", function() {
          beforeEach(function() {
              this.originalError = globalContext.Error;
          });
  
          afterEach(function() {
              globalContext.Error = this.originalError;
          });
  
          it("creates a spy for Error", function() {
              refute.exception(function() {
                  createSpy(globalContext, "Error");
              });
          });
      });
  
      it("should work with combination of withArgs arguments and order of calling withArgs", function() {
          function assertSpy(spy) {
              // assert callCount
              assert.equals(spy.callCount, 4);
              assert.equals(spy.withArgs(1).callCount, 3);
              assert.equals(spy.withArgs(1, 1).callCount, 1);
              assert.equals(spy.withArgs(1, 2).callCount, 1);
  
              // assert call
              assert.isUndefined(spy.getCall(0).args[0]);
              assert.equals(spy.getCall(1).args[0], 1);
              assert.isUndefined(spy.getCall(1).args[1]);
              assert.equals(spy.getCall(2).args[0], 1);
              assert.equals(spy.getCall(2).args[1], 1);
              assert.isUndefined(spy.getCall(2).args[2]);
              assert.equals(spy.getCall(3).args[0], 1);
              assert.equals(spy.getCall(3).args[1], 2);
              assert.isUndefined(spy.getCall(3).args[2]);
              ["args", "callId"].forEach(function(propName) {
                  assert.equals(spy.withArgs(1).getCall(0)[propName], spy.getCall(1)[propName]);
                  assert.equals(spy.withArgs(1).getCall(1)[propName], spy.getCall(2)[propName]);
                  assert.equals(spy.withArgs(1).getCall(2)[propName], spy.getCall(3)[propName]);
                  assert.isNull(spy.withArgs(1).getCall(3));
                  assert.equals(spy.withArgs(1, 1).getCall(0)[propName], spy.getCall(2)[propName]);
                  assert.isNull(spy.withArgs(1, 1).getCall(1));
                  assert.equals(spy.withArgs(1, 2).getCall(0)[propName], spy.getCall(3)[propName]);
                  assert.isNull(spy.withArgs(1, 2).getCall(1));
              });
  
              // assert firstCall, secondCall, thirdCall, and lastCall
              assert.equals(spy.firstCall.callId, spy.getCall(0).callId);
              assert.equals(spy.secondCall.callId, spy.getCall(1).callId);
              assert.equals(spy.thirdCall.callId, spy.getCall(2).callId);
              assert.equals(spy.lastCall.callId, spy.getCall(3).callId);
              assert.equals(spy.withArgs(1).firstCall.callId, spy.withArgs(1).getCall(0).callId);
              assert.equals(spy.withArgs(1).secondCall.callId, spy.withArgs(1).getCall(1).callId);
              assert.equals(spy.withArgs(1).thirdCall.callId, spy.withArgs(1).getCall(2).callId);
              assert.equals(spy.withArgs(1).lastCall.callId, spy.withArgs(1).getCall(2).callId);
              assert.equals(spy.withArgs(1, 1).firstCall.callId, spy.withArgs(1, 1).getCall(0).callId);
              assert.isNull(spy.withArgs(1, 1).secondCall);
              assert.isNull(spy.withArgs(1, 1).thirdCall);
              assert.equals(spy.withArgs(1, 1).lastCall.callId, spy.withArgs(1, 1).getCall(0).callId);
              assert.equals(spy.withArgs(1, 2).firstCall.callId, spy.withArgs(1, 2).getCall(0).callId);
              assert.isNull(spy.withArgs(1, 2).secondCall);
              assert.isNull(spy.withArgs(1, 2).thirdCall);
              assert.equals(spy.withArgs(1, 2).lastCall.callId, spy.withArgs(1, 2).getCall(0).callId);
          }
  
          var object = {
              f1: function() {
                  return;
              },
              f2: function() {
                  return;
              }
          };
  
          // f1: the order of withArgs(1), withArgs(1, 1)
          var spy1 = createSpy(object, "f1");
          assert.equals(spy1.callCount, 0);
          assert.equals(spy1.withArgs(1).callCount, 0);
          assert.equals(spy1.withArgs(1, 1).callCount, 0);
          assert.isNull(spy1.getCall(0));
          assert.isNull(spy1.getCall(1));
          assert.isNull(spy1.getCall(2));
          assert.isNull(spy1.getCall(3));
  
          object.f1();
          object.f1(1);
          object.f1(1, 1);
          object.f1(1, 2);
          assertSpy(spy1);
  
          // f2: the order of withArgs(1, 1), withArgs(1)
          var spy2 = createSpy(object, "f2");
          assert.equals(spy2.callCount, 0);
          assert.equals(spy2.withArgs(1, 1).callCount, 0);
          assert.equals(spy2.withArgs(1).callCount, 0);
          assert.isNull(spy2.getCall(0));
          assert.isNull(spy2.getCall(1));
          assert.isNull(spy2.getCall(2));
          assert.isNull(spy2.getCall(3));
  
          object.f2();
          object.f2(1);
          object.f2(1, 1);
          object.f2(1, 2);
          assertSpy(spy2);
      });
  
      describe(".named", function() {
          it("sets name and displayName", function() {
              var spy = createSpy();
              var retval = spy.named("beep");
              assert.equals(spy.displayName, "beep");
              verifyFunctionName(spy, "beep");
              assert.same(spy, retval);
          });
      });
  
      describe("call", function() {
          it("calls underlying function", function() {
              var called = false;
  
              var spy = createSpy(function() {
                  called = true;
              });
  
              spy();
  
              assert(called);
          });
  
          it("passes 'new' to underlying function", function() {
              function TestClass() {
                  return;
              }
  
              var SpyClass = createSpy(TestClass);
  
              var instance = new SpyClass();
  
              assert(instance instanceof TestClass);
          });
  
          it("passs arguments to function", function() {
              var actualArgs;
  
              var func = function(a, b, c, d) {
                  actualArgs = [a, b, c, d];
              };
  
              var args = [1, {}, [], ""];
              var spy = createSpy(func);
              spy(args[0], args[1], args[2], args[3]);
  
              assert.equals(actualArgs, args);
          });
  
          it("maintains this binding", function() {
              var actualThis;
  
              var func = function() {
                  actualThis = this;
              };
  
              var object = {};
              var spy = createSpy(func);
              spy.call(object);
  
              assert.same(actualThis, object);
          });
  
          it("returns function's return value", function() {
              var object = {};
  
              var func = function() {
                  return object;
              };
  
              var spy = createSpy(func);
              var actualReturn = spy();
  
              assert.same(actualReturn, object);
          });
  
          it("throws if function throws", function() {
              var err = new Error();
              var spy = createSpy(function() {
                  throw err;
              });
  
              assert.exception(spy, err);
          });
  
          it("retains function name", function() {
              function test() {
                  return;
              }
  
              var spy = createSpy(test);
  
              assert.equals(spy.displayName, "test");
              verifyFunctionName(spy, "test");
          });
  
          it("retains function length 0", function() {
              var spy = createSpy(function() {
                  return;
              });
  
              assert.equals(spy.length, 0);
          });
  
          it("retains function length 1", function() {
              // eslint-disable-next-line no-unused-vars
              var spy = createSpy(function(a) {
                  return;
              });
  
              assert.equals(spy.length, 1);
          });
  
          it("retains function length 2", function() {
              // eslint-disable-next-line no-unused-vars
              var spy = createSpy(function(a, b) {
                  return;
              });
  
              assert.equals(spy.length, 2);
          });
  
          it("retains function length 3", function() {
              // eslint-disable-next-line no-unused-vars
              var spy = createSpy(function(a, b, c) {
                  return;
              });
  
              assert.equals(spy.length, 3);
          });
  
          it("retains function length 4", function() {
              // eslint-disable-next-line no-unused-vars
              var spy = createSpy(function(a, b, c, d) {
                  return;
              });
  
              assert.equals(spy.length, 4);
          });
  
          it("retains function length 12", function() {
              // eslint-disable-next-line no-unused-vars
              var func12Args = function(a, b, c, d, e, f, g, h, i, j, k, l) {
                  return;
              };
              var spy = createSpy(func12Args);
  
              assert.equals(spy.length, 12);
          });
      });
  
      describe(".called", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false prior to calling the spy", function() {
              assert.isFalse(this.spy.called);
          });
  
          it("is true after calling the spy once", function() {
              this.spy();
  
              assert(this.spy.called);
          });
  
          it("is true after calling the spy twice", function() {
              this.spy();
              this.spy();
  
              assert(this.spy.called);
          });
      });
  
      describe(".notCalled", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is true prior to calling the spy", function() {
              assert.isTrue(this.spy.notCalled);
          });
  
          it("is false after calling the spy once", function() {
              this.spy();
  
              assert.isFalse(this.spy.notCalled);
          });
      });
  
      describe(".calledOnce", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false prior to calling the spy", function() {
              assert.isFalse(this.spy.calledOnce);
          });
  
          it("is true after calling the spy once", function() {
              this.spy();
  
              assert(this.spy.calledOnce);
          });
  
          it("is false after calling the spy twice", function() {
              this.spy();
              this.spy();
  
              assert.isFalse(this.spy.calledOnce);
          });
      });
  
      describe(".calledTwice", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false prior to calling the spy", function() {
              assert.isFalse(this.spy.calledTwice);
          });
  
          it("is false after calling the spy once", function() {
              this.spy();
  
              assert.isFalse(this.spy.calledTwice);
          });
  
          it("is true after calling the spy twice", function() {
              this.spy();
              this.spy();
  
              assert(this.spy.calledTwice);
          });
  
          it("is false after calling the spy thrice", function() {
              this.spy();
              this.spy();
              this.spy();
  
              assert.isFalse(this.spy.calledTwice);
          });
      });
  
      describe(".calledThrice", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false prior to calling the spy", function() {
              assert.isFalse(this.spy.calledThrice);
          });
  
          it("is false after calling the spy twice", function() {
              this.spy();
              this.spy();
  
              assert.isFalse(this.spy.calledThrice);
          });
  
          it("is true after calling the spy thrice", function() {
              this.spy();
              this.spy();
              this.spy();
  
              assert(this.spy.calledThrice);
          });
  
          it("is false after calling the spy four times", function() {
              this.spy();
              this.spy();
              this.spy();
              this.spy();
  
              assert.isFalse(this.spy.calledThrice);
          });
      });
  
      describe(".callCount", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("reports 0 calls", function() {
              assert.equals(this.spy.callCount, 0);
          });
  
          it("records one call", function() {
              this.spy();
  
              assert.equals(this.spy.callCount, 1);
          });
  
          it("records two calls", function() {
              this.spy();
              this.spy();
  
              assert.equals(this.spy.callCount, 2);
          });
  
          it("increases call count for each call", function() {
              this.spy();
              this.spy();
              assert.equals(this.spy.callCount, 2);
  
              this.spy();
              assert.equals(this.spy.callCount, 3);
          });
      });
  
      describe(".calledOn", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false if spy wasn't called", function() {
              assert.isFalse(this.spy.calledOn({}));
          });
  
          it("is true if called with thisValue", function() {
              var object = {};
              this.spy.call(object);
  
              assert(this.spy.calledOn(object));
          });
  
          describe("in browser", function() {
              before(function() {
                  if (typeof window === "undefined") {
                      this.skip();
                  }
              });
  
              it("is true if called on object at least once", function() {
                  var object = {};
                  this.spy();
                  this.spy.call({});
                  this.spy.call(object);
                  this.spy.call(window);
  
                  assert(this.spy.calledOn(object));
              });
          });
  
          it("returns false if not called on object", function() {
              var object = {};
              this.spy.call(object);
              this.spy();
  
              assert.isFalse(this.spy.calledOn({}));
          });
  
          it("is true if called with matcher that returns true", function() {
              var matcher = match(function() {
                  return true;
              });
              this.spy();
  
              assert(this.spy.calledOn(matcher));
          });
  
          it("is false if called with matcher that returns false", function() {
              var matcher = match(function() {
                  return false;
              });
              this.spy();
  
              assert.isFalse(this.spy.calledOn(matcher));
          });
  
          it("invokes matcher.test with given object", function() {
              var expected = {};
              var actual;
              this.spy.call(expected);
  
              this.spy.calledOn(
                  match(function(value) {
                      actual = value;
                  })
              );
  
              assert.same(actual, expected);
          });
      });
  
      describe(".alwaysCalledOn", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false prior to calling the spy", function() {
              assert.isFalse(this.spy.alwaysCalledOn({}));
          });
  
          it("is true if called with thisValue once", function() {
              var object = {};
              this.spy.call(object);
  
              assert(this.spy.alwaysCalledOn(object));
          });
  
          it("is true if called with thisValue many times", function() {
              var object = {};
              this.spy.call(object);
              this.spy.call(object);
              this.spy.call(object);
              this.spy.call(object);
  
              assert(this.spy.alwaysCalledOn(object));
          });
  
          it("is false if called with another object atleast once", function() {
              var object = {};
              this.spy.call(object);
              this.spy.call(object);
              this.spy.call(object);
              this.spy();
              this.spy.call(object);
  
              assert.isFalse(this.spy.alwaysCalledOn(object));
          });
  
          it("is false if never called with expected object", function() {
              var object = {};
              this.spy();
              this.spy();
              this.spy();
  
              assert.isFalse(this.spy.alwaysCalledOn(object));
          });
      });
  
      describe(".calledWithNew", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false if spy wasn't called", function() {
              assert.isFalse(this.spy.calledWithNew());
          });
  
          it("is true if called with new", function() {
              var result = new this.spy(); // eslint-disable-line no-unused-vars, new-cap
  
              assert(this.spy.calledWithNew());
          });
  
          it("is true if called with new on custom constructor", function() {
              function MyThing() {
                  return;
              }
              MyThing.prototype = {};
              var ns = { MyThing: MyThing };
              createSpy(ns, "MyThing");
  
              var result = new ns.MyThing(); // eslint-disable-line no-unused-vars
              assert(ns.MyThing.calledWithNew());
          });
  
          it("is false if called as function", function() {
              this.spy();
  
              assert.isFalse(this.spy.calledWithNew());
          });
  
          describe("in browser", function() {
              before(function() {
                  if (typeof window === "undefined") {
                      this.skip();
                  }
              });
  
              it("is true if called with new at least once", function() {
                  var object = {};
                  this.spy();
                  var a = new this.spy(); // eslint-disable-line no-unused-vars, new-cap
                  this.spy(object);
                  this.spy(window);
  
                  assert(this.spy.calledWithNew());
              });
          });
  
          it("is true newed constructor returns object", function() {
              function MyThing() {
                  return {};
              }
              var object = { MyThing: MyThing };
              createSpy(object, "MyThing");
  
              var result = new object.MyThing(); // eslint-disable-line no-unused-vars
  
              assert(object.MyThing.calledWithNew());
          });
  
          describe("spied native function", function() {
              before(function() {
                  // eslint-disable-next-line no-restricted-syntax
                  try {
                      // eslint-disable-next-line no-console
                      console.log.apply({}, []);
                  } catch (e) {
                      this.skip();
                  }
              });
  
              it("is false when called on spied native function", function() {
                  var log = { info: console.log }; // eslint-disable-line no-console
                  createSpy(log, "info");
  
                  // by logging an empty string, we're not polluting the test console output
                  log.info("");
  
                  assert.isFalse(log.info.calledWithNew());
              });
          });
      });
  
      describe(".alwaysCalledWithNew", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is false if spy wasn't called", function() {
              assert.isFalse(this.spy.alwaysCalledWithNew());
          });
  
          it("is true if always called with new", function() {
              /*eslint-disable no-unused-vars, new-cap*/
              var result = new this.spy();
              var result2 = new this.spy();
              var result3 = new this.spy();
              /*eslint-enable no-unused-vars, new-cap*/
  
              assert(this.spy.alwaysCalledWithNew());
          });
  
          it("is false if called as function once", function() {
              /*eslint-disable no-unused-vars, new-cap*/
              var result = new this.spy();
              var result2 = new this.spy();
              /*eslint-enable no-unused-vars, new-cap*/
              this.spy();
  
              assert.isFalse(this.spy.alwaysCalledWithNew());
          });
      });
  
      describe(".thisValues", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("contains one object", function() {
              var object = {};
              this.spy.call(object);
  
              assert.equals(this.spy.thisValues, [object]);
          });
  
          it("stacks up objects", function() {
              function MyConstructor() {
                  return;
              }
              var objects = [{}, [], new MyConstructor(), { id: 243 }];
              this.spy();
              this.spy.call(objects[0]);
              this.spy.call(objects[1]);
              this.spy.call(objects[2]);
              this.spy.call(objects[3]);
  
              assert.equals(this.spy.thisValues, [this].concat(objects));
          });
      });
  
      // eslint-disable-next-line mocha/no-setup-in-describe
      describe(".calledWith", spyCalledTests("calledWith"));
      // eslint-disable-next-line mocha/no-setup-in-describe
      describe(".calledWithMatch", spyCalledTests("calledWithMatch"));
  
      describe(".calledWithMatchSpecial", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("checks substring match", function() {
              this.spy("I like it");
  
              assert(this.spy.calledWithMatch("like"));
              assert.isFalse(this.spy.calledWithMatch("nope"));
          });
  
          it("checks for regexp match", function() {
              this.spy("I like it");
  
              assert(this.spy.calledWithMatch(/[a-z ]+/i));
              assert.isFalse(this.spy.calledWithMatch(/[0-9]+/));
          });
  
          it("checks for partial object match", function() {
              this.spy({ foo: "foo", bar: "bar" });
  
              assert(this.spy.calledWithMatch({ bar: "bar" }));
              assert.isFalse(this.spy.calledWithMatch({ same: "same" }));
          });
      });
  
      // eslint-disable-next-line mocha/no-setup-in-describe
      describe(".alwaysCalledWith", spyAlwaysCalledTests("alwaysCalledWith"));
      // eslint-disable-next-line mocha/no-setup-in-describe
      describe(".alwaysCalledWithMatch", spyAlwaysCalledTests("alwaysCalledWithMatch"));
  
      describe(".alwaysCalledWithMatchSpecial", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("checks true", function() {
              this.spy(true);
  
              assert(this.spy.alwaysCalledWithMatch(true));
              assert.isFalse(this.spy.alwaysCalledWithMatch(false));
          });
  
          it("checks false", function() {
              this.spy(false);
  
              assert(this.spy.alwaysCalledWithMatch(false));
              assert.isFalse(this.spy.alwaysCalledWithMatch(true));
          });
  
          it("checks substring match", function() {
              this.spy("test case");
              this.spy("some test");
              this.spy("all tests");
  
              assert(this.spy.alwaysCalledWithMatch("test"));
              assert.isFalse(this.spy.alwaysCalledWithMatch("case"));
          });
  
          it("checks regexp match", function() {
              this.spy("1");
              this.spy("2");
              this.spy("3");
  
              assert(this.spy.alwaysCalledWithMatch(/[123]/));
              assert.isFalse(this.spy.alwaysCalledWithMatch(/[12]/));
          });
  
          it("checks partial object match", function() {
              this.spy({ a: "a", b: "b" });
              this.spy({ c: "c", b: "b" });
              this.spy({ b: "b", d: "d" });
  
              assert(this.spy.alwaysCalledWithMatch({ b: "b" }));
              assert.isFalse(this.spy.alwaysCalledWithMatch({ a: "a" }));
          });
      });
  
      // eslint-disable-next-line mocha/no-setup-in-describe
      describe(".neverCalledWith", spyNeverCalledTests("neverCalledWith"));
      // eslint-disable-next-line mocha/no-setup-in-describe
      describe(".neverCalledWithMatch", spyNeverCalledTests("neverCalledWithMatch"));
  
      describe(".neverCalledWithMatchSpecial", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("checks substring match", function() {
              this.spy("a test", "b test");
  
              assert(this.spy.neverCalledWithMatch("a", "a"));
              assert(this.spy.neverCalledWithMatch("b", "b"));
              assert(this.spy.neverCalledWithMatch("b", "a"));
              assert.isFalse(this.spy.neverCalledWithMatch("a", "b"));
          });
  
          it("checks regexp match", function() {
              this.spy("a test", "b test");
  
              assert(this.spy.neverCalledWithMatch(/a/, /a/));
              assert(this.spy.neverCalledWithMatch(/b/, /b/));
              assert(this.spy.neverCalledWithMatch(/b/, /a/));
              assert.isFalse(this.spy.neverCalledWithMatch(/a/, /b/));
          });
  
          it("checks partial object match", function() {
              this.spy({ a: "test", b: "test" });
  
              assert(this.spy.neverCalledWithMatch({ a: "nope" }));
              assert(this.spy.neverCalledWithMatch({ c: "test" }));
              assert.isFalse(this.spy.neverCalledWithMatch({ b: "test" }));
          });
      });
  
      describe(".args", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("contains real arrays", function() {
              this.spy();
  
              assert.isArray(this.spy.args[0]);
          });
  
          it("contains empty array when no arguments", function() {
              this.spy();
  
              assert.equals(this.spy.args, [[]]);
          });
  
          it("contains array with first call's arguments", function() {
              this.spy(1, 2, 3);
  
              assert.equals(this.spy.args, [[1, 2, 3]]);
          });
  
          it("stacks up arguments in nested array", function() {
              var objects = [{}, [], { id: 324 }];
              this.spy(1, objects[0], 3);
              this.spy(1, 2, objects[1]);
              this.spy(objects[2], 2, 3);
  
              assert.equals(this.spy.args, [
                  [1, objects[0], 3],
                  [1, 2, objects[1]],
                  [objects[2], 2, 3]
              ]);
          });
      });
  
      describe(".calledWithExactly", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("returns false for partial match", function() {
              this.spy(1, 2, 3);
  
              assert.isFalse(this.spy.calledWithExactly(1, 2));
          });
  
          it("returns false for missing arguments", function() {
              this.spy(1, 2, 3);
  
              assert.isFalse(this.spy.calledWithExactly(1, 2, 3, 4));
          });
  
          it("returns true for exact match", function() {
              this.spy(1, 2, 3);
  
              assert(this.spy.calledWithExactly(1, 2, 3));
          });
  
          it("matchs by strict comparison", function() {
              this.spy({}, []);
  
              assert.isFalse(this.spy.calledWithExactly({}, [], null));
          });
  
          it("returns true for one exact match", function() {
              var object = {};
              var array = [];
              this.spy({}, []);
              this.spy(object, []);
              this.spy(object, array);
  
              assert(this.spy.calledWithExactly(object, array));
          });
  
          it("returns true when all properties of an object argument match", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert(this.spy.calledWithExactly({ a: 1, b: 2, c: 3 }));
          });
  
          it("returns false when a property of an object argument is set to undefined", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert.isFalse(this.spy.calledWithExactly({ a: 1, b: 2, c: undefined }));
          });
  
          it("returns false when a property of an object argument is set to a different value", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert.isFalse(this.spy.calledWithExactly({ a: 1, b: 2, c: "blah" }));
          });
  
          it("returns false when an object argument has a different property/value pair", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert.isFalse(this.spy.calledWithExactly({ a: 1, b: 2, foo: "blah" }));
          });
  
          it("returns false when property of Object argument is set to undefined and has a different name", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert.isFalse(this.spy.calledWithExactly({ a: 1, b: 2, foo: undefined }));
          });
  
          it("returns false when any properties of an object argument aren't present", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert.isFalse(this.spy.calledWithExactly({ a: 1, b: 2 }));
          });
  
          it("returns false when an object argument has extra properties", function() {
              this.spy({ a: 1, b: 2, c: 3 });
  
              assert.isFalse(this.spy.calledWithExactly({ a: 1, b: 2, c: 3, d: 4 }));
          });
      });
  
      describe(".calledOnceWith", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("returns true for not exact match", function() {
              this.spy(1, 2, 3, 4);
  
              assert.isTrue(this.spy.calledOnceWith(1, 2, 3));
          });
  
          it("returns false for matching calls but called more then once", function() {
              this.spy(1, 2, 3, 4);
              this.spy(1, 2, 3, 6);
  
              assert.isFalse(this.spy.calledOnceWith(1, 2, 3));
          });
  
          it("return false for one mismatched call", function() {
              this.spy(1, 2);
  
              assert.isFalse(this.spy.calledOnceWith(1, 2, 3));
          });
  
          it("return false for one mismatched call with some other ", function() {
              this.spy(1, 2, 3);
              this.spy(1, 2);
  
              assert.isFalse(this.spy.calledOnceWith(1, 2, 3));
          });
      });
  
      describe(".calledOnceWithExactly", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("returns true for exact match", function() {
              this.spy(1, 2, 3);
  
              assert.isTrue(this.spy.calledOnceWithExactly(1, 2, 3));
          });
  
          it("returns false for exact parameters but called more then once", function() {
              this.spy(1, 2, 3);
              this.spy(1, 2, 3);
  
              assert.isFalse(this.spy.calledOnceWithExactly(1, 2, 3));
          });
  
          it("return false for one mismatched call", function() {
              this.spy(1, 2);
  
              assert.isFalse(this.spy.calledOnceWithExactly(1, 2, 3));
          });
  
          it("return false for one mismatched call with some other ", function() {
              this.spy(1, 2, 3);
              this.spy(1, 2);
  
              assert.isFalse(this.spy.calledOnceWithExactly(1, 2, 3));
          });
      });
  
      describe(".alwaysCalledWithExactly", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("returns false for partial match", function() {
              this.spy(1, 2, 3);
  
              assert.isFalse(this.spy.alwaysCalledWithExactly(1, 2));
          });
  
          it("returns false for missing arguments", function() {
              this.spy(1, 2, 3);
  
              assert.isFalse(this.spy.alwaysCalledWithExactly(1, 2, 3, 4));
          });
  
          it("returns true for exact match", function() {
              this.spy(1, 2, 3);
  
              assert(this.spy.alwaysCalledWithExactly(1, 2, 3));
          });
  
          it("returns false for excess arguments", function() {
              this.spy({}, []);
  
              assert.isFalse(this.spy.alwaysCalledWithExactly({}, [], null));
          });
  
          it("returns false for one exact match", function() {
              var object = {};
              var array = [];
              this.spy({}, []);
              this.spy(object, []);
              this.spy(object, array);
  
              assert(this.spy.alwaysCalledWithExactly(object, array));
          });
  
          it("returns true for only exact matches", function() {
              var object = {};
              var array = [];
  
              this.spy(object, array);
              this.spy(object, array);
              this.spy(object, array);
  
              assert(this.spy.alwaysCalledWithExactly(object, array));
          });
  
          it("returns false for no exact matches", function() {
              var object = {};
              var array = [];
  
              this.spy(object, array, null);
              this.spy(object, array, undefined);
              this.spy(object, array, "");
  
              assert.isFalse(this.spy.alwaysCalledWithExactly(object, array));
          });
      });
  
      describe(".threw", function() {
          beforeEach(function() {
              this.spy = createSpy();
  
              this.spyWithTypeError = createSpy(function() {
                  throw new TypeError();
              });
  
              this.spyWithStringError = createSpy(function() {
                  // eslint-disable-next-line no-throw-literal
                  throw "error";
              });
          });
  
          it("returns exception thrown by function", function() {
              var err = new Error();
  
              var spy = createSpy(function() {
                  throw err;
              });
  
              assert.exception(spy);
  
              assert(spy.threw(err));
          });
  
          it("returns false if spy did not throw", function() {
              this.spy();
  
              assert.isFalse(this.spy.threw());
          });
  
          it("returns true if spy threw", function() {
              assert.exception(this.spyWithTypeError);
  
              assert(this.spyWithTypeError.threw());
          });
  
          it("returns true if string type matches", function() {
              assert.exception(this.spyWithTypeError);
  
              assert(this.spyWithTypeError.threw("TypeError"));
          });
  
          it("returns false if string did not match", function() {
              assert.exception(this.spyWithTypeError);
  
              assert.isFalse(this.spyWithTypeError.threw("Error"));
          });
  
          it("returns false if spy did not throw specified error", function() {
              this.spy();
  
              assert.isFalse(this.spy.threw("Error"));
          });
  
          it("returns true if string matches", function() {
              assert.exception(this.spyWithStringError);
  
              assert(this.spyWithStringError.threw("error"));
          });
  
          it("returns false if strings do not match", function() {
              assert.exception(this.spyWithStringError);
  
              assert.isFalse(this.spyWithStringError.threw("not the error"));
          });
      });
  
      describe(".alwaysThrew", function() {
          beforeEach(function() {
              this.spy = createSpy();
  
              this.spyWithTypeError = createSpy(function() {
                  throw new TypeError();
              });
          });
  
          it("returns true when spy threw", function() {
              var err = new Error();
  
              var spy = createSpy(function() {
                  throw err;
              });
  
              assert.exception(spy);
  
              assert(spy.alwaysThrew(err));
          });
  
          it("returns false if spy did not throw", function() {
              this.spy();
  
              assert.isFalse(this.spy.alwaysThrew());
          });
  
          it("returns true if spy threw", function() {
              assert.exception(this.spyWithTypeError);
  
              assert(this.spyWithTypeError.alwaysThrew());
          });
  
          it("returns true if string type matches", function() {
              assert.exception(this.spyWithTypeError);
  
              assert(this.spyWithTypeError.alwaysThrew("TypeError"));
          });
  
          it("returns false if string did not match", function() {
              assert.exception(this.spyWithTypeError);
  
              assert.isFalse(this.spyWithTypeError.alwaysThrew("Error"));
          });
  
          it("returns false if spy did not throw specified error", function() {
              this.spy();
  
              assert.isFalse(this.spy.alwaysThrew("Error"));
          });
  
          it("returns false if some calls did not throw", function() {
              var callCount = 0;
  
              this.spy = createSpy(function() {
                  callCount += 1;
                  if (callCount === 1) {
                      throw new Error("throwing on first call");
                  }
              });
  
              assert.exception(this.spy);
  
              this.spy();
  
              assert.isFalse(this.spy.alwaysThrew());
          });
  
          it("returns true if all calls threw", function() {
              assert.exception(this.spyWithTypeError);
  
              assert.exception(this.spyWithTypeError);
  
              assert(this.spyWithTypeError.alwaysThrew());
          });
  
          it("returns true if all calls threw same type", function() {
              assert.exception(this.spyWithTypeError);
  
              assert.exception(this.spyWithTypeError);
  
              assert(this.spyWithTypeError.alwaysThrew("TypeError"));
          });
      });
  
      describe(".exceptions", function() {
          beforeEach(function() {
              this.spy = createSpy();
              var error = (this.error = {});
  
              this.spyWithTypeError = createSpy(function() {
                  throw error;
              });
          });
  
          it("contains exception thrown by function", function() {
              assert.exception(this.spyWithTypeError);
  
              assert.equals(this.spyWithTypeError.exceptions, [this.error]);
          });
  
          it("contains undefined entry when function did not throw", function() {
              this.spy();
  
              assert.equals(this.spy.exceptions.length, 1);
              assert.isUndefined(this.spy.exceptions[0]);
          });
  
          it("stacks up exceptions and undefined", function() {
              var calls = 0;
              var err = this.error;
  
              var spy = createSpy(function() {
                  calls += 1;
  
                  if (calls % 2 === 0) {
                      throw err;
                  }
              });
  
              spy();
  
              assert.exception(spy);
  
              spy();
  
              assert.exception(spy);
  
              spy();
  
              assert.equals(spy.exceptions.length, 5);
              assert.isUndefined(spy.exceptions[0]);
              assert.equals(spy.exceptions[1], err);
              assert.isUndefined(spy.exceptions[2]);
              assert.equals(spy.exceptions[3], err);
              assert.isUndefined(spy.exceptions[4]);
          });
      });
  
      describe(".returned", function() {
          it("returns true when no argument", function() {
              var spy = createSpy();
              spy();
  
              assert(spy.returned());
          });
  
          it("returns true for undefined when no explicit return", function() {
              var spy = createSpy();
              spy();
  
              assert(spy.returned(undefined));
          });
  
          it("returns true when returned value once", function() {
              var values = [
                  {},
                  2,
                  "hey",
                  function() {
                      return;
                  }
              ];
              var spy = createSpy(function() {
                  return values[spy.callCount];
              });
  
              spy();
              spy();
              spy();
              spy();
  
              assert(spy.returned(values[3]));
          });
  
          it("returns false when value is never returned", function() {
              var values = [
                  {},
                  2,
                  "hey",
                  function() {
                      return;
                  }
              ];
              var spy = createSpy(function() {
                  return values[spy.callCount];
              });
  
              spy();
              spy();
              spy();
              spy();
  
              assert.isFalse(spy.returned({ id: 42 }));
          });
  
          it("returns true when value is returned several times", function() {
              var object = { id: 42 };
              var spy = createSpy(function() {
                  return object;
              });
  
              spy();
              spy();
              spy();
  
              assert(spy.returned(object));
          });
  
          it("compares values deeply", function() {
              var object = { deep: { id: 42 } };
              var spy = createSpy(function() {
                  return object;
              });
  
              spy();
  
              assert(spy.returned({ deep: { id: 42 } }));
          });
  
          it("compares values strictly using match.same", function() {
              var object = { id: 42 };
              var spy = createSpy(function() {
                  return object;
              });
  
              spy();
  
              assert.isFalse(spy.returned(match.same({ id: 42 })));
              assert(spy.returned(match.same(object)));
          });
      });
  
      describe(".returnValues", function() {
          it("contains undefined when function does not return explicitly", function() {
              var spy = createSpy();
              spy();
  
              assert.equals(spy.returnValues.length, 1);
              assert.isUndefined(spy.returnValues[0]);
          });
  
          it("contains return value", function() {
              var object = { id: 42 };
  
              var spy = createSpy(function() {
                  return object;
              });
  
              spy();
  
              assert.equals(spy.returnValues, [object]);
          });
  
          it("contains undefined when function throws", function() {
              var spy = createSpy(function() {
                  throw new Error();
              });
  
              assert.exception(spy);
  
              assert.equals(spy.returnValues.length, 1);
              assert.isUndefined(spy.returnValues[0]);
          });
  
          it("contains the created object for spied constructors", function() {
              var Spy = createSpy(function() {
                  return;
              });
  
              var result = new Spy();
  
              assert.equals(Spy.returnValues[0], result);
          });
  
          it("contains the return value for spied constructors that explicitly return objects", function() {
              var Spy = createSpy(function() {
                  return { isExplicitlyCreatedValue: true };
              });
  
              var result = new Spy();
  
              assert.isTrue(result.isExplicitlyCreatedValue);
              assert.equals(Spy.returnValues[0], result);
          });
  
          it("contains the created object for spied constructors that explicitly return primitive values", function() {
              var Spy = createSpy(function() {
                  return 10;
              });
  
              var result = new Spy();
  
              refute.equals(result, 10);
              assert.equals(Spy.returnValues[0], result);
          });
  
          it("stacks up return values", function() {
              var calls = 0;
  
              /*eslint consistent-return: "off"*/
              var spy = createSpy(function() {
                  calls += 1;
  
                  if (calls % 2 === 0) {
                      return calls;
                  }
              });
  
              spy();
              spy();
              spy();
              spy();
              spy();
  
              assert.equals(spy.returnValues.length, 5);
              assert.isUndefined(spy.returnValues[0]);
              assert.equals(spy.returnValues[1], 2);
              assert.isUndefined(spy.returnValues[2]);
              assert.equals(spy.returnValues[3], 4);
              assert.isUndefined(spy.returnValues[4]);
          });
      });
  
      describe(".calledBefore", function() {
          beforeEach(function() {
              this.spyA = createSpy();
              this.spyB = createSpy();
          });
  
          it("is function", function() {
              assert.isFunction(this.spyA.calledBefore);
          });
  
          it("returns true if first call to A was before first to B", function() {
              this.spyA();
              this.spyB();
  
              assert(this.spyA.calledBefore(this.spyB));
          });
  
          it("compares call order of calls directly", function() {
              this.spyA();
              this.spyB();
  
              assert(this.spyA.getCall(0).calledBefore(this.spyB.getCall(0)));
          });
  
          it("returns false if not called", function() {
              this.spyB();
  
              assert.isFalse(this.spyA.calledBefore(this.spyB));
          });
  
          it("returns true if other not called", function() {
              this.spyA();
  
              assert(this.spyA.calledBefore(this.spyB));
          });
  
          it("returns false if other called first", function() {
              this.spyB();
              this.spyA();
              this.spyB();
  
              assert(this.spyA.calledBefore(this.spyB));
          });
      });
  
      describe(".calledAfter", function() {
          beforeEach(function() {
              this.spyA = createSpy();
              this.spyB = createSpy();
          });
  
          it("is function", function() {
              assert.isFunction(this.spyA.calledAfter);
          });
  
          it("returns true if first call to A was after first to B", function() {
              this.spyB();
              this.spyA();
  
              assert(this.spyA.calledAfter(this.spyB));
          });
  
          it("compares calls directly", function() {
              this.spyB();
              this.spyA();
  
              assert(this.spyA.getCall(0).calledAfter(this.spyB.getCall(0)));
          });
  
          it("returns false if not called", function() {
              this.spyB();
  
              assert.isFalse(this.spyA.calledAfter(this.spyB));
          });
  
          it("returns false if other not called", function() {
              this.spyA();
  
              assert.isFalse(this.spyA.calledAfter(this.spyB));
          });
  
          it("returns true if called anytime after other", function() {
              this.spyB();
              this.spyA();
              this.spyB();
  
              assert.isTrue(this.spyA.calledAfter(this.spyB));
          });
      });
  
      describe(".calledImmediatelyAfter", function() {
          beforeEach(function() {
              this.spyA = createSpy();
              this.spyB = createSpy();
              this.spyC = createSpy();
          });
  
          it("is function", function() {
              assert.isFunction(this.spyA.calledImmediatelyAfter);
          });
  
          it("returns true if first call to A was immediately after first to B", function() {
              this.spyB();
              this.spyA();
  
              assert(this.spyA.calledImmediatelyAfter(this.spyB));
          });
  
          it("compares calls directly", function() {
              this.spyB();
              this.spyA();
  
              assert(this.spyA.getCall(0).calledImmediatelyAfter(this.spyB.getCall(0)));
          });
  
          it("returns false if not called", function() {
              this.spyB();
  
              assert.isFalse(this.spyA.calledImmediatelyAfter(this.spyB));
          });
  
          it("returns false if other not called", function() {
              this.spyA();
  
              assert.isFalse(this.spyA.calledImmediatelyAfter(this.spyB));
          });
  
          it("returns false if other called last", function() {
              this.spyB();
              this.spyA();
              this.spyB();
  
              assert.isFalse(this.spyA.calledImmediatelyAfter(this.spyB));
          });
  
          it("returns false if another spy called between", function() {
              this.spyA();
              this.spyC();
              this.spyB();
  
              assert.isFalse(this.spyB.calledImmediatelyAfter(this.spyA));
          });
      });
  
      describe(".calledImmediatelyBefore", function() {
          beforeEach(function() {
              this.spyA = createSpy();
              this.spyB = createSpy();
              this.spyC = createSpy();
          });
  
          it("is function", function() {
              assert.isFunction(this.spyA.calledImmediatelyBefore);
          });
  
          it("returns true if first call to A was immediately after first to B", function() {
              this.spyB();
              this.spyA();
  
              assert(this.spyB.calledImmediatelyBefore(this.spyA));
          });
  
          it("compares calls directly", function() {
              this.spyB();
              this.spyA();
  
              assert(this.spyB.getCall(0).calledImmediatelyBefore(this.spyA.getCall(0)));
          });
  
          it("returns false if not called", function() {
              this.spyB();
  
              assert.isFalse(this.spyA.calledImmediatelyBefore(this.spyB));
          });
  
          it("returns false if other not called", function() {
              this.spyA();
  
              assert.isFalse(this.spyA.calledImmediatelyBefore(this.spyB));
          });
  
          it("returns false if other called last", function() {
              this.spyB();
              this.spyA();
              this.spyB();
  
              assert.isFalse(this.spyB.calledImmediatelyBefore(this.spyA));
          });
  
          it("returns false if another spy called between", function() {
              this.spyA();
              this.spyC();
              this.spyB();
  
              assert.isFalse(this.spyA.calledImmediatelyBefore(this.spyB));
          });
      });
  
      describe(".firstCall", function() {
          it("is undefined by default", function() {
              var spy = createSpy();
  
              assert.isNull(spy.firstCall);
          });
  
          it("is equal to getCall(0) result after first call", function() {
              var spy = createSpy();
  
              spy();
  
              var call0 = spy.getCall(0);
              assert.equals(spy.firstCall.callId, call0.callId);
              assert.same(spy.firstCall.spy, call0.spy);
          });
  
          it("is equal to getCall(0) after first call when control flow has continued after invocation", function() {
              var spy;
  
              function runAsserts() {
                  var call0 = spy.getCall(0);
                  assert.equals(spy.firstCall.callId, call0.callId);
                  assert.same(spy.firstCall.spy, call0.spy);
              }
  
              spy = createSpy(runAsserts);
  
              spy();
          });
  
          it("is tracked even if exceptions are thrown", function() {
              var spy = createSpy(function() {
                  throw new Error("an exception");
              });
  
              assert.exception(spy);
  
              refute.isNull(spy.firstCall);
          });
  
          it("has correct returnValue", function() {
              var spy = createSpy(function() {
                  return 42;
              });
  
              spy();
  
              assert.equals(spy.firstCall.returnValue, 42);
              assert(spy.firstCall.returned(42));
          });
  
          it("has correct exception", function() {
              var err = new Error();
              var spy = createSpy(function() {
                  throw err;
              });
  
              assert.exception(spy);
  
              assert.same(spy.firstCall.exception, err);
              assert(spy.firstCall.threw(err));
          });
      });
  
      describe(".secondCall", function() {
          it("is null by default", function() {
              var spy = createSpy();
  
              assert.isNull(spy.secondCall);
          });
  
          it("stills be null after first call", function() {
              var spy = createSpy();
              spy();
  
              assert.isNull(spy.secondCall);
          });
  
          it("is equal to getCall(1) result after second call", function() {
              var spy = createSpy();
  
              spy();
              spy();
  
              var call1 = spy.getCall(1);
              assert.equals(spy.secondCall.callId, call1.callId);
              assert.same(spy.secondCall.spy, call1.spy);
          });
      });
  
      describe(".thirdCall", function() {
          it("is undefined by default", function() {
              var spy = createSpy();
  
              assert.isNull(spy.thirdCall);
          });
  
          it("stills be undefined after second call", function() {
              var spy = createSpy();
              spy();
              spy();
  
              assert.isNull(spy.thirdCall);
          });
  
          it("is equal to getCall(1) result after second call", function() {
              var spy = createSpy();
  
              spy();
              spy();
              spy();
  
              var call2 = spy.getCall(2);
              assert.equals(spy.thirdCall.callId, call2.callId);
              assert.same(spy.thirdCall.spy, call2.spy);
          });
      });
  
      describe(".lastCall", function() {
          it("is undefined by default", function() {
              var spy = createSpy();
  
              assert.isNull(spy.lastCall);
          });
  
          it("is same as firstCall after first call", function() {
              var spy = createSpy();
  
              spy();
  
              assert.same(spy.lastCall.callId, spy.firstCall.callId);
              assert.same(spy.lastCall.spy, spy.firstCall.spy);
          });
  
          it("is same as secondCall after second call", function() {
              var spy = createSpy();
  
              spy();
              spy();
  
              assert.same(spy.lastCall.callId, spy.secondCall.callId);
              assert.same(spy.lastCall.spy, spy.secondCall.spy);
          });
  
          it("is same as thirdCall after third call", function() {
              var spy = createSpy();
  
              spy();
              spy();
              spy();
  
              assert.same(spy.lastCall.callId, spy.thirdCall.callId);
              assert.same(spy.lastCall.spy, spy.thirdCall.spy);
          });
  
          it("is equal to getCall(3) result after fourth call", function() {
              var spy = createSpy();
  
              spy();
              spy();
              spy();
              spy();
  
              var call3 = spy.getCall(3);
              assert.equals(spy.lastCall.callId, call3.callId);
              assert.same(spy.lastCall.spy, call3.spy);
          });
  
          it("is equal to getCall(4) result after fifth call", function() {
              var spy = createSpy();
  
              spy();
              spy();
              spy();
              spy();
              spy();
  
              var call4 = spy.getCall(4);
              assert.equals(spy.lastCall.callId, call4.callId);
              assert.same(spy.lastCall.spy, call4.spy);
          });
      });
  
      describe(".getCalls", function() {
          it("returns an empty Array by default", function() {
              var spy = createSpy();
  
              assert.isArray(spy.getCalls());
              assert.equals(spy.getCalls().length, 0);
          });
  
          it("is analogous to using getCall(n)", function() {
              var spy = createSpy();
  
              spy();
              spy();
  
              assert.equals(spy.getCalls(), [spy.getCall(0), spy.getCall(1)]);
          });
      });
  
      describe(".callArg", function() {
          it("is function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.callArg);
          });
  
          it("invokes argument at index for all calls", function() {
              var spy = createSpy();
              var callback = createSpy();
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              spy.callArg(2);
  
              assert(callback.calledTwice);
              assert(callback.alwaysCalledWith());
          });
  
          it("throws if argument at index is not a function", function() {
              var spy = createSpy();
              spy();
  
              assert.exception(
                  function() {
                      spy.callArg(1);
                  },
                  { name: "TypeError" }
              );
          });
  
          it("throws if spy was not yet invoked", function() {
              var spy = createSpy();
  
              assert.exception(
                  function() {
                      spy.callArg(0);
                  },
                  {
                      message: "spy cannot call arg since it was not yet invoked."
                  }
              );
          });
  
          it("includes spy name in error message", function() {
              var api = {
                  someMethod: function() {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
  
              assert.exception(
                  function() {
                      spy.callArg(0);
                  },
                  {
                      message: "someMethod cannot call arg since it was not yet invoked."
                  }
              );
          });
  
          it("throws if index is not a number", function() {
              var spy = createSpy();
              spy();
  
              assert.exception(
                  function() {
                      spy.callArg("");
                  },
                  { name: "TypeError" }
              );
          });
  
          it("passs additional arguments", function() {
              var spy = createSpy();
              var callback = createSpy();
              var array = [];
              var object = {};
              spy(callback);
  
              spy.callArg(0, "abc", 123, array, object);
  
              assert(callback.calledWith("abc", 123, array, object));
          });
  
          it("returns callbacks return values for all calls", function() {
              var spy = createSpy();
              var i = 0;
              var callback = createSpy(function() {
                  i++;
                  return "useful value " + i;
              });
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              var returnValues = spy.callArg(2);
  
              assert.equals(returnValues, ["useful value 1", "useful value 2"]);
          });
      });
  
      describe(".callArgOn", function() {
          it("is function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.callArgOn);
          });
  
          it("invokes argument at index for all calls", function() {
              var spy = createSpy();
              var callback = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              spy.callArgOn(2, thisObj);
  
              assert(callback.calledTwice);
              assert(callback.alwaysCalledWith());
              assert(callback.alwaysCalledOn(thisObj));
          });
  
          it("throws if argument at index is not a function", function() {
              var spy = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
              spy();
  
              assert.exception(
                  function() {
                      spy.callArgOn(1, thisObj);
                  },
                  { name: "TypeError" }
              );
          });
  
          it("throws if spy was not yet invoked", function() {
              var spy = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.callArgOn(0, thisObj);
                  },
                  {
                      message: "spy cannot call arg since it was not yet invoked."
                  }
              );
          });
  
          it("includes spy name in error message", function() {
              var api = {
                  someMethod: function() {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.callArgOn(0, thisObj);
                  },
                  {
                      message: "someMethod cannot call arg since it was not yet invoked."
                  }
              );
          });
  
          it("throws if index is not a number", function() {
              var spy = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
              spy();
  
              assert.exception(
                  function() {
                      spy.callArg("", thisObj);
                  },
                  { name: "TypeError" }
              );
          });
  
          it("pass additional arguments", function() {
              var spy = createSpy();
              var callback = createSpy();
              var array = [];
              var object = {};
              var thisObj = { name1: "value1", name2: "value2" };
              spy(callback);
  
              spy.callArgOn(0, thisObj, "abc", 123, array, object);
  
              assert(callback.calledWith("abc", 123, array, object));
              assert(callback.calledOn(thisObj));
          });
  
          it("returns callbacks return values for all calls", function() {
              var spy = createSpy();
              var i = 0;
              var callback = createSpy(function() {
                  i++;
                  return "useful value " + i;
              });
              var thisObj = { name1: "value1", name2: "value2" };
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              var returnValues = spy.callArgOn(2, thisObj);
  
              assert.equals(returnValues, ["useful value 1", "useful value 2"]);
          });
      });
  
      describe(".callArgWith", function() {
          it("is alias for callArg", function() {
              var spy = createSpy();
  
              assert.same(spy.callArgWith, spy.callArg);
          });
      });
  
      describe(".callArgOnWith", function() {
          it("is alias for callArgOn", function() {
              var spy = createSpy();
  
              assert.same(spy.callArgOnWith, spy.callArgOn);
          });
      });
  
      describe(".yield", function() {
          it("is function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.yield);
          });
  
          it("invokes first function arg for all calls", function() {
              var spy = createSpy();
              var callback = createSpy();
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              spy.yield();
  
              assert(callback.calledTwice);
              assert(callback.alwaysCalledWith());
          });
  
          it("throws if spy was not yet invoked", function() {
              var spy = createSpy();
  
              assert.exception(
                  function() {
                      spy.yield();
                  },
                  {
                      message: "spy cannot yield since it was not yet invoked."
                  }
              );
          });
  
          it("includes spy name in error message", function() {
              var api = {
                  someMethod: function() {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
  
              assert.exception(
                  function() {
                      spy.yield();
                  },
                  {
                      message: "someMethod cannot yield since it was not yet invoked."
                  }
              );
          });
  
          it("passs additional arguments", function() {
              var spy = createSpy();
              var callback = createSpy();
              var array = [];
              var object = {};
              spy(callback);
  
              spy.yield("abc", 123, array, object);
  
              assert(callback.calledWith("abc", 123, array, object));
          });
  
          it("returns callbacks return values for all calls", function() {
              var spy = createSpy();
              var i = 0;
              var callback = createSpy(function() {
                  i++;
                  return "useful value " + i;
              });
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              var returnValues = spy.yield();
  
              assert.equals(returnValues, ["useful value 1", "useful value 2"]);
          });
      });
  
      describe(".invokeCallback", function() {
          it("is alias for yield", function() {
              var spy = createSpy();
  
              assert.same(spy.invokeCallback, spy.yield);
          });
      });
  
      describe(".yieldOn", function() {
          it("is function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.yieldOn);
          });
  
          it("invokes first function arg for all calls", function() {
              var spy = createSpy();
              var callback = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              spy.yieldOn(thisObj);
  
              assert(callback.calledTwice);
              assert(callback.alwaysCalledWith());
              assert(callback.alwaysCalledOn(thisObj));
          });
  
          it("throws if spy was not yet invoked", function() {
              var spy = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.yieldOn(thisObj);
                  },
                  {
                      message: "spy cannot yield since it was not yet invoked."
                  }
              );
          });
  
          it("includes spy name in error message", function() {
              var api = {
                  someMethod: function() {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.yieldOn(thisObj);
                  },
                  {
                      message: "someMethod cannot yield since it was not yet invoked."
                  }
              );
          });
  
          it("pass additional arguments", function() {
              var spy = createSpy();
              var callback = createSpy();
              var array = [];
              var object = {};
              var thisObj = { name1: "value1", name2: "value2" };
              spy(callback);
  
              spy.yieldOn(thisObj, "abc", 123, array, object);
  
              assert(callback.calledWith("abc", 123, array, object));
              assert(callback.calledOn(thisObj));
          });
  
          it("returns callbacks return values for all calls", function() {
              var spy = createSpy();
              var i = 0;
              var callback = createSpy(function() {
                  i++;
                  return "useful value " + i;
              });
              var thisObj = { name1: "value1", name2: "value2" };
              spy(1, 2, callback);
              spy(3, 4, callback);
  
              var returnValues = spy.yieldOn(thisObj);
  
              assert.equals(returnValues, ["useful value 1", "useful value 2"]);
          });
      });
  
      describe(".yieldTo", function() {
          it("is function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.yieldTo);
          });
  
          it("invokes first function arg for all calls", function() {
              var spy = createSpy();
              var callback = createSpy();
              spy(1, 2, { success: callback });
              spy(3, 4, { success: callback });
  
              spy.yieldTo("success");
  
              assert(callback.calledTwice);
              assert(callback.alwaysCalledWith());
          });
  
          it("throws if spy was not yet invoked", function() {
              var spy = createSpy();
  
              assert.exception(
                  function() {
                      spy.yieldTo("success");
                  },
                  {
                      message: "spy cannot yield to 'success' since it was not yet invoked."
                  }
              );
          });
  
          it("includes spy name in error message", function() {
              var api = {
                  someMethod: function() {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
  
              assert.exception(
                  function() {
                      spy.yieldTo("success");
                  },
                  {
                      message: "someMethod cannot yield to 'success' since it was not yet invoked."
                  }
              );
          });
  
          it("throws readable message for symbol when spy was not yet invoked", function() {
              if (typeof Symbol !== "function") {
                  this.skip();
              }
  
              var spy = createSpy();
  
              assert.exception(
                  function() {
                      spy.yieldTo(Symbol());
                  },
                  {
                      message: "spy cannot yield to 'Symbol()' since it was not yet invoked."
                  }
              );
          });
  
          it("pass additional arguments", function() {
              var spy = createSpy();
              var callback = createSpy();
              var array = [];
              var object = {};
              spy({ test: callback });
  
              spy.yieldTo("test", "abc", 123, array, object);
  
              assert(callback.calledWith("abc", 123, array, object));
          });
  
          it("returns callbacks return values for all calls", function() {
              var spy = createSpy();
              var i = 0;
              var callback = createSpy(function() {
                  i++;
                  return "useful value " + i;
              });
              spy(1, 2, { success: callback });
              spy(3, 4, { success: callback });
  
              var returnValues = spy.yieldTo("success");
  
              assert.equals(returnValues, ["useful value 1", "useful value 2"]);
          });
      });
  
      describe(".yieldToOn", function() {
          it("is function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.yieldToOn);
          });
  
          it("invokes first function arg for all calls", function() {
              var spy = createSpy();
              var callback = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
              spy(1, 2, { success: callback });
              spy(3, 4, { success: callback });
  
              spy.yieldToOn("success", thisObj);
  
              assert(callback.calledTwice);
              assert(callback.alwaysCalledWith());
              assert(callback.alwaysCalledOn(thisObj));
          });
  
          it("throws if spy was not yet invoked", function() {
              var spy = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.yieldToOn("success", thisObj);
                  },
                  {
                      message: "spy cannot yield to 'success' since it was not yet invoked."
                  }
              );
          });
  
          it("includes spy name in error message", function() {
              var api = {
                  someMethod: function() {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.yieldToOn("success", thisObj);
                  },
                  {
                      message: "someMethod cannot yield to 'success' since it was not yet invoked."
                  }
              );
          });
  
          it("throws readable message for symbol when spy was not yet invoked", function() {
              if (typeof Symbol !== "function") {
                  this.skip();
              }
  
              var spy = createSpy();
              var thisObj = { name1: "value1", name2: "value2" };
  
              assert.exception(
                  function() {
                      spy.yieldToOn(Symbol(), thisObj);
                  },
                  {
                      message: "spy cannot yield to 'Symbol()' since it was not yet invoked."
                  }
              );
          });
  
          it("pass additional arguments", function() {
              var spy = createSpy();
              var callback = createSpy();
              var array = [];
              var object = {};
              var thisObj = { name1: "value1", name2: "value2" };
              spy({ test: callback });
  
              spy.yieldToOn("test", thisObj, "abc", 123, array, object);
  
              assert(callback.calledWith("abc", 123, array, object));
              assert(callback.calledOn(thisObj));
          });
  
          it("returns callbacks return values for all calls", function() {
              var spy = createSpy();
              var i = 0;
              var callback = createSpy(function() {
                  i++;
                  return "useful value " + i;
              });
              var thisObj = { name1: "value1", name2: "value2" };
              spy(1, 2, { success: callback });
              spy(3, 4, { success: callback });
  
              var returnValues = spy.yieldToOn("success", thisObj);
  
              assert.equals(returnValues, ["useful value 1", "useful value 2"]);
          });
      });
  
      describe(".throwArg", function() {
          it("should be a function", function() {
              var spy = createSpy();
  
              assert.isFunction(spy.throwArg);
          });
  
          it("should throw if spy hasn't been called", function() {
              var spy = createSpy();
  
              assert.exception(
                  function() {
                      spy.throwArg(0);
                  },
                  function(error) {
                      return error.message === "spy cannot throw arg since it was not yet invoked.";
                  }
              );
          });
  
          it("should throw if there aren't enough arguments in the previous spy call", function() {
              var spy = createSpy();
  
              spy("only", "four", "arguments", "here");
  
              assert.exception(
                  function() {
                      spy.throwArg(7);
                  },
                  function(error) {
                      return error.message === "Not enough arguments: 7 required but only 4 present";
                  }
              );
          });
  
          it("should throw specified argument", function() {
              var spy = createSpy();
              var expectedError = new TypeError("catpants");
  
              spy(true, false, null, expectedError, "meh");
              assert.exception(
                  function() {
                      spy.throwArg(3);
                  },
                  function(error) {
                      return error instanceof TypeError && error.message === expectedError.message;
                  }
              );
          });
      });
  
      describe(".resetHistory", function() {
          it("return same object", function() {
              var spy = createSpy();
              var reset = spy.resetHistory();
  
              assert(reset === spy);
          });
  
          it("throws if called during spy invocation", function() {
              var spy = createSpy(function() {
                  spy.resetHistory();
              });
  
              assert.exception(spy, "InvalidResetException");
          });
      });
  
      describe(".length", function() {
          it("is zero by default", function() {
              var spy = createSpy();
  
              assert.equals(spy.length, 0);
          });
  
          it("matches the function length", function() {
              var api = {
                  // eslint-disable-next-line no-unused-vars
                  someMethod: function(a, b, c) {
                      return;
                  }
              };
              var spy = createSpy(api, "someMethod");
  
              assert.equals(spy.length, 3);
          });
      });
  
      describe(".matchingFakes", function() {
          beforeEach(function() {
              this.spy = createSpy();
          });
  
          it("is function", function() {
              assert.isFunction(this.spy.matchingFakes);
          });
  
          it("returns an empty array by default", function() {
              assert.equals(this.spy.matchingFakes([]), []);
              assert.equals(this.spy.matchingFakes([1]), []);
              assert.equals(this.spy.matchingFakes([1, 1]), []);
          });
  
          it("returns one matched fake", function() {
              this.spy.withArgs(1);
              this.spy.withArgs(2);
  
              assert.equals(this.spy.matchingFakes([1]), [this.spy.withArgs(1)]);
              assert.equals(this.spy.matchingFakes([2]), [this.spy.withArgs(2)]);
          });
  
          it("return some matched fake", function() {
              this.spy.withArgs(1);
              this.spy.withArgs(1, 1);
              this.spy.withArgs(2);
  
              assert.equals(this.spy.matchingFakes([]), []);
              assert.equals(this.spy.matchingFakes([1]), [this.spy.withArgs(1)]);
              assert.equals(this.spy.matchingFakes([1, 1]), [this.spy.withArgs(1), this.spy.withArgs(1, 1)]);
          });
      });
  
      describe(".id", function() {
          it("should start with 'spy#'", function() {
              for (var i = 0; i < 10; i++) {
                  assert.isTrue(createSpy().id.indexOf("spy#") === 0);
              }
          });
      });
  
      describe("non enumerable properties", function() {
          it("create and call spy apis", function() {
              var spy = createSpy();
              assert.equals(Object.keys(spy), []);
  
              // call spy and verify no enumerable properties are added
              spy(15);
              assert.equals(Object.keys(spy), []);
  
              // it should still work to add properties
              spy.fooBar = 1;
              assert.equals(Object.keys(spy), ["fooBar"]);
  
              // call some spy APIs and verify no enumerable properties are added
              spy.withArgs(1);
              spy.calledBefore(createSpy());
              spy.calledAfter(createSpy());
              spy.calledOn(undefined);
              spy.calledWith(15);
              spy.calledWithNew();
              spy.threw();
              spy.returned("ret");
              assert.equals(spy.thisValues.length, 1);
              assert.equals(spy.exceptions.length, 1);
              assert.equals(spy.returnValues.length, 1);
              assert.equals(Object.keys(spy), ["fooBar"]);
  
              // verify that reset history doesn't change enumerable properties
              spy.resetHistory();
              assert.equals(Object.keys(spy), ["fooBar"]);
          });
  
          it("create spy from function", function() {
              var func = function() {
                  throw new Error("aError");
              };
              func.aProp = 42;
              var spy = createSpy(func);
  
              assert.equals(spy.aProp, 42);
              assert.equals(Object.keys(spy), Object.keys(func));
              assert.equals(Object.keys(spy), ["aProp"]);
  
              assert.exception(function() {
                  spy();
              });
              assert(spy.threw());
  
              spy.resetHistory();
              assert.equals(Object.keys(spy), ["aProp"]);
          });
      });
  
      describe("everything", function() {
          // eslint-disable-next-line mocha/no-setup-in-describe
          require("./shared-spy-stub-everything-tests")(createSpy);
      });
  });
  