<commit_before>'use strict';

const log = require('intel').getLogger('browsertime.command.measure');
const get = require('lodash.get');
const engineUtils = require('../../../support/engineUtils');
const Video = require('../../../video/video');
const merge = require('lodash.merge');
const pathToFolder = require('../../../support/pathToFolder');
const path = require('path');
const setOrangeBackground = require('../../../video/screenRecording/setOrangeBackground');
const { filterWhitelisted } = require('../../../support/userTiming');
const { isAndroidConfigured, Android } = require('../../../android');
const TCPDump = require('../../../support/tcpdump');
const delay = ms => new Promise(res => setTimeout(res, ms));

// Make this configurable
const ANDROID_DELAY_TIME = 2000;

// In some cases we could have one alias mapped to multiple URLs
// we've seen it for login etc where the user get different query parameter
// values. So map alias = url 1:1
const aliasAndUrl = {};

function getNewResult() {
  return {
    extraJson: {},
    timestamp: engineUtils.timestamp(),
    extras: {}
  };
}

class Measure {
  constructor(
    browser,
    index,
    pageCompleteCheck,
    result,
    engineDelegate,
    extensionServer,
    storageManager,
    videos,
    scriptsByCategory,
    asyncScriptsByCategory,
    postURLScripts,
    context,
    screenshotManager,
    options
  ) {
    this.browser = browser;
    this.pageCompleteCheck = pageCompleteCheck;
    this.index = index;
    this.result = result;
    this.engineDelegate = engineDelegate;
    this.options = options;
    this.screenshotManager = screenshotManager;
    this.storageManager = storageManager;
    this.recordVideo = options.visualMetrics || options.video;
    this.extensionServer = extensionServer;
    this.videos = videos;
    this.scriptsByCategory = scriptsByCategory;
    this.asyncScriptsByCategory = asyncScriptsByCategory;
    this.postURLScripts = postURLScripts;
    this.context = context;
    this.numberOfMeasuredPages = 0;
    this.numberOfVisitedPages = 0;
    this.areWeMeasuring = false;
    this.testedURLs = {};
    this.tcpDump = new TCPDump(storageManager.directory, options);
  }

  // Have a consistent way of starting the video
  async _startVideo(numberOfMeasuredPages, index) {
    this.video = new Video(this.storageManager, this.options, this.browser);

    if (this.options.firefox && this.options.firefox.windowRecorder) {
      // The Firefox window recorder will only record subsequent
      // changes after recording has begun.  So the orange frame needs
      // to come after we start recording.
      await this.video.record(numberOfMeasuredPages, index);
      await setOrangeBackground(this.browser.getDriver(), this.options);
    } else {
      await setOrangeBackground(this.browser.getDriver(), this.options);
      await this.video.record(numberOfMeasuredPages, index);
    }

    // Give ffmpeg/video on phone time to settle
    if (isAndroidConfigured(this.options)) {
      return delay(ANDROID_DELAY_TIME);
    } else {
      return delay(400);
    }
  }

  _error(message) {
    // If we already are measuring a page, associate the error woth that page
    if (this.areWeMeasuring === true) {
      if (!this.result[this.numberOfMeasuredPages].error) {
        this.result[this.numberOfMeasuredPages].error = [message];
      } else {
        this.result[this.numberOfMeasuredPages].error.push(message);
      }
    } else {
      // If we have tested pages before, but not in the making of
      // measuring a page, associate the error with that latest page.
      if (this.result.length > 0) {
        if (this.result[this.result.length - 1].error === undefined) {
          this.result[this.result.length - 1].error = [message];
        } else {
          this.result[this.result.length - 1].error.push(message);
        }
      } else {
        //  error not associated to a start/run so far
        log.error('No page that could be associated with the error:' + message);
      }
    }
  }
  async _navigate(url) {
    log.info('Navigating to url %s iteration %s', url, this.index);
    if (this.numberOfVisitedPages === 0) {
      await this.extensionServer.setup(url, this.browser, this.options);
    }
    if (this.numberOfVisitedPages === 0) {
      await this.engineDelegate.beforeStartIteration(this.browser, this.index);
    }
    this.numberOfVisitedPages++;
    return this.browser.loadAndWait(url, this.pageCompleteCheck);
  }

  /**
   *  Start collecting metrics for a URL. If you supply a URL to this method, the browser will navigate to that URL.
   *  If you do not use an URL (start()) everything is prepared for a new page to measure except the browser do not
   *  navigate to a new URL. You can also add an alias for the URL.
   * @param {string} urlOrAlias
   * @param {string} optionalAlias
   * @returns {Promise} Promise object represents when the URL has been navigated and finished loading according to the pageCompleteCheck or when everything is setup for measuring a new URL (if no URL is supplied).
   */
  async start(urlOrAlias, optionalAlias) {
    let url, alias;
    if (
      urlOrAlias &&
      (urlOrAlias.startsWith('http') || urlOrAlias.startsWith('data:text'))
    ) {
      url = urlOrAlias;
      log.info('Testing url %s iteration %s', url, this.index);
    } else if (urlOrAlias) {
      alias = urlOrAlias;
      log.info('Start to measure %s', alias);
    } else {
      log.info('Start to measure');
    }

    // On the first page of an iteration, do what you need to do!
    if (this.numberOfVisitedPages === 0 && url) {
      // We can only setup the extension if we have a URL at the moment
      await this.extensionServer.setup(url, this.browser, this.options);
    }
    if (this.numberOfVisitedPages === 0) {
      await this.engineDelegate.beforeStartIteration(this.browser, this.index);
    }

    this.result.push(getNewResult());
    this.result[this.numberOfMeasuredPages].timestamp = engineUtils.timestamp();

    if (alias || optionalAlias) {
      this.result[this.numberOfMeasuredPages].alias = alias || optionalAlias;
    }

    if (this.options.spa) {
      // Make sure that the resource timing is empty
      await this.browser
        .getDriver()
        .executeScript('window.performance.clearResourceTimings();');
    }

    if (this.recordVideo && !this.options.videoParams.debug) {
      await this._startVideo(this.numberOfMeasuredPages, this.index);
      // if we do not have the URL, we should remove the orange and
      // start meausuring because the user themself will navigate to
      // the URL that will be tested.
      if (!url) {
        const removeOrange = `(function() {
          const orange = document.getElementById('browsertime-orange');
          if (orange) {
            orange.parentNode.removeChild(orange);
          }
        })();`;
        await this.browser.getDriver().executeScript(removeOrange);
      }
    }

    // await this.engineDelegate.clear(this.browser);

    if (this.options.tcpdump) {
      await this.tcpDump.start(this.index);
    }
    if (url) {
      // We jack in the URL that we wanna test and then override it from the browser
      this.result[this.numberOfMeasuredPages].url = url;
      try {
        await this.engineDelegate.beforeEachURL(this.browser, url);
        await this.browser.loadAndWait(url, this.pageCompleteCheck);
        // We want to stop the video direct after the page completed
        // to make Visual Metrics faster and independent of what happens later
        if (this.recordVideo && !this.options.videoParams.debug) {
          await this.video.stop(url);
          this.videos.push(this.video);
        }
        const res = await this.engineDelegate.afterPageCompleteCheck(
          this.browser,
          this.index,
          url
        );
        this.result[this.numberOfMeasuredPages] = merge(
          this.result[this.numberOfMeasuredPages],
          res
        );
      } catch (e) {
        this.result[this.numberOfMeasuredPages].error = [e.name];
        // The page failed loading but make sure we keep track of
        // how many pages we tried to measure
        this.numberOfMeasuredPages++;
        this.numberOfVisitedPages++;
        this.engineDelegate.failing(url);
        throw e;
      }
      return this.collect(url);
    } else {
      this.areWeMeasuring = true;
      return this.engineDelegate.beforeEachURL(this.browser);
    }
  }

  /**
   * Stop measuring and collect all the metrics.
   * @returns {Promise} Promise object represents all the metrics has been collected.
   */
  async stop() {
    log.debug('Stop measuring');
    const res = await this.engineDelegate.afterPageCompleteCheck(
      this.browser,
      this.index,
      await this.browser.runScript('return document.URL', 'PAGE_URL')
    );
    this.result[this.numberOfMeasuredPages] = merge(
      this.result[this.numberOfMeasuredPages],
      res
    );
    return this.collect();
  }

  /**
   * Add your own metric.
   * @param {string} name
   * @param {*} value
   */
  add(name, value) {
    if (this.result[this.numberOfMeasuredPages - 1]) {
      this.result[this.numberOfMeasuredPages - 1].extras[name] = value;
    } else {
      log.error(
        'You need to have done one (start/stop) measurement before you can add any metrics to a result.'
      );
    }
  }

  /**
   * Add your own metrics. You can add an object witch multiple keys and they will all be collected.
   * @param {*} object
   */
  addObject(object) {
    if (this.result[this.numberOfMeasuredPages - 1]) {
      merge(this.result[this.numberOfMeasuredPages - 1].extras, object);
    } else
      log.error(
        'You need to have done one (start/stop) measurement before you can add any metrics to a result.'
      );
  }

  async collect(testedStartUrl) {
    if (this.options.tcpdump) {
      await this.tcpDump.stop();
    }
    // This stops collecting trace logs in Chrome etc
    // await this.engineDelegate.beforeCollect();

    log.verbose('Collecting metrics');
    // Collect all the metrics through JavaScript
    // If we don't have a URL (tested using clicking on link etc) get the URL from the browser
    let url =
      testedStartUrl ||
      (await this.browser.runScript('return document.URL', 'PAGE_URL'));

    // We have the URL, create the data dir
    await this.storageManager.createSubDataDir(
      path.join(pathToFolder(url, this.options))
    );

    // If we have an alias for the URL, use that URL
    if (
      this.result[this.numberOfMeasuredPages] &&
      this.result[this.numberOfMeasuredPages].alias
    ) {
      const alias = this.result[this.numberOfMeasuredPages].alias;
      if (!aliasAndUrl[alias]) {
        aliasAndUrl[alias] = url;
      } else {
        url = aliasAndUrl[alias];
      }
    }

    if (this.testedURLs[url]) {
      log.info(
        '%s has been tested before within the same run, it will get an extra query parameter named browsertime_run. Make sure to use alias to keep track of the URLs',
        url
      );
      this.testedURLs[url] = this.testedURLs[url] + 1;
      if (url.indexOf('?') > -1) {
        url = url + '&browsertime_run=' + this.testedURLs[url];
      } else {
        url = url + '?browsertime_run=' + this.testedURLs[url];
      }
    } else {
      this.testedURLs[url] = 1;
    }
    this.result[this.numberOfMeasuredPages].url = url;

    // when we have the URL we can use that to stop the video and put it where we want it
    if (
      this.recordVideo &&
      !this.options.videoParams.debug &&
      this.video.getRecordingStartTime()
    ) {
      this.result[this.numberOfMeasuredPages].recordingStartTime = parseFloat(
        this.video.getRecordingStartTime()
      );
      this.result[this.numberOfMeasuredPages].timeToFirstFrame = parseInt(
        this.video.getTimeToFirstFrame(),
        10
      );
    }

    if (this.options.screenshot) {
      try {
        const screenshot = await this.browser.takeScreenshot(url);
        await this.screenshotManager.save(
          'afterPageCompleteCheck',
          screenshot,
          url,
          this.index
        );
      } catch (e) {
        // not getting screenshots shouldn't result in a failed test.
      }
    }

    const syncScripts = this.scriptsByCategory
        ? await this.browser.runScripts(this.scriptsByCategory)
        : {},
      asyncScripts = this.asyncScriptsByCategory
        ? await this.browser.runScripts(this.asyncScriptsByCategory, true)
        : {};

    this.result[this.numberOfMeasuredPages].browserScripts = merge(
      {},
      syncScripts,
      asyncScripts
    );

    // Some sites has crazy amount of user timings:
    // strip them if you want
    if (this.options.userTimingWhitelist) {
      filterWhitelisted(
        this.result[this.numberOfMeasuredPages].browserScripts.timings
          .userTimings,
        this.options.userTimingWhitelist
      );
    }

    if (isAndroidConfigured(this.options) && this.options.processStartTime) {
      const packageName =
        this.options.browser === 'firefox'
          ? get(this.options, 'firefox.android.package', 'org.mozilla.firefox')
          : get(this.options, 'chrome.android.package');

      const android = new Android(this.options);
      const pid = await android.pidof(packageName);

      if (pid) {
        const processStartTime = await android.processStartTime(pid);
        this.result[
          this.numberOfMeasuredPages
        ].browserScripts.browser.processStartTime = processStartTime;
      }
    }

    for (const postURLScript of this.postURLScripts) {
      await postURLScript(this.context);
    }

    if (this.options.tcpdump) {
      this.tcpDump.mv(url, this.index);
    }

    await this.engineDelegate.afterEachURL(
      this.browser,
      this.index,
      this.result[this.numberOfMeasuredPages]
    );
    this.numberOfMeasuredPages++;
    this.numberOfVisitedPages++;
    this.areWeMeasuring = false;
  }
}

module.exports = Measure;
<commit_msg>Stop the video when measuring (#1371)<commit_after>'use strict';

const log = require('intel').getLogger('browsertime.command.measure');
const get = require('lodash.get');
const engineUtils = require('../../../support/engineUtils');
const Video = require('../../../video/video');
const merge = require('lodash.merge');
const pathToFolder = require('../../../support/pathToFolder');
const path = require('path');
const setOrangeBackground = require('../../../video/screenRecording/setOrangeBackground');
const { filterWhitelisted } = require('../../../support/userTiming');
const { isAndroidConfigured, Android } = require('../../../android');
const TCPDump = require('../../../support/tcpdump');
const delay = ms => new Promise(res => setTimeout(res, ms));

// Make this configurable
const ANDROID_DELAY_TIME = 2000;

// In some cases we could have one alias mapped to multiple URLs
// we've seen it for login etc where the user get different query parameter
// values. So map alias = url 1:1
const aliasAndUrl = {};

function getNewResult() {
  return {
    extraJson: {},
    timestamp: engineUtils.timestamp(),
    extras: {}
  };
}

class Measure {
  constructor(
    browser,
    index,
    pageCompleteCheck,
    result,
    engineDelegate,
    extensionServer,
    storageManager,
    videos,
    scriptsByCategory,
    asyncScriptsByCategory,
    postURLScripts,
    context,
    screenshotManager,
    options
  ) {
    this.browser = browser;
    this.pageCompleteCheck = pageCompleteCheck;
    this.index = index;
    this.result = result;
    this.engineDelegate = engineDelegate;
    this.options = options;
    this.screenshotManager = screenshotManager;
    this.storageManager = storageManager;
    this.recordVideo = options.visualMetrics || options.video;
    this.extensionServer = extensionServer;
    this.videos = videos;
    this.scriptsByCategory = scriptsByCategory;
    this.asyncScriptsByCategory = asyncScriptsByCategory;
    this.postURLScripts = postURLScripts;
    this.context = context;
    this.numberOfMeasuredPages = 0;
    this.numberOfVisitedPages = 0;
    this.areWeMeasuring = false;
    this.testedURLs = {};
    this.tcpDump = new TCPDump(storageManager.directory, options);
  }

  // Have a consistent way of starting the video
  async _startVideo(numberOfMeasuredPages, index) {
    this.video = new Video(this.storageManager, this.options, this.browser);

    if (this.options.firefox && this.options.firefox.windowRecorder) {
      // The Firefox window recorder will only record subsequent
      // changes after recording has begun.  So the orange frame needs
      // to come after we start recording.
      await this.video.record(numberOfMeasuredPages, index);
      await setOrangeBackground(this.browser.getDriver(), this.options);
    } else {
      await setOrangeBackground(this.browser.getDriver(), this.options);
      await this.video.record(numberOfMeasuredPages, index);
    }

    // Give ffmpeg/video on phone time to settle
    if (isAndroidConfigured(this.options)) {
      return delay(ANDROID_DELAY_TIME);
    } else {
      return delay(400);
    }
  }

  _error(message) {
    // If we already are measuring a page, associate the error woth that page
    if (this.areWeMeasuring === true) {
      if (!this.result[this.numberOfMeasuredPages].error) {
        this.result[this.numberOfMeasuredPages].error = [message];
      } else {
        this.result[this.numberOfMeasuredPages].error.push(message);
      }
    } else {
      // If we have tested pages before, but not in the making of
      // measuring a page, associate the error with that latest page.
      if (this.result.length > 0) {
        if (this.result[this.result.length - 1].error === undefined) {
          this.result[this.result.length - 1].error = [message];
        } else {
          this.result[this.result.length - 1].error.push(message);
        }
      } else {
        //  error not associated to a start/run so far
        log.error('No page that could be associated with the error:' + message);
      }
    }
  }
  async _navigate(url) {
    log.info('Navigating to url %s iteration %s', url, this.index);
    if (this.numberOfVisitedPages === 0) {
      await this.extensionServer.setup(url, this.browser, this.options);
    }
    if (this.numberOfVisitedPages === 0) {
      await this.engineDelegate.beforeStartIteration(this.browser, this.index);
    }
    this.numberOfVisitedPages++;
    return this.browser.loadAndWait(url, this.pageCompleteCheck);
  }

  /**
   *  Start collecting metrics for a URL. If you supply a URL to this method, the browser will navigate to that URL.
   *  If you do not use an URL (start()) everything is prepared for a new page to measure except the browser do not
   *  navigate to a new URL. You can also add an alias for the URL.
   * @param {string} urlOrAlias
   * @param {string} optionalAlias
   * @returns {Promise} Promise object represents when the URL has been navigated and finished loading according to the pageCompleteCheck or when everything is setup for measuring a new URL (if no URL is supplied).
   */
  async start(urlOrAlias, optionalAlias) {
    let url, alias;
    if (
      urlOrAlias &&
      (urlOrAlias.startsWith('http') || urlOrAlias.startsWith('data:text'))
    ) {
      url = urlOrAlias;
      log.info('Testing url %s iteration %s', url, this.index);
    } else if (urlOrAlias) {
      alias = urlOrAlias;
      log.info('Start to measure %s', alias);
    } else {
      log.info('Start to measure');
    }

    // On the first page of an iteration, do what you need to do!
    if (this.numberOfVisitedPages === 0 && url) {
      // We can only setup the extension if we have a URL at the moment
      await this.extensionServer.setup(url, this.browser, this.options);
    }
    if (this.numberOfVisitedPages === 0) {
      await this.engineDelegate.beforeStartIteration(this.browser, this.index);
    }

    this.result.push(getNewResult());
    this.result[this.numberOfMeasuredPages].timestamp = engineUtils.timestamp();

    if (alias || optionalAlias) {
      this.result[this.numberOfMeasuredPages].alias = alias || optionalAlias;
    }

    if (this.options.spa) {
      // Make sure that the resource timing is empty
      await this.browser
        .getDriver()
        .executeScript('window.performance.clearResourceTimings();');
    }

    if (this.recordVideo && !this.options.videoParams.debug) {
      await this._startVideo(this.numberOfMeasuredPages, this.index);
      // if we do not have the URL, we should remove the orange and
      // start meausuring because the user themself will navigate to
      // the URL that will be tested.
      if (!url) {
        const removeOrange = `(function() {
          const orange = document.getElementById('browsertime-orange');
          if (orange) {
            orange.parentNode.removeChild(orange);
          }
        })();`;
        await this.browser.getDriver().executeScript(removeOrange);
      }
    }

    // await this.engineDelegate.clear(this.browser);

    if (this.options.tcpdump) {
      await this.tcpDump.start(this.index);
    }
    if (url) {
      // We jack in the URL that we wanna test and then override it from the browser
      this.result[this.numberOfMeasuredPages].url = url;
      try {
        await this.engineDelegate.beforeEachURL(this.browser, url);
        await this.browser.loadAndWait(url, this.pageCompleteCheck);
        // We want to stop the video direct after the page completed
        // to make Visual Metrics faster and independent of what happens later
        if (this.recordVideo && !this.options.videoParams.debug) {
          await this.video.stop(url);
          this.videos.push(this.video);
        }
        const res = await this.engineDelegate.afterPageCompleteCheck(
          this.browser,
          this.index,
          url
        );
        this.result[this.numberOfMeasuredPages] = merge(
          this.result[this.numberOfMeasuredPages],
          res
        );
      } catch (e) {
        this.result[this.numberOfMeasuredPages].error = [e.name];
        // The page failed loading but make sure we keep track of
        // how many pages we tried to measure
        this.numberOfMeasuredPages++;
        this.numberOfVisitedPages++;
        this.engineDelegate.failing(url);
        throw e;
      }
      return this.collect(url);
    } else {
      this.areWeMeasuring = true;
      return this.engineDelegate.beforeEachURL(this.browser);
    }
  }

  /**
   * Stop measuring and collect all the metrics.
   * @returns {Promise} Promise object represents all the metrics has been collected.
   */
  async stop() {
    log.debug('Stop measuring');
    const url = await this.browser.runScript('return document.URL', 'PAGE_URL');
    if (this.recordVideo && !this.options.videoParams.debug) {
      await this.video.stop(url);
      this.videos.push(this.video);
    }
    const res = await this.engineDelegate.afterPageCompleteCheck(
      this.browser,
      this.index,
      url
    );
    this.result[this.numberOfMeasuredPages] = merge(
      this.result[this.numberOfMeasuredPages],
      res
    );
    return this.collect();
  }

  /**
   * Add your own metric.
   * @param {string} name
   * @param {*} value
   */
  add(name, value) {
    if (this.result[this.numberOfMeasuredPages - 1]) {
      this.result[this.numberOfMeasuredPages - 1].extras[name] = value;
    } else {
      log.error(
        'You need to have done one (start/stop) measurement before you can add any metrics to a result.'
      );
    }
  }

  /**
   * Add your own metrics. You can add an object witch multiple keys and they will all be collected.
   * @param {*} object
   */
  addObject(object) {
    if (this.result[this.numberOfMeasuredPages - 1]) {
      merge(this.result[this.numberOfMeasuredPages - 1].extras, object);
    } else
      log.error(
        'You need to have done one (start/stop) measurement before you can add any metrics to a result.'
      );
  }

  async collect(testedStartUrl) {
    if (this.options.tcpdump) {
      await this.tcpDump.stop();
    }
    // This stops collecting trace logs in Chrome etc
    // await this.engineDelegate.beforeCollect();

    log.verbose('Collecting metrics');
    // Collect all the metrics through JavaScript
    // If we don't have a URL (tested using clicking on link etc) get the URL from the browser
    let url =
      testedStartUrl ||
      (await this.browser.runScript('return document.URL', 'PAGE_URL'));

    // We have the URL, create the data dir
    await this.storageManager.createSubDataDir(
      path.join(pathToFolder(url, this.options))
    );

    // If we have an alias for the URL, use that URL
    if (
      this.result[this.numberOfMeasuredPages] &&
      this.result[this.numberOfMeasuredPages].alias
    ) {
      const alias = this.result[this.numberOfMeasuredPages].alias;
      if (!aliasAndUrl[alias]) {
        aliasAndUrl[alias] = url;
      } else {
        url = aliasAndUrl[alias];
      }
    }

    if (this.testedURLs[url]) {
      log.info(
        '%s has been tested before within the same run, it will get an extra query parameter named browsertime_run. Make sure to use alias to keep track of the URLs',
        url
      );
      this.testedURLs[url] = this.testedURLs[url] + 1;
      if (url.indexOf('?') > -1) {
        url = url + '&browsertime_run=' + this.testedURLs[url];
      } else {
        url = url + '?browsertime_run=' + this.testedURLs[url];
      }
    } else {
      this.testedURLs[url] = 1;
    }
    this.result[this.numberOfMeasuredPages].url = url;

    // when we have the URL we can use that to stop the video and put it where we want it
    if (
      this.recordVideo &&
      !this.options.videoParams.debug &&
      this.video.getRecordingStartTime()
    ) {
      this.result[this.numberOfMeasuredPages].recordingStartTime = parseFloat(
        this.video.getRecordingStartTime()
      );
      this.result[this.numberOfMeasuredPages].timeToFirstFrame = parseInt(
        this.video.getTimeToFirstFrame(),
        10
      );
    }

    if (this.options.screenshot) {
      try {
        const screenshot = await this.browser.takeScreenshot(url);
        await this.screenshotManager.save(
          'afterPageCompleteCheck',
          screenshot,
          url,
          this.index
        );
      } catch (e) {
        // not getting screenshots shouldn't result in a failed test.
      }
    }

    const syncScripts = this.scriptsByCategory
        ? await this.browser.runScripts(this.scriptsByCategory)
        : {},
      asyncScripts = this.asyncScriptsByCategory
        ? await this.browser.runScripts(this.asyncScriptsByCategory, true)
        : {};

    this.result[this.numberOfMeasuredPages].browserScripts = merge(
      {},
      syncScripts,
      asyncScripts
    );

    // Some sites has crazy amount of user timings:
    // strip them if you want
    if (this.options.userTimingWhitelist) {
      filterWhitelisted(
        this.result[this.numberOfMeasuredPages].browserScripts.timings
          .userTimings,
        this.options.userTimingWhitelist
      );
    }

    if (isAndroidConfigured(this.options) && this.options.processStartTime) {
      const packageName =
        this.options.browser === 'firefox'
          ? get(this.options, 'firefox.android.package', 'org.mozilla.firefox')
          : get(this.options, 'chrome.android.package');

      const android = new Android(this.options);
      const pid = await android.pidof(packageName);

      if (pid) {
        const processStartTime = await android.processStartTime(pid);
        this.result[
          this.numberOfMeasuredPages
        ].browserScripts.browser.processStartTime = processStartTime;
      }
    }

    for (const postURLScript of this.postURLScripts) {
      await postURLScript(this.context);
    }

    if (this.options.tcpdump) {
      this.tcpDump.mv(url, this.index);
    }

    await this.engineDelegate.afterEachURL(
      this.browser,
      this.index,
      this.result[this.numberOfMeasuredPages]
    );
    this.numberOfMeasuredPages++;
    this.numberOfVisitedPages++;
    this.areWeMeasuring = false;
  }
}

module.exports = Measure;
<|endoftext|>


<commit_before>'use strict';

const config = require('config');
const ElasticSearch = require('elasticsearch');
const Search = require('./search');

// Controller dependencies

const container = {};

container.elasticClient = new ElasticSearch.Client(Object.assign({}, config.get('elasticsearch')));
container.search = new Search(container.elasticClient);

module.exports = container;
<commit_msg>Omit index.<commit_after>'use strict';

const config = require('config');
const ElasticSearch = require('elasticsearch');
const omit = require('lodash.omit');
const Search = require('./search');

// Controller dependencies

const container = {};

container.elasticClient = new ElasticSearch.Client(Object.assign({}, omit(config.get('elasticsearch'), 'index')));
container.search = new Search(container.elasticClient);

module.exports = container;
<|endoftext|>


<commit_before>/**
 *  Copyright 2012 Tomaz Muraus
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var fs = require('fs');
var util = require('util');
var path = require('path');

var sprintf = require('sprintf').sprintf;
var async = require('async');
var marked = require('marked');
var et = require('elementtree');
var XML = et.XML;
var ElementTree = et.ElementTree;
var element = et.Element;
var subElement = et.SubElement;

var utils = require('./utils');

var LINKS_REGEX = /!?\[((?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*)\]\((#)?\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*\)/g;
var BOLD_REGEX = /(\*(\w+)\*)/g;
var ITALIC_REGEX = /(_(\w+)_)/g;
var CODE_REGEX = /`(\w+)`/g;

function convertFile(inputFilePath, outputFilePath, callback) {
  var content = fs.readFileSync(inputFilePath, 'utf8'), ast, i, item, root,
      elems, elem, exampleElem, tableElem, trElem, name, attrib, text, id,
      currentParrent, lastParrentForLevel = {}, currentTableParent = null;

  ast = marked.lexer(content);

  elems = [];

  for (i = 0; i < ast.length; i++) {
    item = ast[i];

    name = null;
    attrib = {};
    text = null;

    if (item.type === 'heading') {
      if (item.depth === 1) {
        name = 'chapter';
        attrib = {'xmlns': 'http://docbook.org/ns/docbook',
                  'xmlns:xlink': 'http://www.w3.org/1999/xlink',
                  'xmlns:xi': 'http://www.w3.org/2001/XInclude',
                  'xmlns:svg': 'http://www.w3.org/2000/svg',
                  'version': '5.0',
                  'xml:id': item.text};

        elem = element(name, attrib);
        elems.push(elem);

        currentParrent = elem;
        lastParrentForLevel[1] = elem;

        elem = subElement(elem, 'title');
        elem.text = item.text;
        elems.push(elem);
      }
      else if ([2, 3, 4].indexOf(item.depth) !== -1) {
        if (item.depth === 4 && ast[i + 1].type === 'code') {
          // Next item is code block, consider this heading special and treat it
          // as a code block title
          ast[i + 1].label = item.text;
        }
        else {
          id = item.text;

          if (item.depth > 1) {
            // Include previous heading as the id prefix
            id = lastParrentForLevel[item.depth - 1].getItem(0).text + '-' + id;
          }

          id = utils.normalizeString(id);

          elem = subElement(lastParrentForLevel[item.depth - 1], 'section',
                            {'xml:id': id});
          elems.push(elem);

          currentParrent = elem;
          lastParrentForLevel[item.depth] = elem;

          elem = subElement(elem, 'title');
          elem.text = item.text;
          elems.push(elem);
        }
      }
    }
    else if (item.type === 'paragraph') {
      elem = subElement(currentParrent, 'para');
      elem.text = item.text;
      elems.push(elem);
    }
    else if (item.type === 'list_start' && item.ordered === false) {
      elem = subElement(currentParrent, 'itemizedlist');
      elems.push(elem);

      currentTableParent = elem;
    }
    else if (item.type === 'list_item_start') {
      elem = subElement(currentTableParent, 'listitem');
      elems.push(elem);

      currentParrent = elem;
    }
    else if (item.type === 'text') {
      elem = subElement(currentParrent, 'para');
      elem.text = item.text;
      elems.push(elem);
    }
    else if (item.type === 'table') {
      tableElem = subElement(currentParrent, 'informaltable', {'rules': 'all'});
      elems.push(tableElem);

      elem = subElement(tableElem, 'thead', {'xmlns': 'http://docbook.org/ns/docbook'});
      trElem = subElement(elem, 'tr');

      item.header.forEach(function(text) {
        elem = subElement(trElem, 'td');
        elem.text = text;
      });

      if (item.cells.length > 0) {
        elem = subElement(tableElem, 'tbody');

        item.cells.forEach(function(cell) {
          trElem = subElement(elem, 'tr');

          cell.forEach(function(text) {
            elem = subElement(trElem, 'td');
            elem.text = text;
          });
        });
      }
    }
    else if (item.type === 'code') {
      exampleElem = subElement(currentParrent, 'example');

      if (item.label) {
        elem = subElement(exampleElem, 'title');
        elem.text = item.label;
      }

      elem = subElement(exampleElem, 'programlisting', {'language': item.lang});

      if (item.file) {
        elem = subElement(exampleElem, 'xi:include', {'href': item.file, 'parse': 'text'});
        elem = subElement(elem, 'xi:fallback');
        elem.text = 'Missing example!';
      }
      else {
        elem.text = item.text;
      }
    }
    else if (item.type === 'html') {
      // TODO: raw html
    }
  }

  root = elems[0];

  if (!root) {
    console.log('Input markdown file is empty, skipping saving...');
    return;
  }

  etree = new ElementTree(root);
  xml = etree.write({'xml_declaration': true, 'indent': 4});

  // Replace links
  xml = processXml(xml);
  fs.writeFileSync(outputFilePath, xml, 'utf8');
}

function processXml(xml) {

  function linksReplaceFunction(str, p1, p2, p3, p4, p5, p6) {
    if (/^http/.test(p3)) {
      // External url
      return sprintf('<link xlink:href="%s">%s</link>', p3, p1);
    }
    else {
      // Internal url
      return sprintf('<link linked="%s">%s</link>', p3, p1);
    }
  }

  function codeReplaceFunction(str, p1) {
    return sprintf('<emphasis>%s</emphasis>', p1);
  }

  function emphasisReplaceFunction(str, p1) {
    return sprintf('<emphasis>%s</emphasis>', p1);
  }

  xml = xml.replace(LINKS_REGEX, linksReplaceFunction);
  xml = xml.replace(CODE_REGEX, codeReplaceFunction);
  xml = xml.replace(BOLD_REGEX, emphasisReplaceFunction);
  xml = xml.replace(ITALIC_REGEX, emphasisReplaceFunction);

  return xml;
}

exports.convert = function(inputDirectory, outputDirectory, callback) {
  var files = fs.readdirSync(inputDirectory);

  files = files.filter(function(name) {
    return /\.md$/.test(name);
  });

  async.forEach(files, function(name, callback) {
    var inputFilePath = path.join(inputDirectory, name),
        outputFilePath = path.join(outputDirectory, name.replace('.md', '.xml'));

    convertFile(inputFilePath, outputFilePath, callback);
  }, callback);
}
<commit_msg>Fix a bug in table generation.<commit_after>/**
 *  Copyright 2012 Tomaz Muraus
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var fs = require('fs');
var util = require('util');
var path = require('path');

var sprintf = require('sprintf').sprintf;
var async = require('async');
var marked = require('marked');
var et = require('elementtree');
var XML = et.XML;
var ElementTree = et.ElementTree;
var element = et.Element;
var subElement = et.SubElement;

var utils = require('./utils');

var LINKS_REGEX = /!?\[((?:\[[^\]]*\]|[^\]]|\](?=[^\[]*\]))*)\]\((#)?\s*<?([^\s]*?)>?(?:\s+['"]([^\0]*?)['"])?\s*\)/g;
var BOLD_REGEX = /(\*(\w+)\*)/g;
var ITALIC_REGEX = /(_(\w+)_)/g;
var CODE_REGEX = /`(\w+)`/g;

function convertFile(inputFilePath, outputFilePath, callback) {
  var content = fs.readFileSync(inputFilePath, 'utf8'), ast, i, item, root,
      elems, elem, exampleElem, tableElem, theadElem, tbodyElem, trElem, name, attrib, text, id,
      currentParrent, lastParrentForLevel = {}, currentTableParent = null;

  ast = marked.lexer(content);

  elems = [];

  for (i = 0; i < ast.length; i++) {
    item = ast[i];

    name = null;
    attrib = {};
    text = null;

    if (item.type === 'heading') {
      if (item.depth === 1) {
        name = 'chapter';
        attrib = {'xmlns': 'http://docbook.org/ns/docbook',
                  'xmlns:xlink': 'http://www.w3.org/1999/xlink',
                  'xmlns:xi': 'http://www.w3.org/2001/XInclude',
                  'xmlns:svg': 'http://www.w3.org/2000/svg',
                  'version': '5.0',
                  'xml:id': item.text};

        elem = element(name, attrib);
        elems.push(elem);

        currentParrent = elem;
        lastParrentForLevel[1] = elem;

        elem = subElement(elem, 'title');
        elem.text = item.text;
        elems.push(elem);
      }
      else if ([2, 3, 4].indexOf(item.depth) !== -1) {
        if (item.depth === 4 && ast[i + 1].type === 'code') {
          // Next item is code block, consider this heading special and treat it
          // as a code block title
          ast[i + 1].label = item.text;
        }
        else {
          id = item.text;

          if (item.depth > 1) {
            // Include previous heading as the id prefix
            id = lastParrentForLevel[item.depth - 1].getItem(0).text + '-' + id;
          }

          id = utils.normalizeString(id);

          elem = subElement(lastParrentForLevel[item.depth - 1], 'section',
                            {'xml:id': id});
          elems.push(elem);

          currentParrent = elem;
          lastParrentForLevel[item.depth] = elem;

          elem = subElement(elem, 'title');
          elem.text = item.text;
          elems.push(elem);
        }
      }
    }
    else if (item.type === 'paragraph') {
      elem = subElement(currentParrent, 'para');
      elem.text = item.text;
      elems.push(elem);
    }
    else if (item.type === 'list_start' && item.ordered === false) {
      elem = subElement(currentParrent, 'itemizedlist');
      elems.push(elem);

      currentTableParent = elem;
    }
    else if (item.type === 'list_item_start') {
      elem = subElement(currentTableParent, 'listitem');
      elems.push(elem);

      currentParrent = elem;
    }
    else if (item.type === 'text') {
      elem = subElement(currentParrent, 'para');
      elem.text = item.text;
      elems.push(elem);
    }
    else if (item.type === 'table') {
      tableElem = subElement(currentParrent, 'informaltable', {'rules': 'all'});
      elems.push(tableElem);

      theadElem = subElement(tableElem, 'thead', {'xmlns': 'http://docbook.org/ns/docbook'});
      trElem = subElement(theadElem, 'tr');

      item.header.forEach(function(text) {
        elem = subElement(trElem, 'td');
        elem.text = text;
      });

      if (item.cells.length > 0) {
        tbodyElem = subElement(tableElem, 'tbody');

        item.cells.forEach(function(cell) {
          trElem = subElement(tbodyElem, 'tr');

          cell.forEach(function(text) {
            elem = subElement(trElem, 'td');
            elem.text = (text === '' ? ' ' : text);
          });
        });
      }
    }
    else if (item.type === 'code') {
      exampleElem = subElement(currentParrent, 'example');

      if (item.label) {
        elem = subElement(exampleElem, 'title');
        elem.text = item.label;
      }

      elem = subElement(exampleElem, 'programlisting', {'language': item.lang});

      if (item.file) {
        elem = subElement(exampleElem, 'xi:include', {'href': item.file, 'parse': 'text'});
        elem = subElement(elem, 'xi:fallback');
        elem.text = 'Missing example!';
      }
      else {
        elem.text = item.text;
      }
    }
    else if (item.type === 'html') {
      // TODO: raw html
    }
  }

  root = elems[0];

  if (!root) {
    console.log('Input markdown file is empty, skipping saving...');
    return;
  }

  etree = new ElementTree(root);
  xml = etree.write({'xml_declaration': true, 'indent': 4});

  // Replace links
  xml = processXml(xml);
  fs.writeFileSync(outputFilePath, xml, 'utf8');
}

function processXml(xml) {

  function linksReplaceFunction(str, p1, p2, p3, p4, p5, p6) {
    if (/^http/.test(p3)) {
      // External url
      return sprintf('<link xlink:href="%s">%s</link>', p3, p1);
    }
    else {
      // Internal url
      return sprintf('<link linked="%s">%s</link>', p3, p1);
    }
  }

  function codeReplaceFunction(str, p1) {
    return sprintf('<emphasis>%s</emphasis>', p1);
  }

  function emphasisReplaceFunction(str, p1) {
    return sprintf('<emphasis>%s</emphasis>', p1);
  }

  xml = xml.replace(LINKS_REGEX, linksReplaceFunction);
  xml = xml.replace(CODE_REGEX, codeReplaceFunction);
  xml = xml.replace(BOLD_REGEX, emphasisReplaceFunction);
  xml = xml.replace(ITALIC_REGEX, emphasisReplaceFunction);

  return xml;
}

exports.convert = function(inputDirectory, outputDirectory, callback) {
  var files = fs.readdirSync(inputDirectory);

  files = files.filter(function(name) {
    return /\.md$/.test(name);
  });

  async.forEach(files, function(name, callback) {
    var inputFilePath = path.join(inputDirectory, name),
        outputFilePath = path.join(outputDirectory, name.replace('.md', '.xml'));

    convertFile(inputFilePath, outputFilePath, callback);
  }, callback);
}
<|endoftext|>


<commit_before>var sprintf = require("sprintf-js").sprintf;

/**
 * Perform JSON conversion to target language
 */
function jsonToLang(json, lang, level) {
  var indentationLevel = level || 0;
  var dataFormatted = '';
  var formatMap = {
    csharp: {
      fileStart: "",
      variable: "var data = ",
      hashStart: "new Hashtable {",
      hashEnd: "}",
      hashRow: "{ \"%s\", %s }",
      hashRowEnd: ",\n",
      indent: "    ",
      lineEnd: ";"
    },
    php: {
      fileStart: "<?php\n",
      variable: "$data = ",
      hashStart: "[",
      hashEnd: "]",
      hashRow: "'%s' => %s",
      hashRowEnd: ",\n",
      indent: "    ",
      lineEnd: ";"
    },
    python: {
      fileStart: "",
      variable: "data = ",
      hashStart: "{",
      hashEnd: "}",
      hashRow: "'%s' : %s",
      hashRowEnd: ",\n",
      indent: "    ",
      lineEnd: ""
    },
    ruby: {
      fileStart: "",
      variable: "data = ",
      hashStart: "{",
      hashEnd: "}",
      hashRow: ":%s => %s",
      hashRowEnd: ",\n",
      indent: "  ",
      lineEnd: ""
    }
  };

  // Format JSON data according to how it looks
  // Calls jsonToLang recursively to format arrays and objects
  var jsonValueFormat = function(value, lang) {
    var indent = "\t".repeat(indentationLevel)
    var valueType = typeof value;

    // Recursive
    if (valueType == "array" || valueType == "object") {
      value = jsonToLang(value, lang, indentationLevel+1);
    } else if (valueType == "string") {
      // Certain languages require double quotes for values
      switch(lang)
      {
        case 'csharp':
          value = "\"" + value + "\"";
          break;

        default:
          value = "'" + value + "'";
          break;
      }
    }

    return value;
  };

  // What kind of language is it?
  switch(lang) {
    case 'json':
      dataFormatted = 'var data = ' + JSON.stringify(json, null, 4);
      break;

    default:
      var format = formatMap[lang];
      if (!format) {
        return false;
      }

      // Level indentation
      if (indentationLevel === 0) {
        dataFormatted += format.fileStart.replace('<', '<').replace('>', '>');
        dataFormatted += format.variable;
      }
      dataFormatted += format.hashStart + "\n";
      var indent = format.indent.repeat(indentationLevel + 1);

      if (typeof json == "array") {
        var jsonLen = json.length;
        for(var key = 0; key < jsonLen; key++) {
          var value = jsonValueFormat(json[key], lang);

          dataFormatted += indent + sprintf(format.hashRow, key, value);
          if (jsonLen != key) {
            dataFormatted += format.hashRowEnd;
          }
        };
      }
      else if (typeof json == "object") {
        var jsonLen = Object.keys(json).length;
        var i = 0;
        for(var key in json) {
          i++;
          var value = jsonValueFormat(json[key], lang);

          dataFormatted += indent + sprintf(format.hashRow, key, value);
          if (jsonLen != i) {
            dataFormatted += format.hashRowEnd;
          }
        };
      }

      indent = format.indent.repeat(indentationLevel);
      dataFormatted += "\n" + indent + format.hashEnd;

      if (indentationLevel === 0) {
        dataFormatted += format.lineEnd;
      }
  }

  return dataFormatted;
}

/**
 * String repeat polyfill
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
 */
if (!String.prototype.repeat) {
  String.prototype.repeat = function(count) {
    'use strict';
    if (this == null) {
      throw new TypeError('can\'t convert ' + this + ' to object');
    }
    var str = '' + this;
    count = +count;
    if (count != count) {
      count = 0;
    }
    if (count < 0) {
      throw new RangeError('repeat count must be non-negative');
    }
    if (count == Infinity) {
      throw new RangeError('repeat count must be less than infinity');
    }
    count = Math.floor(count);
    if (str.length == 0 || count == 0) {
      return '';
    }
    // Ensuring count is a 31-bit integer allows us to heavily optimize the
    // main part. But anyway, most current (August 2014) browsers can't handle
    // strings 1 << 28 chars or longer, so:
    if (str.length * count >= 1 << 28) {
      throw new RangeError('repeat count must not overflow maximum string size');
    }
    var rpt = '';
    for (;;) {
      if ((count & 1) == 1) {
        rpt += str;
      }
      count >>>= 1;
      if (count == 0) {
        break;
      }
      str += str;
    }
    return rpt;
  }
}

/**
 * Exports
 */
module.exports.jsonToLang = jsonToLang;
<commit_msg>Use double quotes by default for all languages<commit_after>var sprintf = require("sprintf-js").sprintf;

/**
 * Perform JSON conversion to target language
 */
function jsonToLang(json, lang, level) {
  var indentationLevel = level || 0;
  var dataFormatted = '';
  var formatMap = {
    csharp: {
      fileStart: "",
      variable: "var data = ",
      hashStart: "new Hashtable {",
      hashEnd: "}",
      hashRow: "{ \"%s\", %s }",
      hashRowEnd: ",\n",
      indent: "    ",
      lineEnd: ";"
    },
    php: {
      fileStart: "<?php\n",
      variable: "$data = ",
      hashStart: "[",
      hashEnd: "]",
      hashRow: "\"%s\" => %s",
      hashRowEnd: ",\n",
      indent: "    ",
      lineEnd: ";"
    },
    python: {
      fileStart: "",
      variable: "data = ",
      hashStart: "{",
      hashEnd: "}",
      hashRow: "\"%s\" : %s",
      hashRowEnd: ",\n",
      indent: "    ",
      lineEnd: ""
    },
    ruby: {
      fileStart: "",
      variable: "data = ",
      hashStart: "{",
      hashEnd: "}",
      hashRow: ":%s => %s",
      hashRowEnd: ",\n",
      indent: "  ",
      lineEnd: ""
    }
  };

  // Format JSON data according to how it looks
  // Calls jsonToLang recursively to format arrays and objects
  var jsonValueFormat = function(value, lang) {
    var indent = "\t".repeat(indentationLevel)
    var valueType = typeof value;

    // Recursive
    if (valueType == "array" || valueType == "object") {
      value = jsonToLang(value, lang, indentationLevel+1);
    } else if (valueType == "string") {
      value = "\"" + value + "\"";
    }

    return value;
  };

  // What kind of language is it?
  switch(lang) {
    case 'json':
      dataFormatted = 'var data = ' + JSON.stringify(json, null, 4);
      break;

    default:
      var format = formatMap[lang];
      if (!format) {
        return false;
      }

      // Level indentation
      if (indentationLevel === 0) {
        dataFormatted += format.fileStart.replace('<', '<').replace('>', '>');
        dataFormatted += format.variable;
      }
      dataFormatted += format.hashStart + "\n";
      var indent = format.indent.repeat(indentationLevel + 1);

      if (typeof json == "array") {
        var jsonLen = json.length;
        for(var key = 0; key < jsonLen; key++) {
          var value = jsonValueFormat(json[key], lang);

          dataFormatted += indent + sprintf(format.hashRow, key, value);
          if (jsonLen != key) {
            dataFormatted += format.hashRowEnd;
          }
        };
      }
      else if (typeof json == "object") {
        var jsonLen = Object.keys(json).length;
        var i = 0;
        for(var key in json) {
          i++;
          var value = jsonValueFormat(json[key], lang);

          dataFormatted += indent + sprintf(format.hashRow, key, value);
          if (jsonLen != i) {
            dataFormatted += format.hashRowEnd;
          }
        };
      }

      indent = format.indent.repeat(indentationLevel);
      dataFormatted += "\n" + indent + format.hashEnd;

      if (indentationLevel === 0) {
        dataFormatted += format.lineEnd;
      }
  }

  return dataFormatted;
}

/**
 * String repeat polyfill
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
 */
if (!String.prototype.repeat) {
  String.prototype.repeat = function(count) {
    'use strict';
    if (this == null) {
      throw new TypeError('can\'t convert ' + this + ' to object');
    }
    var str = '' + this;
    count = +count;
    if (count != count) {
      count = 0;
    }
    if (count < 0) {
      throw new RangeError('repeat count must be non-negative');
    }
    if (count == Infinity) {
      throw new RangeError('repeat count must be less than infinity');
    }
    count = Math.floor(count);
    if (str.length == 0 || count == 0) {
      return '';
    }
    // Ensuring count is a 31-bit integer allows us to heavily optimize the
    // main part. But anyway, most current (August 2014) browsers can't handle
    // strings 1 << 28 chars or longer, so:
    if (str.length * count >= 1 << 28) {
      throw new RangeError('repeat count must not overflow maximum string size');
    }
    var rpt = '';
    for (;;) {
      if ((count & 1) == 1) {
        rpt += str;
      }
      count >>>= 1;
      if (count == 0) {
        break;
      }
      str += str;
    }
    return rpt;
  }
}

/**
 * Exports
 */
module.exports.jsonToLang = jsonToLang;
<|endoftext|>


<commit_before>const { FriendlyError } = require('discord.js-commando');
const { oneLine } = require('common-tags');
const path = require('path');
const winston = require('winston');

require('./structures/CommandoMessage');

const SequelizeProvider = require('./providers/Sequelize');
const { OWNERS, COMMAND_PREFIX, TOKEN } = process.env;

const CommandoClient = require('./structures/CommandoClient');
const client = new CommandoClient({
	owner: OWNERS.split(','),
	commandPrefix: COMMAND_PREFIX,
	unknownCommandResponse: false,
	disableEveryone: true
});

const Currency = require('./structures/currency/Currency');
const Experience = require('./structures/currency/Experience');
const userName = require('./models/UserName');

const earnedRecently = [];
const gainedXPRecently = [];

client.setProvider(new SequelizeProvider(client.database));

client.dispatcher.addInhibitor(msg => {
	const blacklist = client.provider.get('global', 'userBlacklist', []);
	if (!blacklist.includes(msg.author.id)) return false;
	return `Has been blacklisted.`;
});

client.on('error', winston.error)
	.on('warn', winston.warn)
	.once('ready', () => Currency.leaderboard())
	.on('ready', () => {
		winston.info(oneLine`
			[DISCORD]: Client ready...
			Logged in as ${client.user.tag} (${client.user.id})
		`);
	})
	.on('disconnect', () => winston.warn('[DISCORD]: Disconnected!'))
	.on('reconnect', () => winston.warn('[DISCORD]: Reconnecting...'))
	.on('commandRun', (cmd, promise, msg, args) =>
		winston.info(oneLine`
			[DISCORD]: ${msg.author.tag} (${msg.author.id})
			> ${msg.guild ? `${msg.guild.name} (${msg.guild.id})` : 'DM'}
			>> ${cmd.groupID}:${cmd.memberName}
			${Object.values(args).length ? `>>> ${Object.values(args)}` : ''}
		`)
	)
	.on('unknownCommand', msg => {
		if (msg.channel.type === 'dm') return;
		if (msg.author.bot) return;
		if (msg.content.split(msg.guild.commandPrefix)[1] === 'undefined') return;
		const args = { name: msg.content.split(msg.guild.commandPrefix)[1].toLowerCase() };
		client.registry.resolveCommand('tags:tag').run(msg, args);
	})
	.on('message', async message => {
		if (message.channel.type === 'dm') return;
		if (message.author.bot) return;

		const channelLocks = client.provider.get(message.guild.id, 'locks', []);
		if (channelLocks.includes(message.channel.id)) return;
		if (!earnedRecently.includes(message.author.id)) {
			const hasImageAttachment = message.attachments.some(attachment =>
				attachment.url.match(/\.(png|jpg|jpeg|gif|webp)$/)
			);
			const moneyEarned = hasImageAttachment
				? Math.ceil(Math.random() * 7) + 5
				: Math.ceil(Math.random() * 7) + 1;

			Currency._changeBalance(message.author.id, moneyEarned);

			earnedRecently.push(message.author.id);
			setTimeout(() => {
				const index = earnedRecently.indexOf(message.author.id);
				earnedRecently.splice(index, 1);
			}, 8000);
		}

		if (!gainedXPRecently.includes(message.author.id)) {
			const xpEarned = Math.ceil(Math.random() * 9) + 3;
			const oldLevel = await Experience.getLevel(message.author.id);

			Experience.addExperience(message.author.id, xpEarned).then(async () => {
				const newLevel = await Experience.getLevel(message.author.id);
				if (newLevel > oldLevel) {
					Currency._changeBalance(message.author.id, 100 * newLevel);
				}
			}).catch(err => null); // eslint-disable-line no-unused-vars, handle-callback-err

			gainedXPRecently.push(message.author.id);
			setTimeout(() => {
				const index = gainedXPRecently.indexOf(message.author.id);
				gainedXPRecently.splice(index, 1);
			}, 60 * 1000);
		}
	})
	.on('commandError', (cmd, err) => {
		if (err instanceof FriendlyError) return;
		winston.error(`[DISCORD]: Error in command ${cmd.groupID}:${cmd.memberName}`, err);
	})
	.on('commandBlocked', (msg, reason) => {
		winston.info(oneLine`
			[DISCORD]: Command ${msg.command ? `${msg.command.groupID}:${msg.command.memberName}` : ''}
			blocked; User ${msg.author.tag} (${msg.author.id}): ${reason}
		`);
	})
	.on('commandPrefixChange', (guild, prefix) => {
		winston.info(oneLine`
			[DISCORD]: Prefix changed to ${prefix || 'the default'}
			${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}.
		`);
	})
	.on('commandStatusChange', (guild, command, enabled) => {
		winston.info(oneLine`
			[DISCORD]: Command ${command.groupID}:${command.memberName}
			${enabled ? 'enabled' : 'disabled'}
			${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}.
		`);
	})
	.on('groupStatusChange', (guild, group, enabled) => {
		winston.info(oneLine`
			[DISCORD]: Group ${group.id}
			${enabled ? 'enabled' : 'disabled'}
			${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}.
		`);
	})
	.on('userUpdate', (oldUser, newUser) => {
		if (oldUser.username !== newUser.username) {
			userName.create({ userID: newUser.id, username: oldUser.username }).catch(err => null); // eslint-disable-line no-unused-vars, handle-callback-err, max-len
		}
	});

client.registry
	.registerGroups([
		['info', 'Info'],
		['economy', 'Economy'],
		['social', 'Social'],
		['games', 'Games'],
		['item', 'Item'],
		['weather', 'Weather'],
		['music', 'Music'],
		['tags', 'Tags']
	])
	.registerDefaultTypes()
	.registerDefaultGroups()
	.registerDefaultCommands({ help: false })
	.registerTypesIn(path.join(__dirname, 'types'))
	.registerCommandsIn(path.join(__dirname, 'commands'));

client.login(TOKEN);
<commit_msg>add: azure app insights<commit_after>const { FriendlyError } = require('discord.js-commando');
const { oneLine } = require('common-tags');
const path = require('path');
const winston = require('winston');

const SequelizeProvider = require('./providers/Sequelize');
const { OWNERS, COMMAND_PREFIX, TOKEN, AZURE_APP_INSIGHTS_KEY } = process.env;

require('./structures/CommandoMessage');

const appInsights = require('applicationinsights');
appInsights
	.setup(AZURE_APP_INSIGHTS_KEY)
	.setAutoCollectConsole(true)
	.start();
const appInsightClient = appInsights.defaultClient;

const CommandoClient = require('./structures/CommandoClient');
const client = new CommandoClient({
	owner: OWNERS.split(','),
	commandPrefix: COMMAND_PREFIX,
	unknownCommandResponse: false,
	disableEveryone: true
});

const Currency = require('./structures/currency/Currency');
const Experience = require('./structures/currency/Experience');
const userName = require('./models/UserName');

const earnedRecently = [];
const gainedXPRecently = [];

client.setProvider(new SequelizeProvider(client.database));

client.dispatcher.addInhibitor(msg => {
	const blacklist = client.provider.get('global', 'userBlacklist', []);
	if (!blacklist.includes(msg.author.id)) return false;
	return `Has been blacklisted.`;
});

let startTime = Date.now();
client.on('error', winston.error)
	.on('warn', winston.warn)
	.once('ready', () => Currency.leaderboard())
	.on('ready', () => {
		winston.info(oneLine`
			[DISCORD]: Client ready...
			Logged in as ${client.user.tag} (${client.user.id})
		`);

		appInsightClient.trackMetric('Timothy startup time', Date.now() - startTime);
	})
	.on('disconnect', () => winston.warn('[DISCORD]: Disconnected!'))
	.on('reconnect', () => {
		winston.warn('[DISCORD]: Reconnecting...');
		startTime = Date.now();
	})
	.on('commandRun', (cmd, promise, msg, args) =>
		winston.info(oneLine`
			[DISCORD]: ${msg.author.tag} (${msg.author.id})
			> ${msg.guild ? `${msg.guild.name} (${msg.guild.id})` : 'DM'}
			>> ${cmd.groupID}:${cmd.memberName}
			${Object.values(args).length ? `>>> ${Object.values(args)}` : ''}
		`)
	)
	.on('unknownCommand', msg => {
		if (msg.channel.type === 'dm') return;
		if (msg.author.bot) return;
		if (msg.content.split(msg.guild.commandPrefix)[1] === 'undefined') return;
		const args = { name: msg.content.split(msg.guild.commandPrefix)[1].toLowerCase() };
		client.registry.resolveCommand('tags:tag').run(msg, args);
	})
	.on('message', async message => {
		if (message.channel.type === 'dm') return;
		if (message.author.bot) return;

		const channelLocks = client.provider.get(message.guild.id, 'locks', []);
		if (channelLocks.includes(message.channel.id)) return;
		if (!earnedRecently.includes(message.author.id)) {
			const hasImageAttachment = message.attachments.some(attachment =>
				attachment.url.match(/\.(png|jpg|jpeg|gif|webp)$/)
			);
			const moneyEarned = hasImageAttachment
				? Math.ceil(Math.random() * 7) + 5
				: Math.ceil(Math.random() * 7) + 1;

			Currency._changeBalance(message.author.id, moneyEarned);

			earnedRecently.push(message.author.id);
			setTimeout(() => {
				const index = earnedRecently.indexOf(message.author.id);
				earnedRecently.splice(index, 1);
			}, 8000);
		}

		if (!gainedXPRecently.includes(message.author.id)) {
			const xpEarned = Math.ceil(Math.random() * 9) + 3;
			const oldLevel = await Experience.getLevel(message.author.id);

			Experience.addExperience(message.author.id, xpEarned).then(async () => {
				const newLevel = await Experience.getLevel(message.author.id);
				if (newLevel > oldLevel) {
					Currency._changeBalance(message.author.id, 100 * newLevel);
				}
			}).catch(err => null); // eslint-disable-line no-unused-vars, handle-callback-err

			gainedXPRecently.push(message.author.id);
			setTimeout(() => {
				const index = gainedXPRecently.indexOf(message.author.id);
				gainedXPRecently.splice(index, 1);
			}, 60 * 1000);
		}
	})
	.on('commandError', (cmd, err) => {
		if (err instanceof FriendlyError) return;
		winston.error(`[DISCORD]: Error in command ${cmd.groupID}:${cmd.memberName}`, err);
	})
	.on('commandBlocked', (msg, reason) => {
		winston.info(oneLine`
			[DISCORD]: Command ${msg.command ? `${msg.command.groupID}:${msg.command.memberName}` : ''}
			blocked; User ${msg.author.tag} (${msg.author.id}): ${reason}
		`);
	})
	.on('commandPrefixChange', (guild, prefix) => {
		winston.info(oneLine`
			[DISCORD]: Prefix changed to ${prefix || 'the default'}
			${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}.
		`);
	})
	.on('commandStatusChange', (guild, command, enabled) => {
		winston.info(oneLine`
			[DISCORD]: Command ${command.groupID}:${command.memberName}
			${enabled ? 'enabled' : 'disabled'}
			${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}.
		`);
	})
	.on('groupStatusChange', (guild, group, enabled) => {
		winston.info(oneLine`
			[DISCORD]: Group ${group.id}
			${enabled ? 'enabled' : 'disabled'}
			${guild ? `in guild ${guild.name} (${guild.id})` : 'globally'}.
		`);
	})
	.on('userUpdate', (oldUser, newUser) => {
		if (oldUser.username !== newUser.username) {
			userName.create({ userID: newUser.id, username: oldUser.username }).catch(err => null); // eslint-disable-line no-unused-vars, handle-callback-err, max-len
		}
	});

client.registry
	.registerGroups([
		['info', 'Info'],
		['economy', 'Economy'],
		['social', 'Social'],
		['games', 'Games'],
		['item', 'Item'],
		['weather', 'Weather'],
		['music', 'Music'],
		['tags', 'Tags']
	])
	.registerDefaultTypes()
	.registerDefaultGroups()
	.registerDefaultCommands({ help: false })
	.registerTypesIn(path.join(__dirname, 'types'))
	.registerCommandsIn(path.join(__dirname, 'commands'));

client.login(TOKEN);
<|endoftext|>


<commit_before>var config = require('../config/configuration');

module.exports = {

  /**
  * Path for all crud routes
  * Order by action with its method and uri
  */
  paths: {
    'all': {
      method: 'get',
      uri: '/%s'
    },
    'read': {
      method: 'get',
      uri: '/%s/:id'
    },
    'create': {
      method: 'post',
      uri: '/%s'
    },
    'update': {
      method: 'put',
      uri: '/%s/:id'
    },
    'delete': {
      method: 'delete',
      uri: '/%s/:id'
    }
  },

  /**
  * Init the model for the ressource
  * @param {String} model name
  */
  initModel: function(model) {
    this.model = require('../../api/models/'+ model);
  },

  /**
  * Create a new crud route
  * @param {String || Object}
  * @return {Array} routes object
  */
  initRoute: function(ressource) {
    var routes = [];

    // All route with no auth
    if (typeof ressource == 'string') {
      for (var path in this.paths)
        routes.push(this.newRoute(path, ressource, false));
    }
    else if (typeof ressource == 'object')
      for (var i = 0; i < ressource.endpoints.length; i++)
        routes.push(this.newRoute(ressource.endpoints[i], ressource.data, ressource.auth));

    return routes;
  },

  /**
  * Create route object for requester
  * @param {String} the action
  * @param {String} the name of the ressources
  * @param {Array} || Boolean is authenticate
  * @return {Object} route
  */
  newRoute: function(action, ressource, auth) {
    var route = {
      controller: this,
      middleware: [],
      action    : action,
    };

    if (auth)
      route.middleware = (auth === true || auth.indexOf(action) != -1) ? [config.defaultAuthStrategy] : [];

      if(this.paths[action]) {
        route.path   = this.paths[action].uri.replace(/%s/g, ressource);
        route.method = this.paths[action].method.replace(/%s/g, ressource);
      }else {
        route.path   = '/' + ressource;
        route.method = 'get';
      }

    return route;
  },

  /**
  * Create - Crud
  */
  create: function(req, res, next) {
    var new_data = req.body;

    this.model.create(new_data, function(err, data) {
      if (err) res.send(err);
      res.json(data);
      next(req, res);
    });
	},

  /**
  * Read - Crud
  */
	read: function(req, res, next) {
		this.model.findById(req.params.id, function(err, data) {
      if (err) res.send(err);
      if(!data) res.json({message : 'No data found'});
      res.json(data);
      next(req, res);
    });
	},

  /**
  * Update - Crud
  */
	update: function(req, res, next) {
    var self = this;

    delete req.body.id;
    delete req.body._id;

    self.model.update({ _id: req.params.id }, req.body, { multi: true }, function(err, data) {
      if(err) throw err;
      self.model.find({ _id: req.params.id}, function(err, data) {
        res.json(data);
        next(req, res);
      });
    });
	},

  /**
  * Delete - Crud
  */
	delete: function(req, res, next) {
		this.model.remove({ _id: req.params.id }, function(err, data) {
      if (err) res.send(err);
      res.json({message: 'Data deleted', status : true});
      next(req, res);
    });
	},

  /**
  * Get all - Crud
  */
	all: function(req, res, next) {

    // Pagination
    var page     = parseInt(req.query.page)     || 1;
    var per_page = parseInt(req.query.per_page) || 10;
    var sort     = parseInt(req.query.sort)     || -1;
    var options = {
      page: page,
      limit: per_page,
      sortBy: {
        _id: sort
      }
    };

    // Get all with pagination
    this.model.paginate({}, options, function(err, results, pageCount, itemCount) {
      if (err) throw err;
      res.json({
        items: results,
        total_items: itemCount,
        page: page,
        max_page: pageCount
      });
      next(req, res);
    });
	}

};
<commit_msg>Fixed fail<commit_after>var config = require('../config/configuration');

module.exports = {

  /**
  * Array contains all models
  */
  models: [],

  /**
  * Path for all crud routes
  * Order by action with its method and uri
  */
  paths: {
    'all': {
      method: 'get',
      uri: '/%s'
    },
    'read': {
      method: 'get',
      uri: '/%s/:id'
    },
    'create': {
      method: 'post',
      uri: '/%s'
    },
    'update': {
      method: 'put',
      uri: '/%s/:id'
    },
    'delete': {
      method: 'delete',
      uri: '/%s/:id'
    }
  },

  /**
  * Init the model for the ressource
  * @param {String} model name
  */
  initModel: function(model) {
    if (!this.models[model])
      this.models[model] = require('../../api/models/'+ model);
  },

  /**
  * Create a new crud route
  * @param {String || Object}
  * @return {Array} routes object
  */
  initRoute: function(ressource) {
    var routes = [];

    // All route with no auth
    if (typeof ressource == 'string') {
      for (var path in this.paths)
        routes.push(this.newRoute(path, ressource, false));
    }
    else if (typeof ressource == 'object')
      for (var i = 0; i < ressource.endpoints.length; i++)
        routes.push(this.newRoute(ressource.endpoints[i], ressource.data, ressource.auth));

    return routes;
  },

  /**
  * Create route object for requester
  * @param {String} the action
  * @param {String} the name of the ressources
  * @param {Array} || Boolean is authenticate
  * @return {Object} route
  */
  newRoute: function(action, ressource, auth) {
    var route = {
      controller: this,
      middleware: [],
      action    : action,
    };

    if (auth)
      route.middleware = (auth === true || auth.indexOf(action) != -1) ? [config.defaultAuthStrategy] : [];

      if(this.paths[action]) {
        route.path   = this.paths[action].uri.replace(/%s/g, ressource);
        route.method = this.paths[action].method.replace(/%s/g, ressource);
      }else {
        route.path   = '/' + ressource;
        route.method = 'get';
      }

    return route;
  },

  /**
  * Get the model for the right ressource
  * @param {string} url
  * @return {object} mongoose model
  */
  getModel: function(url) {
    return this.models[url.replace(/^\/|\/$/g, '')];
  },

  /**
  * Create - Crud
  */
  create: function(req, res, next) {

    /* Get the model */
    this.model = this.getModel(req.url);

    var new_data = req.body;

    this.model.create(new_data, function(err, data) {
      if (err) res.send(err);
      res.json(data);
      next(req, res);
    });
	},

  /**
  * Read - Crud
  */
	read: function(req, res, next) {

    /* Get the model */
    this.model = this.getModel(req.url);

		this.model.findById(req.params.id, function(err, data) {
      if (err) res.send(err);
      if(!data) res.json({message : 'No data found'});
      res.json(data);
      next(req, res);
    });
	},

  /**
  * Update - Crud
  */
	update: function(req, res, next) {

    /* Get the model */
    this.model = this.getModel(req.url);

    var self = this;

    delete req.body.id;
    delete req.body._id;

    self.model.update({ _id: req.params.id }, req.body, { multi: true }, function(err, data) {
      if(err) throw err;
      self.model.find({ _id: req.params.id}, function(err, data) {
        res.json(data);
        next(req, res);
      });
    });
	},

  /**
  * Delete - Crud
  */
	delete: function(req, res, next) {

    /* Get the model */
    this.model = this.getModel(req.url);

		this.model.remove({ _id: req.params.id }, function(err, data) {
      if (err) res.send(err);
      res.json({message: 'Data deleted', status : true});
      next(req, res);
    });
	},

  /**
  * Get all - Crud
  */
	all: function(req, res, next) {

    /* Get the model */
    this.model = this.getModel(req.url);

    // Pagination
    var page     = parseInt(req.query.page)     || 1;
    var per_page = parseInt(req.query.per_page) || 10;
    var sort     = parseInt(req.query.sort)     || -1;
    var options = {
      page: page,
      limit: per_page,
      sortBy: {
        _id: sort
      }
    };

    // Get all with pagination
    this.model.paginate({}, options, function(err, results, pageCount, itemCount) {
      if (err) throw err;
      res.json({
        items: results,
        total_items: itemCount,
        page: page,
        max_page: pageCount
      });
      next(req, res);
    });
	}

};
<|endoftext|>


<commit_before>describe('ax5.util.left TEST', function () {
    var testCases = [
        {
            args: ['ab.da', '.'],
            expect: 'ab',
            explanation: 'ab.da, "."'
        },
        {
            args: ['abc"', "\""],
            expect: 'abc',
            explanation: 'abc", "\""'
        },
        {
            args: ['abc4', 4],
            expect: 'abc4',
            explanation: 'abc4, 4'
        },
        {
            args: ['abc4efg', 4.0],
            expect: 'abc4',
            explanation: 'abc4efg, 4.0'
        },
        {
            args: ['abc4e', 100],
            expect: 'abc4e',
            explanation: 'abc4e, 100'
        },
        {
            args: ['abc4', '4'],
            expect: 'abc',
            explanation: 'abc4, "4"'
        },
        {
            args: ['abc', '4'],
            expect: '',
            explanation: 'abc, "4"'
        },
        {
            args: ['abc', 0],
            expect: '',
            explanation: 'abc", 0'
        },
        {
            args: ['abc', '0'],
            expect: '',
            explanation: 'abc", "0"'
        },
        {
            args: ['abc'],
            expect: '',
            explanation: 'abc'
        },
        {
            args: ['abc', ''],
            expect: '',
            explanation: 'abc, ""'
        },
        {
            args: ['abc', null],
            expect: '',
            explanation: 'abc, null'
        },
        {
            args: ['abc', undefined],
            expect: '',
            explanation: 'abc", undefined'
        },
        {
            args: ['abc', true],
            expect: '',
            explanation: 'abc", true'
        },
        {
            args: ['abc', false],
            expect: '',
            explanation: 'abc", false'
        },
        {
            args: ['abc', new Object],
            expect: '',
            explanation: 'abc", new Object'
        },
        {
            args: ['abc', new Function],
            expect: '',
            explanation: 'abc", new Function]'
        },
        {
            args: ['abc', []],
            expect: '',
            explanation: 'abc", []]'
        },
        {
            args: ['abc', {}],
            expect: '',
            explanation: 'abc", {}]'
        }
    ];

    testCases.forEach(function (testCase) {
        it('ax5.util.left(' + testCase.explanation + ') expect ' + JSON.stringify(testCase.expect), function () {
            var actual = ax5.util.left.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });

});

describe('ax5.util.right TEST', function () {
    var testCases = [
        {
            args: ['abcd.efd', 3],
            expect: 'efd'
        },
        {
            args: ['abcd.efd', '.'],
            expect: 'efd'
        },
        {
            args: ['vi"veri"veniversum"vivus"vici', '\"'],
            expect: 'vici'
        },
        {
            args: ['vi veri veniversum vivus vici', 10],
            expect: 'vivus vici'
        },
        {
            args: ['abc', null],
            expect: '',
            explanation: 'abc, null'
        },
        {
            args: ['abc', undefined],
            expect: '',
            explanation: 'abc", undefined'
        },
        {
            args: ['coincidence', true],
            expect: '',
            explanation: 'coincidence, ""'
        },
        {
            args: ['coincidence', false],
            expect: '',
            explanation: 'coincidence, ""'
        },
        {
            args: ['object', new Object],
            expect: '',
            explanation: 'object, new Object'
        },
        {
            args: ['function', new Function],
            expect: '',
            explanation: 'function, new Function'
        },
        {
            args: ['abc', []],
            expect: '',
            explanation: 'abc", []]'
        },
        {
            args: ['abc', {}],
            expect: '',
            explanation: 'abc", {}]'
        }
    ];

    testCases.forEach(function (testCase) {
        it('ax5.util.right(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.right.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.camelCase Test', function () {
    var testCases = [
        {
            args: ['inner-width'],
            expect: 'innerWidth'
        },
        {
            args: ['innerWidth'],
            expect: 'innerWidth'
        },
        {
            args: ['-ms-border-radius'],
            expect: 'msBorderRadius'
        },
        {
            args: ['-webkit-border-radius'],
            expect: 'WebkitBorderRadius'
        },
        {
            args: ['-moz-border-radius'],
            expect: 'MozBorderRadius'
        },
        {
            args: ['-o-border-radius'],
            expect: 'OBorderRadius'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.camelCase(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.camelCase.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.snakeCase Test', function () {
    var testCases = [
        {
            args: ['inner-width'],
            expect: 'inner-width'
        },
        {
            args: ['camelCase'],
            expect: 'camel-case'
        },
        {
            args: ['camelCase'],
            expect: 'camel-case'
        },
        {
            args: ['MsBorderRadius'],
            expect: '-ms-border-radius'
        },
        {
            args: ['WebkitBorderRadius'],
            expect: '-webkit-border-radius'
        },
        {
            args: ['MozBorderRadius'],
            expect: '-moz-border-radius'
        },
        {
            args: ['OBorderRadius'],
            expect: '-o-border-radius'
        },
        {
            args: ['HELLOWORLD'],
            expect: '-h-e-l-l-o-w-o-r-l-d'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.snakeCase(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.snakeCase.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.setDigit Test', function () {
    var testCases = [
        {
            args: [1, 2],
            expect: '01'
        },
        {
            args: [10, 2, 0, 2],
            expect: '1010'
        },
        {
            args: [10, 8, 0, 8],
            expect: '00000012'
        },
        {
            args: [320, 8, 0, 16],
            expect: '00000140'
        },
        {
            args: [10, 6, "10"],
            expect: '1010101010'
        },
        {
            args: [10, 4, 0, 4],
            expect: '0022'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.setDigit(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.setDigit.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.times Test', function () {
    var testCases = [
        {
            args: [2016, 2],
            expect: '20162016'
        },
        {
            args: ["", 4],
            expect: ''
        },
        {
            args: [true, 3],
            expect: 'truetruetrue'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.times(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.times.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.escape Test', function () {
    it('ax5.util.escapeHtml("HTML string & "escape"") expect "HTML <span>string</span> & "escape""', function () {
        var actual = ax5.util.escapeHtml('HTML string & "escape"');
        should(actual).be.equal('HTML <span>string</span> & "escape"');
    });

    it('ax5.util.escapeHtml(undefined) expect undefined', function () {
        var actual = ax5.util.escapeHtml(undefined);
        should(actual).be.equal(undefined);
    });

    it('ax5.util.escapeHtml({ "foo": 1 }) expect { "foo": 1 }', function () {
        var actual = ax5.util.escapeHtml({"foo": 1});
        should(actual).be.deepEqual({"foo": 1});
    });

    it('ax5.util.unescapeHtml("HTML <span>string</span> & "escape"") expect "HTML string & "escape""', function () {
        var actual = ax5.util.unescapeHtml('HTML <span>string</span> & "escape"');
        should(actual).be.equal('HTML string & "escape"');
    });

    it('ax5.util.unescapeHtml(undefined) expect undefined', function () {
        var actual = ax5.util.unescapeHtml(undefined);
        should(actual).be.equal(undefined);
    });

    it('ax5.util.unescapeHtml({ "foo": 1 }) expect { "foo": 1 }', function () {
        var actual = ax5.util.unescapeHtml({"foo": 1});
        should(actual).be.deepEqual({"foo": 1});
    });
});

describe('ax5.util.string Test', function () {
    it('ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format("ASP", "ASP.NET", 99); expect "ASP is dead, but ASP.NET is alive! ASP 99"', function () {
        var actual = ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format("ASP", "ASP.NET", 99);
        should(actual).be.equal("ASP is dead, but ASP.NET is alive! ASP 99");
    });
    it('ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format(["ASP", "ASP.NET", 99]); expect "ASP is dead, but ASP.NET is alive! ASP 99"', function () {
        var actual = ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format(["ASP", "ASP.NET", 99]);
        should(actual).be.equal("ASP is dead, but ASP.NET is alive! ASP 99");
    });
});
<commit_msg>update test code<commit_after>describe('ax5.util.left TEST', function () {
    var testCases = [
        {
            args: ['ab.da', '.'],
            expect: 'ab',
            explanation: 'ab.da, "."'
        },
        {
            args: ['abc"', "\""],
            expect: 'abc',
            explanation: 'abc", "\""'
        },
        {
            args: ['abc4', 4],
            expect: 'abc4',
            explanation: 'abc4, 4'
        },
        {
            args: ['abc4efg', 4.0],
            expect: 'abc4',
            explanation: 'abc4efg, 4.0'
        },
        {
            args: ['abc4e', 100],
            expect: 'abc4e',
            explanation: 'abc4e, 100'
        },
        {
            args: ['abc4', '4'],
            expect: 'abc',
            explanation: 'abc4, "4"'
        },
        {
            args: ['abc', '4'],
            expect: '',
            explanation: 'abc, "4"'
        },
        {
            args: ['abc', 0],
            expect: '',
            explanation: 'abc", 0'
        },
        {
            args: ['abc', '0'],
            expect: '',
            explanation: 'abc", "0"'
        },
        {
            args: ['abc'],
            expect: '',
            explanation: 'abc'
        },
        {
            args: ['abc', ''],
            expect: '',
            explanation: 'abc, ""'
        },
        {
            args: ['abc', null],
            expect: '',
            explanation: 'abc, null'
        },
        {
            args: ['abc', undefined],
            expect: '',
            explanation: 'abc", undefined'
        },
        {
            args: ['abc', true],
            expect: '',
            explanation: 'abc", true'
        },
        {
            args: ['abc', false],
            expect: '',
            explanation: 'abc", false'
        },
        {
            args: ['abc', new Object],
            expect: '',
            explanation: 'abc", new Object'
        },
        {
            args: ['abc', new Function],
            expect: '',
            explanation: 'abc", new Function]'
        },
        {
            args: ['abc', []],
            expect: '',
            explanation: 'abc", []]'
        },
        {
            args: ['abc', {}],
            expect: '',
            explanation: 'abc", {}]'
        }
    ];

    testCases.forEach(function (testCase) {
        it('ax5.util.left(' + testCase.explanation + ') expect ' + JSON.stringify(testCase.expect), function () {
            var actual = ax5.util.left.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });

});

describe('ax5.util.right TEST', function () {
    var testCases = [
        {
            args: ['abcd.efd', 3],
            expect: 'efd'
        },
        {
            args: ['abcd.efd', '.'],
            expect: 'efd'
        },
        {
            args: ['vi"veri"veniversum"vivus"vici', '\"'],
            expect: 'vici'
        },
        {
            args: ['vi veri veniversum vivus vici', 10],
            expect: 'vivus vici'
        },
        {
            args: ['abc', null],
            expect: '',
            explanation: 'abc, null'
        },
        {
            args: ['abc', undefined],
            expect: '',
            explanation: 'abc", undefined'
        },
        {
            args: ['coincidence', true],
            expect: '',
            explanation: 'coincidence, ""'
        },
        {
            args: ['coincidence', false],
            expect: '',
            explanation: 'coincidence, ""'
        },
        {
            args: ['object', new Object],
            expect: '',
            explanation: 'object, new Object'
        },
        {
            args: ['function', new Function],
            expect: '',
            explanation: 'function, new Function'
        },
        {
            args: ['abc', []],
            expect: '',
            explanation: 'abc", []]'
        },
        {
            args: ['abc', {}],
            expect: '',
            explanation: 'abc", {}]'
        }
    ];

    testCases.forEach(function (testCase) {
        it('ax5.util.right(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.right.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.camelCase Test', function () {
    var testCases = [
        {
            args: ['inner-width'],
            expect: 'innerWidth'
        },
        {
            args: ['innerWidth'],
            expect: 'innerWidth'
        },
        {
            args: ['-ms-border-radius'],
            expect: 'msBorderRadius'
        },
        {
            args: ['-webkit-border-radius'],
            expect: 'WebkitBorderRadius'
        },
        {
            args: ['-moz-border-radius'],
            expect: 'MozBorderRadius'
        },
        {
            args: ['-o-border-radius'],
            expect: 'OBorderRadius'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.camelCase(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.camelCase.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.snakeCase Test', function () {
    var testCases = [
        {
            args: ['inner-width'],
            expect: 'inner-width'
        },
        {
            args: ['camelCase'],
            expect: 'camel-case'
        },
        {
            args: ['camelCase'],
            expect: 'camel-case'
        },
        {
            args: ['MsBorderRadius'],
            expect: '-ms-border-radius'
        },
        {
            args: ['WebkitBorderRadius'],
            expect: '-webkit-border-radius'
        },
        {
            args: ['MozBorderRadius'],
            expect: '-moz-border-radius'
        },
        {
            args: ['OBorderRadius'],
            expect: '-o-border-radius'
        },
        {
            args: ['HELLOWORLD'],
            expect: '-h-e-l-l-o-w-o-r-l-d'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.snakeCase(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.snakeCase.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.setDigit Test', function () {
    var testCases = [
        {
            args: [1, 2],
            expect: '01'
        },
        {
            args: [10, 2, 0, 2],
            expect: '1010'
        },
        {
            args: [10, 8, 0, 8],
            expect: '00000012'
        },
        {
            args: [320, 8, 0, 16],
            expect: '00000140'
        },
        {
            args: [10, 6, "10"],
            expect: '1010101010'
        },
        {
            args: [10, 4, 0, 4],
            expect: '0022'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.setDigit(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.setDigit.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.times Test', function () {
    var testCases = [
        {
            args: [2016, 2],
            expect: '20162016'
        },
        {
            args: ["", 4],
            expect: ''
        },
        {
            args: [true, 3],
            expect: 'truetruetrue'
        },
        {
            args: [null, 4],
            expect: 'nullnullnullnull'
        }
    ];
    testCases.forEach(function (testCase) {
        it('ax5.util.times(' + testCase.args + ') expect ' + testCase.expect, function () {
            var actual = ax5.util.times.apply(this, testCase.args);

            actual.should.deepEqual(testCase.expect);
        });
    });
});

describe('ax5.util.escape Test', function () {
    it('ax5.util.escapeHtml("HTML string & "escape"") expect "HTML <span>string</span> & "escape""', function () {
        var actual = ax5.util.escapeHtml('HTML string & "escape"');
        should(actual).be.equal('HTML <span>string</span> & "escape"');
    });

    it('ax5.util.escapeHtml(undefined) expect undefined', function () {
        var actual = ax5.util.escapeHtml(undefined);
        should(actual).be.equal(undefined);
    });

    it('ax5.util.escapeHtml({ "foo": 1 }) expect { "foo": 1 }', function () {
        var actual = ax5.util.escapeHtml({"foo": 1});
        should(actual).be.deepEqual({"foo": 1});
    });

    it('ax5.util.unescapeHtml("HTML <span>string</span> & "escape"") expect "HTML string & "escape""', function () {
        var actual = ax5.util.unescapeHtml('HTML <span>string</span> & "escape"');
        should(actual).be.equal('HTML string & "escape"');
    });

    it('ax5.util.unescapeHtml(undefined) expect undefined', function () {
        var actual = ax5.util.unescapeHtml(undefined);
        should(actual).be.equal(undefined);
    });

    it('ax5.util.unescapeHtml({ "foo": 1 }) expect { "foo": 1 }', function () {
        var actual = ax5.util.unescapeHtml({"foo": 1});
        should(actual).be.deepEqual({"foo": 1});
    });
});

describe('ax5.util.string Test', function () {
    it('ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format("ASP", "ASP.NET", 99); expect "ASP is dead, but ASP.NET is alive! ASP 99"', function () {
        var actual = ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format("ASP", "ASP.NET", 99);
        should(actual).be.equal("ASP is dead, but ASP.NET is alive! ASP 99");
    });
    it('ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format(["ASP", "ASP.NET", 99]); expect "ASP is dead, but ASP.NET is alive! ASP 99"', function () {
        var actual = ax5.util.string("{0} is dead, but {1} is alive! {0} {2}").format(["ASP", "ASP.NET", 99]);
        should(actual).be.equal("ASP is dead, but ASP.NET is alive! ASP 99");
    });
});
<|endoftext|>