API Docs for: 0.0.4
Show:

File: index.js

/*!
 *
 * Copyright (c) 2013 Sebastian Golasch
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

'use strict';

// ext. libs
var Handlebars = require('handlebars');
var stylus = require('stylus');
var fs = require('fs');

// int. globals
var reporter = null;

/**
 * The HTML reporter can produce a set of HTML files with the results of your testrun.
 *
 * The reporter can be installed with the following command:
 *
 * ```bash
 * $ npm install dalek-reporter-html --save-dev
 * ```
 *
 * By default the files will be written to the `report/dalek/` folder,
 * you can change this by adding a config option to the your Dalekfile
 *
 * ```javascript
 * "html-reporter": {
 *   "dest": "your/folder"
 * }
 * ```
 *
 * If you would like to use the reporter (in addition to the std. console reporter),
 * you can start dalek with a special command line argument
 *
 * ```bash
 * $ dalek your_test.js -r console,html
 * ```
 *
 * or you can add it to your Dalekfile
 *
 * ```javascript
 * "reporter": ["console", "html"]
 * ```
 *
 * @class Reporter
 * @constructor
 * @part html
 * @api
 */

function Reporter (opts) {
  this.events = opts.events;
  this.config = opts.config;
  this.temporaryAssertions = [];
  this.temp = {};

  var defaultReportFolder = 'report/dalek';
  this.dest = this.config.get('html-reporter') && this.config.get('html-reporter').dest ? this.config.get('html-reporter').dest : defaultReportFolder;

  this.loadTemplates();
  this.initOutputHandlers();
  this.startListening();
}

/**
 * @module Reporter
 */

module.exports = function (opts) {
  if (reporter === null) {
    reporter = new Reporter(opts);
  }

  return reporter;
};

Reporter.prototype = {

  /**
   * Inits the html buffer objects
   *
   * @method initOutputHandlers
   * @chainable
   */

  initOutputHandlers: function () {
    this.output = {};
    this.output.test = {};
    return this;
  },

  /**
   * Loads and prepares all the templates for
   * CSS, JS & HTML
   *
   * @method loadTemplates
   * @chainable
   */

  loadTemplates: function () {
    // render stylesheets
    var precss = fs.readFileSync(__dirname + '/themes/default/styl/default.styl', 'utf8');
    stylus.render(precss, { filename: 'default.css' }, function(err, css){
      if (err) {
        throw err;
      }

      this.styles = css;
    }.bind(this));

    // collect client js (to be inined later)
    this.js = fs.readFileSync(__dirname + '/themes/default/js/default.js', 'utf8');

    // register handlebars helpers
    Handlebars.registerHelper('roundNumber', function (number) {
      return Math.round(number * Math.pow(10, 2)) / Math.pow(10, 2);
    });

    // collect & compile templates
    this.templates = {};
    this.templates.test = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/test.hbs', 'utf8'));
    this.templates.wrapper = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/wrapper.hbs', 'utf8'));
    this.templates.testresult = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/tests.hbs', 'utf8'));
    this.templates.banner = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/banner.hbs', 'utf8'));
    this.templates.detail = Handlebars.compile(fs.readFileSync(__dirname + '/themes/default/hbs/detail.hbs', 'utf8'));

    return this;
  },

  /**
   * Connects to all the event listeners
   *
   * @method startListening
   * @chainable
   */

  startListening: function () {
    // index page
    this.events.on('report:assertion', this.outputAssertionResult.bind(this));
    this.events.on('report:test:finished', this.outputTestFinished.bind(this));
    this.events.on('report:runner:finished', this.outputRunnerFinished.bind(this));
    this.events.on('report:run:browser', this.outputRunBrowser.bind(this));

    // detail page
    this.events.on('report:test:started', this.startDetailPage.bind(this));
    this.events.on('report:action', this.addActionToDetailPage.bind(this));
    this.events.on('report:assertion', this.addAssertionToDetailPage.bind(this));
    this.events.on('report:test:finished', this.finishDetailPage.bind(this));

    return this;
  },

  /**
   * Prepares the output for a test detail page
   *
   * @method startDetailPage
   * @chainable
   */

  startDetailPage: function () {
    this.detailContents = {};
    this.detailContents.eventLog = [];
    return this;
  },

  /**
   * Adds an action output to the detail page
   *
   * @method addActionToDetailPage
   * @param {object} data Event data
   * @chainable
   */

  addActionToDetailPage: function (data) {
    data.isAction = true;
    this.detailContents.eventLog.push(data);
    return this;
  },

  /**
   * Adds an assertion result to the detail page
   *
   * @method addAssertionToDetailPage
   * @param {object} data Event data
   * @chainable
   */

  addAssertionToDetailPage: function (data) {
    data.isAssertion = true;
    this.detailContents.eventLog.push(data);
    return this;
  },

  /**
   * Writes a detail page to the file system
   *
   * @method finishDetailPage
   * @param {object} data Event data
   * @chainable
   */

  finishDetailPage: function (data) {
    this.detailContents.testResult = data;
    this.detailContents.styles = this.styles;
    this.detailContents.js = this.js;
    fs.writeFileSync(this.dest + '/details/' + data.id + '.html', this.templates.detail(this.detailContents), 'utf8');
    return this;
  },

  /**
   * Stores the current browser name
   *
   * @method outputRunBrowser
   * @param {string} browser Browser name
   * @chainable
   */

  outputRunBrowser: function (browser) {
    this.temp.browser = browser;
    return this;
  },

  /**
   * Writes the index page to the filesystem
   *
   * @method outputRunnerFinished
   * @param {object} data Event data
   * @chainable
   */

  outputRunnerFinished: function (data) {
    var body = '';
    var contents = '';
    var tests = '';
    var banner = '';

    // add test results
    var keys = Object.keys(this.output.test);
    keys.forEach(function (key) {
      tests += this.output.test[key];
    }.bind(this));

    // compile the test result template
    body = this.templates.testresult({result: data, tests: tests});

    // compile the banner
    banner = this.templates.banner({status: data.status});

    // compile the contents within the wrapper template
    contents = this.templates.wrapper({styles: this.styles, js: this.js, banner: banner, body: body});

    // save the main test output file
    this.events.emit('report:written', {type: 'html', dest: this.dest});
    this._recursiveMakeDirSync(this.dest + '/details');
    fs.writeFileSync(this.dest + '/index.html', contents, 'utf8');
    return this;
  },

  /**
   * Pushes an assertion result to the index output queue
   *
   * @method outputAssertionResult
   * @param {object} data Event data
   * @chainable
   */

  outputAssertionResult: function (data) {
    this.temporaryAssertions.push(data);
    return this;
  },

  /**
   * Pushes an test result to the index output queue
   *
   * @method outputTestFinished
   * @param {object} data Event data
   * @chainable
   */

  outputTestFinished: function (data) {
    data.assertionInfo = this.temporaryAssertions;
    data.browser = this.temp.browser;
    this.output.test[data.id] = this.templates.test(data);
    this.temporaryAssertions = [];
    return this;
  },

  /**
   * Helper method to generate deeper nested directory structures
   *
   * @method _recursiveMakeDirSync
   * @param {string} path PAth to create
   */

  _recursiveMakeDirSync: function (path) {
    var pathSep = require('path').sep;
    var dirs = path.split(pathSep);
    var root = '';

    while (dirs.length > 0) {
      var dir = dirs.shift();
      if (dir === '') {
        root = pathSep;
      }
      if (!fs.existsSync(root + dir)) {
        fs.mkdirSync(root + dir);
      }
      root += dir + pathSep;
    }
  }
};