Home Reference Source

lib/utils/config.js

const fs = require("fs");
const path = require("path");
const OptParser = require("./opt_parser");

/**
 * this class have configuratin information.
 */
class Config {
  static get CONFIG_FILE_PATH(){
    return "config.json";
  }
  static get DEFAULT_CONFIG(){
    return {
      browser: "electron",
      scenarioDir: "test",
      scenarioFile: undefined,
      ignoreDir: "supports",
      silentMode: false,
      exitImmediately: false,  // whether or not keep shows for browser window on failed
      takeScreenshotAuto: false,
      fileDownloadDir: "downloads",
      screenshotsDir: "screenshots",
      width: 1024,
      height: 768,
      mocha: {
        ui: "bdd",
        bail: true,
        reporter: "spec",
        timeout: 5000,
        slow: false,
      },
      assertion: {
        png: {
          threshold: 0.3,
          ksize: 50
        }
      },
      logger: {
        appenders: {
          out: { type: 'stdout' }
        },
        categories: {
          default: { appenders: ['out'], level: 'debug' },
        },
      },
      electron: {
        show: true,
        webPreferences: {
          nodeIntegration: false,
          preload: path.join(__dirname, "../browser/electron/window_preloaded.js"),
          allowRunningInsecureContent: true
        }
      },
      puppeteer: {
        product: 'chrome',
        headless: false
      },
    }
  }
  static create(commandLineArgs){
    this.instance = new Config();
    this.instance.merge(Config.DEFAULT_CONFIG)
    this.instance.loadConfigFile(this.CONFIG_FILE_PATH);
    this.instance.loadConfigFile(this.CONFIG_FILE_PATH + ".local");

    let optParser = new OptParser(commandLineArgs);
    this.instance.merge(optParser.parse((config, cmdOpt)=>{
      switch(cmdOpt.option()){
        case "-s":
        case "--silent":          config.silentMode     = true; break;
        default:
          let optionKey = cmdOpt.option(true).replace(/--/, '');
          if(this.instance.isExists(optionKey)){
            Config.assign(config, optionKey, cmdOpt.getValueOrFail())
          }else{
            const msg = "Unexpected CommandLine Argument, [" + cmdOpt.option() + "]"
            console.log(msg);
            throw new Error(msg);
          }
      }
    }));

    optParser.scriptArguments().forEach((args)=>{
      let filePath = args[0] == "/" ? args : path.join(process.cwd(), args);
      let stat = fs.statSync(filePath)
      let keyName = stat.isDirectory() ? "scenarioDir" : "scenarioFile";
      if(!(this.instance[keyName] instanceof Array)){
        this.instance[keyName] = []
      }
      this.instance[keyName].push(args);
    })

    // apply options
    if(this.instance.silentMode){
      this.instance.electron.show = false;
      this.instance.puppeteer.headless = true;
      this.instance.mocha.reporter = "dot";
      this.instance.exitImmediately = true;
    }
    this.instance.puppeteer.defaultViewport = this.instance.puppeteer.defaultViewport || {};
    this.instance.electron.width = this.instance.puppeteer.defaultViewport.width = this.instance.width;
    this.instance.electron.height = this.instance.puppeteer.defaultViewport.height = this.instance.height;

    // validates
    if(!["electron", "puppeteer"].includes(this.instance.browser)){
      throw new Error(`config.browser is not allowed! [${this.instance.browser}]`)
    }
    return this.instance;
  }
  static get config(){
    return this.instance;
  }
  /**
   * @private
   */
  loadConfigFile(filePath){
    if(fs.existsSync(filePath)){
      this.merge(JSON.parse(fs.readFileSync(filePath, 'utf8')));
    }
  }
  clone(){
    let cloned = new Config();
    cloned.merge(this);
    return cloned;
  }
  merge(source){
    Config.merge(this, source);
  }
  static merge(dest, source){
    Object.keys(source).forEach((key)=>{
      if(typeof source[key] == "object" && typeof dest[key] == "object"){
        this.merge(dest[key], source[key]);
      }else{
        dest[key] = source[key];
      }
    });
  }
  isExists(key){
    let parentObj = this;
    return !!key.split(".").every((k)=>{
      let isExists = parentObj && (k in parentObj);
      parentObj = isExists && parentObj[k];
      return isExists;
    })
  }
  static isExists(object, key){
    if(typeof object != "object") return false;
    if(key in object){
      return typeof object[key] == "object" ? object[key] : true;
    }
  }
  static assign(config, key, value){
    let keys = key.split(".");
    keys.reduce((object, key, index)=>{
      if(index < keys.length - 1){
        if(!object[key]){
          object[key] = {}
        }
        return object[key];
      }
      switch(typeof object[key]){
        case 'number':  object[key] = new Number(value);
        case 'boolean': object[key] = new Boolean(value);
        default:        object[key] = value;
      }
    }, config);
  }
}

module.exports = Config;