var jasmineNode = require(__dirname + "/../lib/jasmine-node/reporter").jasmineNode;

describe('TerminalReporter', function() {
  beforeEach(function() {
    var config = {}
    this.reporter = new jasmineNode.TerminalReporter(config);
  });

  describe("initialize", function() {
    it('initializes print_ from config', function() {
      var config = { print: true };
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.print_).toBeTruthy();
    });

    it('initializes color_ from config', function() {
      var config = { color: true }
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.color_).toEqual(jasmineNode.TerminalReporter.prototype.ANSIColors);
    });

    it('initializes includeStackTrace_ from config', function () {
        var config = {}
        this.reporter = new jasmineNode.TerminalReporter(config);
        expect(this.reporter.includeStackTrace_).toBeTruthy();
    });

    it('sets the started_ flag to false', function() {
      var config = {}
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.started_).toBeFalsy();
    });

    it('sets the finished_ flag to false', function() {
      var config = {}
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.finished_).toBeFalsy();
    });

    it('initializes the suites_ array', function() {
      var config = {}
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.suites_.length).toEqual(0);
    });

    it('initializes the specResults_ to an Object', function() {
      var config = {}
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.specResults_).toBeDefined();
    });

    it('initializes the failures_ array', function() {
      var config = {}
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.failures_.length).toEqual(0);
    });

    it('sets the callback_ property to false by default', function() {
      var config = {}
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.callback_).toEqual(false)
    });

    it('sets the callback_ property to onComplete if supplied', function() {
      var foo = function() { }
      var config = { onComplete: foo }
      this.reporter = new jasmineNode.TerminalReporter(config);
      expect(this.reporter.callback_).toBe(foo)
    });
  });

  describe('when the report runner starts', function() {
    beforeEach(function() {
      this.spy = spyOn(this.reporter, 'printLine_');

      var runner = {
        topLevelSuites: function() {
          var suites = [];
          var suite = { id: 25 };
          suites.push(suite);
          return suites;
        }
      };
      this.reporter.reportRunnerStarting(runner);
    });

    it('sets the started_ field to true', function() {
      expect(this.reporter.started_).toBeTruthy();
    });

    it('sets the startedAt field', function() {
      // instanceof does not work cross-context (such as when run with requirejs)
      var ts = Object.prototype.toString;
      expect(ts.call(this.reporter.startedAt)).toBe(ts.call(new Date()));
    });

    it('buildes the suites_ collection', function() {
      expect(this.reporter.suites_.length).toEqual(1);
      expect(this.reporter.suites_[0].id).toEqual(25);
    });
  });

  describe('the summarize_ creates suite and spec tree', function() {
    beforeEach(function() {
      this.spec = {
        id: 1,
        description: 'the spec',
        isSuite: false
      }
    });

    it('creates a summary object from spec', function() {
      var result = this.reporter.summarize_(this.spec);

      expect(result.id).toEqual(1);
      expect(result.name).toEqual('the spec');
      expect(result.type).toEqual('spec');
      expect(result.children.length).toEqual(0);
    });

    it('creates a summary object from suite with 1 spec', function() {
      var env = { nextSuiteId: false }
      var suite = new jasmine.Suite(env, 'suite name', undefined, undefined);
      suite.description = 'the suite';
      suite.parentSuite = null;
      suite.children_.push(this.spec);

      var result = this.reporter.summarize_(suite);
      expect(result.name).toEqual('the suite');
      expect(result.type).toEqual('suite');
      expect(result.children.length).toEqual(1);

      var suiteChildSpec = result.children[0];
      expect(suiteChildSpec.id).toEqual(1);
    });
  });

  describe('reportRunnerResults', function() {
    beforeEach(function() {
      this.printLineSpy = spyOn(this.reporter, 'printLine_');
    });

    it('generates the report', function() {
      var failuresSpy = spyOn(this.reporter, 'reportFailures_');
      var printRunnerResultsSpy = spyOn(this.reporter, 'printRunnerResults_').
                          andReturn('this is the runner result');

      var callbackSpy = spyOn(this.reporter, 'callback_');

      var runner = {
        results: function() {
          var result = { failedCount: 0 };
          return result;
        },
        specs: function() { return []; }
      };
      this.reporter.startedAt = new Date();

      this.reporter.reportRunnerResults(runner);

      expect(failuresSpy).toHaveBeenCalled();
      expect(this.printLineSpy).toHaveBeenCalled();
      expect(callbackSpy).toHaveBeenCalled();
    });
  });

  describe('reportSpecResults', function() {
    beforeEach(function() {
      this.printSpy = spyOn(this.reporter, 'print_');
      this.spec = {
        id: 1,
        description: 'the spec',
        isSuite: false,
        results: function() {
          var result = {
            passed: function() { return true; }
          }
          return result;
        }
      }
    });

    it('prints a \'.\' for pass', function() {
      this.reporter.reportSpecResults(this.spec);
      expect(this.printSpy).toHaveBeenCalledWith('.');
    });

    it('prints an \'F\' for failure', function() {
      var addFailureToFailuresSpy = spyOn(this.reporter, 'addFailureToFailures_');
      var results = function() {
        var result = {
          passed: function() { return false; }
        }
        return result;
      }
      this.spec.results = results;

      this.reporter.reportSpecResults(this.spec);

      expect(this.printSpy).toHaveBeenCalledWith('F');
      expect(addFailureToFailuresSpy).toHaveBeenCalled();
    });
  });

  describe('addFailureToFailures', function() {
    it('adds message and stackTrace to failures_', function() {
      var spec = {
        suite: {
          getFullName: function() { return 'Suite name' }
        },
        description: 'the spec',
        results: function() {
          var result = {
            items_: function() {
              var theItems = new Array();
              var item = {
                passed_: false,
                message: 'the message',
                trace: {
                  stack: 'the stack'
                }
              }
              theItems.push(item);
              return theItems;
            }.call()
          };
          return result;
        }
      };

      this.reporter.addFailureToFailures_(spec);

      var failures = this.reporter.failures_;
      expect(failures.length).toEqual(1);
      var failure = failures[0];
      expect(failure.spec).toEqual('Suite name the spec');
      expect(failure.message).toEqual('the message');
      expect(failure.stackTrace).toEqual('the stack');
    });
  });

  describe('prints the runner results', function() {
    beforeEach(function() {
      this.runner = {
        results: function() {
          var _results = {
            totalCount: 23,
            failedCount: 52
          };
          return _results;
        },
        specs: function() {
          var _specs = new Array();
          spec = {
            results: function() {
              var _results = {
                skipped: false
              }
              return _results;
            }
          };
          _specs.push(spec);
          return _specs;
        }
      };
    });

    it('uses the specs\'s length, totalCount and failedCount', function() {
      var message = this.reporter.printRunnerResults_(this.runner);
      expect(message).toEqual('1 test, 23 assertions, 52 failures, 0 skipped\n');
    });
  });

  describe('reports failures', function() {
    beforeEach(function() {
      this.printSpy = spyOn(this.reporter, 'print_');
      this.printLineSpy = spyOn(this.reporter, 'printLine_');
    });

    it('does not report anything when there are no failures', function() {
      this.reporter.failures_ = new Array();

      this.reporter.reportFailures_();

      expect(this.printLineSpy).not.toHaveBeenCalled();
    });

    it('prints the failures', function() {
      var failure = {
        spec: 'the spec',
        message: 'the message',
        stackTrace: 'the stackTrace'
      }

      this.reporter.failures_ = new Array();
      this.reporter.failures_.push(failure);

      this.reporter.reportFailures_();

      var generatedOutput =
                 [ [ '\n' ],
                 [ '\n' ],
                 [ '  1) the spec' ],
                 [ '   Message:' ],
                 [ '     the message' ],
                 [ '   Stacktrace:' ] ];

      expect(this.printLineSpy).toHaveBeenCalled();
      expect(this.printLineSpy.argsForCall).toEqual(generatedOutput);

      expect(this.printSpy).toHaveBeenCalled();
      expect(this.printSpy.argsForCall[0]).toEqual(['Failures:']);
      expect(this.printSpy.argsForCall[1]).toEqual(['     the stackTrace']);
    });

    it('prints the failures without a Stacktrace', function () {
        var config = { includeStackTrace: false };
        this.reporter = new jasmineNode.TerminalReporter(config);
        this.printSpy = spyOn(this.reporter, 'print_');
        this.printLineSpy = spyOn(this.reporter, 'printLine_');

        var failure = {
            spec: 'the spec',
            message: 'the message',
            stackTrace: 'the stackTrace'
        }

        this.reporter.failures_ = new Array();
        this.reporter.failures_.push(failure);

        this.reporter.reportFailures_();

        var generatedOutput =
                 [ [ '\n' ],
                 [ '\n' ],
                 [ '  1) the spec' ],
                 [ '   Message:' ],
                 [ '     the message' ] ];

        expect(this.printLineSpy).toHaveBeenCalled();
        expect(this.printLineSpy.argsForCall).toEqual(generatedOutput);

        expect(this.printSpy).toHaveBeenCalled();
        expect(this.printSpy.argsForCall[0]).toEqual(['Failures:']);
        expect(this.printSpy.argsForCall[1]).toBeUndefined();
    });
  });
});

describe('TerminalVerboseReporter', function() {
  beforeEach(function() {
    var config = {}
    this.verboseReporter = new jasmineNode.TerminalVerboseReporter(config);
    this.addFailureToFailuresSpy = spyOn(this.verboseReporter, 'addFailureToFailures_');
    this.spec = {
      id: 23,
      results: function() {
        return {
          failedCount: 1,
          getItems: function() {
            return ["this is the message"];
          }
        }
      }
    };
  });

  describe('#reportSpecResults', function() {
    it('adds the spec to the failures_', function() {
      this.verboseReporter.reportSpecResults(this.spec);

      expect(this.addFailureToFailuresSpy).toHaveBeenCalledWith(this.spec);
    });

    it('adds a new object to the specResults_', function() {
      this.verboseReporter.reportSpecResults(this.spec);

      expect(this.verboseReporter.specResults_[23].messages).toEqual(['this is the message']);
      expect(this.verboseReporter.specResults_[23].result).toEqual('failed');
    });
  });

  describe('#buildMessagesFromResults_', function() {
    beforeEach(function() {
      this.suite = {
        id: 17,
        type: 'suite',
        name: 'a describe block',
        suiteNestingLevel: 0,
        children: [],
        getFullName: function() { return "A spec"; },
      };

      this.spec = {
        id: 23,
        type: 'spec',
        name: 'a spec block',
        children: []
      };

      this.verboseReporter.specResults_['23'] = {
        result: 'passed',
        runtime: 200
      };

      this.verboseReporter.suiteResults_['17'] = {
        runtime: 500
      };

    });

    it('does not build anything when the results collection is empty', function() {
      var results = [],
          messages = [];

      this.verboseReporter.buildMessagesFromResults_(messages, results);

      expect(messages.length).toEqual(0);
    });

    it('adds a single suite to the messages', function() {
      var results = [],
          messages = [];

      results.push(this.suite);

      this.verboseReporter.buildMessagesFromResults_(messages, results);

      expect(messages.length).toEqual(2);
      expect(messages[0]).toEqual('');
      expect(messages[1]).toEqual('a describe block - 500 ms');
    });

    it('adds a single spec with success to the messages', function() {
      var results = [],
          messages = [];

      this.passSpy = spyOn(this.verboseReporter.color_, 'pass');

      results.push(this.spec);

      this.verboseReporter.buildMessagesFromResults_(messages, results);

      expect(this.passSpy).toHaveBeenCalled();
      expect(messages.length).toEqual(1);
      expect(messages[0]).toEqual('a spec block - 200 ms');
    });

    it('adds a single spec with failure to the messages', function() {
      var results = [],
          messages = [];

      this.verboseReporter.specResults_['23'].result = 'failed';

      this.passSpy = spyOn(this.verboseReporter.color_, 'pass');
      this.failSpy = spyOn(this.verboseReporter.color_, 'fail');

      results.push(this.spec);

      this.verboseReporter.buildMessagesFromResults_(messages, results);

      expect(this.failSpy).toHaveBeenCalled();
      expect(this.passSpy).not.toHaveBeenCalled();
    });

    it('adds a suite, a suite and a single spec with success to the messages', function() {
      var results = [],
          messages = [];

      var subSuite = new Object();
      subSuite.id = '29';
      subSuite.type = 'suite';
      subSuite.name = 'a sub describe block';
      subSuite.suiteNestingLevel = 1;
      subSuite.children = [];
      subSuite.children.push(this.spec);

      this.suite.children.push(subSuite);
      results.push(this.suite);

      this.verboseReporter.suiteResults_['29'] = {
        runtime: 350
      };

      this.verboseReporter.buildMessagesFromResults_(messages, results);

      expect(messages.length).toEqual(5);
      expect(messages[0]).toEqual('');
      expect(messages[1]).toEqual('a describe block - 500 ms');
      expect(messages[2]).toEqual('');
      expect(messages[3]).toEqual('    a sub describe block - 350 ms');
      expect(messages[4]).toEqual('        a spec block - 200 ms');
    });
  });
});
