describe(“Spec”, function() {

it("#isPendingSpecException returns true for a pending spec exception", function() {
  var e = new Error(j$.Spec.pendingSpecExceptionMessage);

  expect(j$.Spec.isPendingSpecException(e)).toBe(true);
});

it("#isPendingSpecException returns true for a pending spec exception (even when FF bug is present)", function() {
  var fakeError = {
    toString: function() { return "Error: " + j$.Spec.pendingSpecExceptionMessage; }
  };

  expect(j$.Spec.isPendingSpecException(fakeError)).toBe(true);
});

it("#isPendingSpecException returns true for a pending spec exception with a custom message", function() {
  expect(j$.Spec.isPendingSpecException(j$.Spec.pendingSpecExceptionMessage + 'foo')).toBe(true);
});

it("#isPendingSpecException returns false for not a pending spec exception", function() {
  var e = new Error("foo");

  expect(j$.Spec.isPendingSpecException(e)).toBe(false);
});

it("#isPendingSpecException returns false for thrown values that don't have toString", function() {
  expect(j$.Spec.isPendingSpecException(void 0)).toBe(false);
});

it("delegates execution to a QueueRunner", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    spec = new j$.Spec({
      description: 'my test',
      id: 'some-id',
      queueableFn: { fn: function() {} },
      queueRunnerFactory: fakeQueueRunner
    });

  spec.execute();

  expect(fakeQueueRunner).toHaveBeenCalled();
});

it("should call the start callback on execution", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    startCallback = jasmine.createSpy('startCallback'),
    spec = new j$.Spec({
      id: 123,
      description: 'foo bar',
      queueableFn: { fn: function() {} },
      onStart: startCallback,
      queueRunnerFactory: fakeQueueRunner
    });

  spec.execute();

  // TODO: due to some issue with the Pretty Printer, this line fails, but the other two pass.
  // This means toHaveBeenCalledWith on IE8 will always be broken.

  //   expect(startCallback).toHaveBeenCalledWith(spec);
  expect(startCallback).toHaveBeenCalled();
  expect(startCallback.calls.first().object).toEqual(spec);
});

it("should call the start callback on execution but before any befores are called", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    beforesWereCalled = false,
    startCallback = jasmine.createSpy('start-callback').and.callFake(function() {
      expect(beforesWereCalled).toBe(false);
    }),
    spec = new j$.Spec({
      queueableFn: { fn: function() {} },
      beforeFns: function() {
        return [function() {
          beforesWereCalled = true
        }]
      },
      onStart: startCallback,
      queueRunnerFactory: fakeQueueRunner
    });

  spec.execute();

  expect(startCallback).toHaveBeenCalled();
});

it("provides all before fns and after fns to be run", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    before = jasmine.createSpy('before'),
    after = jasmine.createSpy('after'),
    queueableFn = { fn: jasmine.createSpy('test body').and.callFake(function() {
      expect(before).toHaveBeenCalled();
      expect(after).not.toHaveBeenCalled();
    }) },
    spec = new j$.Spec({
      queueableFn: queueableFn,
      beforeAndAfterFns: function() {
        return {befores: [before], afters: [after]}
      },
      queueRunnerFactory: fakeQueueRunner
    });

  spec.execute();

  var allSpecFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns;
  expect(allSpecFns).toEqual([before, queueableFn, after]);
});

it("is marked pending if created without a function body", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),

    startCallback = jasmine.createSpy('startCallback'),
    resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      onStart: startCallback,
      queueableFn: { fn: null },
      resultCallback: resultCallback,
      queueRunnerFactory: fakeQueueRunner
    });

  expect(spec.status()).toBe('pending');
});

it("can be disabled, but still calls callbacks", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    startCallback = jasmine.createSpy('startCallback'),
    specBody = jasmine.createSpy('specBody'),
    resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      onStart:startCallback,
      queueableFn: { fn: specBody },
      resultCallback: resultCallback,
      queueRunnerFactory: fakeQueueRunner
    });

  spec.disable();

  expect(spec.status()).toBe('disabled');

  spec.execute();

  expect(fakeQueueRunner).not.toHaveBeenCalled();
  expect(specBody).not.toHaveBeenCalled();

  expect(startCallback).toHaveBeenCalled();
  expect(resultCallback).toHaveBeenCalled();
});

it("can be disabled at execution time by a parent", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    startCallback = jasmine.createSpy('startCallback'),
    specBody = jasmine.createSpy('specBody'),
    resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      onStart:startCallback,
      queueableFn: { fn: specBody },
      resultCallback: resultCallback,
      queueRunnerFactory: fakeQueueRunner
    });

  spec.execute(undefined, false);

  expect(spec.result.status).toBe('disabled');

  expect(fakeQueueRunner).not.toHaveBeenCalled();
  expect(specBody).not.toHaveBeenCalled();

  expect(startCallback).toHaveBeenCalled();
  expect(resultCallback).toHaveBeenCalled();
});

it("can be marked pending, but still calls callbacks when executed", function() {
  var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
    startCallback = jasmine.createSpy('startCallback'),
    resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      onStart: startCallback,
      resultCallback: resultCallback,
      description: "with a spec",
      getSpecName: function() {
        return "a suite with a spec"
      },
      queueRunnerFactory: fakeQueueRunner,
      queueableFn: { fn: null }
    });

  spec.pend();

  expect(spec.status()).toBe('pending');

  spec.execute();

  expect(fakeQueueRunner).not.toHaveBeenCalled();

  expect(startCallback).toHaveBeenCalled();
  expect(resultCallback).toHaveBeenCalledWith({
    id: spec.id,
    status: 'pending',
    description: 'with a spec',
    fullName: 'a suite with a spec',
    failedExpectations: [],
    passedExpectations: [],
    pendingReason: ''
  });
});

it("should call the done callback on execution complete", function() {
  var done = jasmine.createSpy('done callback'),
    spec = new j$.Spec({
      queueableFn: { fn: function() {} },
      catchExceptions: function() { return false; },
      resultCallback: function() {},
      queueRunnerFactory: function(attrs) { attrs.onComplete(); }
    });

  spec.execute(done);

  expect(done).toHaveBeenCalled();
});

it("#status returns passing by default", function() {
  var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} });
  expect(spec.status()).toBe('passed');
});

it("#status returns passed if all expectations in the spec have passed", function() {
  var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} });
  spec.addExpectationResult(true);
  expect(spec.status()).toBe('passed');
});

it("#status returns failed if any expectations in the spec have failed", function() {
  var spec = new j$.Spec({queueableFn: { fn: jasmine.createSpy("spec body") } });
  spec.addExpectationResult(true);
  spec.addExpectationResult(false);
  expect(spec.status()).toBe('failed');
});

it("keeps track of passed and failed expectations", function() {
  var resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      queueableFn: { fn: jasmine.createSpy("spec body") },
      expectationResultFactory: function (data) { return data; },
      queueRunnerFactory: function(attrs) { attrs.onComplete(); },
      resultCallback: resultCallback
    });
  spec.addExpectationResult(true, 'expectation1');
  spec.addExpectationResult(false, 'expectation2');

  spec.execute();

  expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['expectation1']);
  expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['expectation2']);
});

it("throws an ExpectationFailed error upon receiving a failed expectation when 'throwOnExpectationFailure' is set", function() {
  var resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
    queueableFn: { fn: function() {} },
    expectationResultFactory: function(data) { return data; },
    queueRunnerFactory: function(attrs) { attrs.onComplete(); },
    resultCallback: resultCallback,
    throwOnExpectationFailure: true
  });

  spec.addExpectationResult(true, 'passed');
  expect(function() {
    spec.addExpectationResult(false, 'failed')
  }).toThrowError(j$.errors.ExpectationFailed);

  spec.execute();

  expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['passed']);
  expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['failed']);
});

it("does not throw an ExpectationFailed error when handling an error", function() {
  var resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      queueableFn: { fn: function() {} },
      expectationResultFactory: function(data) { return data; },
      queueRunnerFactory: function(attrs) { attrs.onComplete(); },
      resultCallback: resultCallback,
      throwOnExpectationFailure: true
    });

  spec.onException('failing exception');
});

it("can return its full name", function() {
  var specNameSpy = jasmine.createSpy('specNameSpy').and.returnValue('expected val');

  var spec = new j$.Spec({
    getSpecName: specNameSpy,
    queueableFn: { fn: null }
  });

  expect(spec.getFullName()).toBe('expected val');
  expect(specNameSpy.calls.mostRecent().args[0].id).toEqual(spec.id);
});

describe("when a spec is marked pending during execution", function() {
  it("should mark the spec as pending", function() {
    var fakeQueueRunner = function(opts) {
        opts.onException(new Error(j$.Spec.pendingSpecExceptionMessage));
      },
      spec = new j$.Spec({
        description: 'my test',
        id: 'some-id',
        queueableFn: { fn: function() { } },
        queueRunnerFactory: fakeQueueRunner
      });

    spec.execute();

    expect(spec.status()).toEqual("pending");
    expect(spec.result.pendingReason).toEqual('');
  });

  it("should set the pendingReason", function() {
    var fakeQueueRunner = function(opts) {
        opts.onException(new Error(j$.Spec.pendingSpecExceptionMessage + 'custom message'));
      },
      spec = new j$.Spec({
        description: 'my test',
        id: 'some-id',
        queueableFn: { fn: function() { } },
        queueRunnerFactory: fakeQueueRunner
      });

    spec.execute();

    expect(spec.status()).toEqual("pending");
    expect(spec.result.pendingReason).toEqual('custom message');
  });
});

it("should log a failure when handling an exception", function() {
  var resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      queueableFn: { fn: function() {} },
      expectationResultFactory: function(data) { return data; },
      queueRunnerFactory: function(attrs) { attrs.onComplete(); },
      resultCallback: resultCallback
    });

  spec.onException('foo');
  spec.execute();

  expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([{
    error: 'foo',
    matcherName: '',
    passed: false,
    expected: '',
    actual: ''
  }]);
});

it("should not log an additional failure when handling an ExpectationFailed error", function() {
  var resultCallback = jasmine.createSpy('resultCallback'),
    spec = new j$.Spec({
      queueableFn: { fn: function() {} },
      expectationResultFactory: function(data) { return data; },
      queueRunnerFactory: function(attrs) { attrs.onComplete(); },
      resultCallback: resultCallback
    });

  spec.onException(new j$.errors.ExpectationFailed());
  spec.execute();

  expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]);
});

it("retrieves a result with updated status", function() {
  var spec = new j$.Spec({ queueableFn: { fn: function() {} } });

  expect(spec.getResult().status).toBe('passed');
});

it("retrives a result with disabled status", function() {
  var spec = new j$.Spec({ queueableFn: { fn: function() {} } });
  spec.disable();

  expect(spec.getResult().status).toBe('disabled');
});

it("retrives a result with pending status", function() {
  var spec = new j$.Spec({ queueableFn: { fn: function() {} } });
  spec.pend();

  expect(spec.getResult().status).toBe('pending');
});

it("should not be executable when disabled", function() {
  var spec = new j$.Spec({
    queueableFn: { fn: function() {} }
  });
  spec.disable();

  expect(spec.isExecutable()).toBe(false);
});

it("should be executable when pending", function() {
  var spec = new j$.Spec({
    queueableFn: { fn: function() {} }
  });
  spec.pend();

  expect(spec.isExecutable()).toBe(true);
});

it("should be executable when not disabled or pending", function() {
  var spec = new j$.Spec({
    queueableFn: { fn: function() {} }
  });

  expect(spec.isExecutable()).toBe(true);
});

});