describe('Spies', function () {
  it('should replace the specified function with a spy object', function() {
    var originalFunctionWasCalled = false;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
      }
    };
    this.spyOn(TestClass, 'someFunction');

    expect(TestClass.someFunction.wasCalled).toEqual(false);
    expect(TestClass.someFunction.callCount).toEqual(0);
    TestClass.someFunction('foo');
    expect(TestClass.someFunction.wasCalled).toEqual(true);
    expect(TestClass.someFunction.callCount).toEqual(1);
    expect(TestClass.someFunction.mostRecentCall.args).toEqual(['foo']);
    expect(TestClass.someFunction.mostRecentCall.object).toEqual(TestClass);
    expect(originalFunctionWasCalled).toEqual(false);

    TestClass.someFunction('bar');
    expect(TestClass.someFunction.callCount).toEqual(2);
    expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']);
  });

  it('should allow you to view args for a particular call', function() {
    var originalFunctionWasCalled = false;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
      }
    };
    this.spyOn(TestClass, 'someFunction');

    TestClass.someFunction('foo');
    TestClass.someFunction('bar');
    expect(TestClass.someFunction.calls[0].args).toEqual(['foo']);
    expect(TestClass.someFunction.calls[1].args).toEqual(['bar']);
    expect(TestClass.someFunction.mostRecentCall.args).toEqual(['bar']);
  });

  it('should be possible to call through to the original method, or return a specific result', function() {
    var originalFunctionWasCalled = false;
    var passedArgs;
    var passedObj;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
        passedArgs = arguments;
        passedObj = this;
        return "return value from original function";
      }
    };

    this.spyOn(TestClass, 'someFunction').andCallThrough();
    var result = TestClass.someFunction('arg1', 'arg2');
    expect(result).toEqual("return value from original function");
    expect(originalFunctionWasCalled).toEqual(true);
    expect(passedArgs).toEqual(['arg1', 'arg2']);
    expect(passedObj).toEqual(TestClass);
    expect(TestClass.someFunction.wasCalled).toEqual(true);
  });

  it('should be possible to return a specific value', function() {
    var originalFunctionWasCalled = false;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
        return "return value from original function";
      }
    };

    this.spyOn(TestClass, 'someFunction').andReturn("some value");
    originalFunctionWasCalled = false;
    var result = TestClass.someFunction('arg1', 'arg2');
    expect(result).toEqual("some value");
    expect(originalFunctionWasCalled).toEqual(false);
  });

  it('should be possible to throw a specific error', function() {
    var originalFunctionWasCalled = false;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
        return "return value from original function";
      }
    };

    this.spyOn(TestClass, 'someFunction').andThrow(new Error('fake error'));
    var exception;
    try {
      TestClass.someFunction('arg1', 'arg2');
    } catch (e) {
      exception = e;
    }
    expect(exception.message).toEqual('fake error');
    expect(originalFunctionWasCalled).toEqual(false);
  });

  it('should be possible to call a specified function', function() {
    var originalFunctionWasCalled = false;
    var fakeFunctionWasCalled = false;
    var passedArgs;
    var passedObj;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
        return "return value from original function";
      }
    };

    this.spyOn(TestClass, 'someFunction').andCallFake(function() {
      fakeFunctionWasCalled = true;
      passedArgs = arguments;
      passedObj = this;
      return "return value from fake function";
    });

    var result = TestClass.someFunction('arg1', 'arg2');
    expect(result).toEqual("return value from fake function");
    expect(originalFunctionWasCalled).toEqual(false);
    expect(fakeFunctionWasCalled).toEqual(true);
    expect(passedArgs).toEqual(['arg1', 'arg2']);
    expect(passedObj).toEqual(TestClass);
    expect(TestClass.someFunction.wasCalled).toEqual(true);
  });

  it('is torn down when this.removeAllSpies is called', function() {
    var originalFunctionWasCalled = false;
    var TestClass = {
      someFunction: function() {
        originalFunctionWasCalled = true;
      }
    };
    this.spyOn(TestClass, 'someFunction');

    TestClass.someFunction('foo');
    expect(originalFunctionWasCalled).toEqual(false);

    this.removeAllSpies();

    TestClass.someFunction('foo');
    expect(originalFunctionWasCalled).toEqual(true);
  });

  it('calls removeAllSpies during spec finish', function() {
    var test = new jasmine.Spec(new jasmine.Env(), {}, 'sample test');

    this.spyOn(test, 'removeAllSpies');

    test.finish();

    expect(test.removeAllSpies).wasCalled();
  });

  it('throws an exception when some method is spied on twice', function() {
    var TestClass = { someFunction: function() {
    } };
    this.spyOn(TestClass, 'someFunction');
    var exception;
    try {
      this.spyOn(TestClass, 'someFunction');
    } catch (e) {
      exception = e;
    }
    expect(exception).toBeDefined();
  });

  it('should be able to reset a spy', function() {
    var TestClass = { someFunction: function() {} };
    this.spyOn(TestClass, 'someFunction');

    expect(TestClass.someFunction).not.toHaveBeenCalled();
    TestClass.someFunction();
    expect(TestClass.someFunction).toHaveBeenCalled();
    TestClass.someFunction.reset();
    expect(TestClass.someFunction).not.toHaveBeenCalled();
    expect(TestClass.someFunction.callCount).toEqual(0);
  });

  describe("createSpyObj", function() {
    it("should create an object with a bunch of spy methods when you call jasmine.createSpyObj()", function() {
      var spyObj = jasmine.createSpyObj('BaseName', ['method1', 'method2']);
      expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)});
      expect(spyObj.method1.identity).toEqual('BaseName.method1');
      expect(spyObj.method2.identity).toEqual('BaseName.method2');
    });

    it("should throw if you do not pass an array argument", function() {
      expect(function() {
        jasmine.createSpyObj('BaseName');
      }).toThrow('createSpyObj requires a non-empty array of method names to create spies for');
    });

    it("should throw if you pass an empty array argument", function() {
      expect(function() {
        jasmine.createSpyObj('BaseName');
      }).toThrow('createSpyObj requires a non-empty array of method names to create spies for');
    });
  });

});