docs: function KarmaReporter (tc, jasmineEnv) {
    var currentSuite = new SuiteNode();
  
    // Save link on native Date object
    // because user can mock it
    var _Date = Date;
  
    /**
     * @param suite
     * @returns {boolean} Return true if it is system jasmine top level suite
     */
    function isTopLevelSuite (suite) {
      return suite.description === 'Jasmine_TopLevel_Suite'
    }
  
    function handleGlobalErrors (result) {
      if (result.failedExpectations && result.failedExpectations.length) {
        var message = 'An error was thrown in afterAll';
        var steps = result.failedExpectations;
        for (var i = 0, l = steps.length; i < l; i++) {
          message += '\n' + formatFailedStep(steps[i]);
        }
  
        tc.error(message)
      }
    }
  
    /**
     * Jasmine 2.0 dispatches the following events:
     *
     *  - jasmineStarted
     *  - jasmineDone
     *  - suiteStarted
     *  - suiteDone
     *  - specStarted
     *  - specDone
     */
  
    this.jasmineStarted = function (data) {
      // TODO(vojta): Do not send spec names when polling.
      tc.info({
        total: data.totalSpecsDefined,
        specs: getAllSpecNames(jasmineEnv.topSuite())
      })
    };
  
    this.jasmineDone = function (result) {
      result = result || {};
  
      // Any errors in top-level afterAll blocks are given here.
      handleGlobalErrors(result);
  
      tc.complete({
        order: result.order,
        coverage: window.__coverage__
      })
    };
  
    this.suiteStarted = function (result) {
      if (!isTopLevelSuite(result)) {
        currentSuite = currentSuite.addChild(result.description)
      }
    };
  
    this.suiteDone = function (result) {
      // In the case of xdescribe, only "suiteDone" is fired.
      // We need to skip that.
      if (result.description !== currentSuite.name) {
        return
      }
  
      // Any errors in afterAll blocks are given here, except for top-level
      // afterAll blocks.
      handleGlobalErrors(result);
  
      currentSuite = currentSuite.parent
    };
  
    this.specStarted = function (specResult) {
      specResult.startTime = new _Date().getTime()
    };
  
    this.specDone = function (specResult) {
      var skipped = specResult.status === 'disabled' || specResult.status === 'pending';
  
      var result = {
        description: specResult.description,
        id: specResult.id,
        log: [],
        skipped: skipped,
        disabled: specResult.status === 'disabled',
        pending: specResult.status === 'pending',
        success: specResult.failedExpectations.length === 0,
        suite: [],
        time: skipped ? 0 : new _Date().getTime() - specResult.startTime,
        executedExpectationsCount: specResult.failedExpectations.length + specResult.passedExpectations.length
      };
  
      // generate ordered list of (nested) suite names
      var suitePointer = currentSuite;
      while (suitePointer.parent) {
        result.suite.unshift(suitePointer.name);
        suitePointer = suitePointer.parent;
      }
  
      if (!result.success) {
        var steps = specResult.failedExpectations;
        for (var i = 0, l = steps.length; i < l; i++) {
          result.log.push(formatFailedStep(steps[i]))
        }
      }
  
      tc.result(result);
      delete specResult.startTime
    }
  }