Home Reference Source Repository

src/component/test-component.js

'use strict';

const print = require('print');
const unitEvents = require('./test/events');
const emitEvents = require('./emit/events');
const UnitRunner = require('./test/unit-runner');
const ContainerTransformer = require('./helper/container-transformer');
const DependencyBasedComponent = require('./dependency-based-component');

/**
 * Test component
 */
class TestComponent extends DependencyBasedComponent {
  /**
   * @param {*} args
   */
  constructor(...args) {
    super(...args);

    /**
     * _units format
     * @type {{
     *  moduleName: {
     *    assets: [],
     *    runner: UnitRunner
     *  }
     * }}
     */
    this._units = {};
    this._stats = {
      total: 0,
      processed: 0,
      ignored: 0,
    };
  }
  
  /**
   * @returns {string}
   */
  get name() {
    return 'test';
  }
  
  /**
   * @returns {string[]}
   */
  get dependencies() {
    return [ 'emit' ];
  }

  /**
   * @param {Emitter} emitter
   * @returns {Promise}
   */
  run(emitter) {
    return new Promise((resolve, reject) => {
      const mochaOptions = this.container.get('mocha.options', {});

      emitter.onBlocking(emitEvents.module.emit.asset, payload => {
        if (!this._match(payload)) {
          return emitter.emitBlocking(unitEvents.asset.test.skip, payload);
        }

        return emitter.emitBlocking(unitEvents.asset.test.add, {}).then(() => {
          const { fileAbs, module } = payload;
          this.logger.info(this.logger.emoji.fist, `New test registered: ${ fileAbs }`);

          if (!this._units.hasOwnProperty(module.name)) {
            this._units[module.name] = {
              assets: [],
              runner: new UnitRunner(mochaOptions)
            };
          }

          this._units[module.name].assets.push(fileAbs);

          return Promise.resolve();
        });
      }, TestComponent.DEFAULT_PRIORITY);

      emitter.onBlocking(emitEvents.module.process.end, module => {
        if (!this._units.hasOwnProperty(module.name)) {
          return Promise.resolve();
        }

        const unitModule = this._units[module.name];
        const unitRunner = unitModule.runner;
        const mocha = unitRunner.getMocha();

        return emitter.emitBlocking(unitEvents.asset.tests.start, mocha, module).then(() => {
          if (unitModule.assets.length <= 0) {
            return Promise.resolve();
          }

          return unitRunner.run(unitModule.assets).then(failures => {
            if (failures > 0) {
              return Promise.reject(
                new Error(`Tests failed in module ${ module.name } with ${ failures } failures`)
              );
            }

            return unitRunner.cleanup();
          });
        }).then(() => emitter.emitBlocking(unitEvents.asset.tests.end, mocha, module));
      }, TestComponent.DEFAULT_PRIORITY);
      
      emitter.on(emitEvents.modules.process.end, () => {
        this.waitProcessing()
          .then(() => emitter.emitBlocking(unitEvents.assets.test.end, this))
          .then(() => {
            this.logger.info(this.logger.emoji.beer, `Finished processing ${ this.stats.processed } test assets`);
            this.logger.debug(this.dumpStats());

            resolve();
          })
          .catch(error => reject(error));
      });
    });
  }
  
  /**
   * @param {*} config
   * @param {string} configFile
   * @returns {Container}
   */
  prepareConfig(config, configFile) {
    return super.prepareConfig(config, configFile)
      .then(container => {
        return (new ContainerTransformer(container))
          .addPattern('pattern')
          .addPattern('ignore')
          .transform();
      });
  }
  
  /**
   * @param {*} payload
   *
   * @returns {boolean}
   * 
   * @private
   */
  _match(payload) {
    const pattern = this.container.get('pattern', []);
    const ignore = this.container.get('ignore', []);

    const result = pattern.filter(p => this._test(p, payload.file)).length > 0
      && ignore.filter(i => this._test(i, payload.file)).length <= 0;
    
    if (result) {
      this.stats.processed++;
    } else {
      this.stats.ignored++;
    }
    
    this.stats.total++;
      
    return result;
  }
  
  /**
   * @param {string|RegExp} pattern
   * @param {string} value
   *
   * @returns {boolean}
   *
   * @private
   */
  _test(pattern, value) {
    if (!(pattern instanceof RegExp)) {
      return value.indexOf(pattern.toString()) !== -1;
    }
    
    return pattern.test(value);
  }
  
  /**
   * @returns {*}
   */
  get stats() {
    return this._stats;
  }
  
  /**
   * @returns {string}
   */
  dumpStats() {
    return print(this.stats, {
      showArrayIndices: true,
      showArrayLength: true,
      sortProps: false,
    }).replace(/\t/g, '   ');
  }
  
  /**
   * @returns {number}
   */
  static get DEFAULT_PRIORITY() {
    return 10;
  }
}

module.exports = TestComponent;