<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb.
import EventListener from 'react-event-listener';
import withStyles from '../styles/withStyles';
import { setRef } from '../utils/reactHelpers';

const ROWS_HEIGHT = 19;

export const styles = {
  /* Styles applied to the root element. */
  root: {
    position: 'relative', // because the shadow has position: 'absolute',
    width: '100%',
  },
  textarea: {
    width: '100%',
    height: '100%',
    resize: 'none',
    font: 'inherit',
    padding: 0,
    cursor: 'inherit',
    boxSizing: 'border-box',
    lineHeight: 'inherit',
    border: 'none',
    outline: 'none',
    background: 'transparent',
  },
  shadow: {
    // Overflow also needed to here to remove the extra row
    // added to textareas in Firefox.
    overflow: 'hidden',
    // Visibility needed to hide the extra text area on iPads
    visibility: 'hidden',
    position: 'absolute',
    height: 'auto',
    whiteSpace: 'pre-wrap',
  },
};

/**
 * @ignore - internal component.
 */
class Textarea extends React.Component {
  constructor(props) {
    super();
    this.isControlled = props.value != null;
    // <Input> expects the components it renders to respond to 'value'
    // so that it can check whether they are filled.
    this.value = props.value || props.defaultValue || '';
    this.state = {
      height: Number(props.rows) * ROWS_HEIGHT,
    };

    if (typeof window !== 'undefined') {
      this.handleResize = debounce(() => {
        this.syncHeightWithShadow();
      }, 166); // Corresponds to 10 frames at 60 Hz.
    }
  }

  componentDidMount() {
    this.syncHeightWithShadow();
  }

  componentDidUpdate() {
    this.syncHeightWithShadow();
  }

  componentWillUnmount() {
    this.handleResize.clear();
  }

  handleRefInput = ref => {
    this.inputRef = ref;

    setRef(this.props.textareaRef, ref);
  };

  handleRefSinglelineShadow = ref => {
    this.singlelineShadowRef = ref;
  };

  handleRefShadow = ref => {
    this.shadowRef = ref;
  };

  handleChange = event => {
    this.value = event.target.value;

    if (!this.isControlled) {
      // The component is not controlled, we need to update the shallow value.
      this.shadowRef.value = this.value;
      this.syncHeightWithShadow();
    }

    if (this.props.onChange) {
      this.props.onChange(event);
    }
  };

  syncHeightWithShadow() {
    const props = this.props;

    // Guarding for **broken** shallow rendering method that call componentDidMount
    // but doesn't handle refs correctly.
    // To remove once the shallow rendering has been fixed.
    if (!this.shadowRef) {
      return;
    }

    if (this.isControlled) {
      // The component is controlled, we need to update the shallow value.
      this.shadowRef.value = props.value == null ? '' : String(props.value);
    }

    const lineHeight = this.singlelineShadowRef.scrollHeight;
    let newHeight = this.shadowRef.scrollHeight;

    // Guarding for jsdom, where scrollHeight isn't present.
    // See https://github.com/tmpvar/jsdom/issues/1013
    if (newHeight === undefined) {
      return;
    }

    if (Number(props.rowsMax) >= Number(props.rows)) {
      newHeight = Math.min(Number(props.rowsMax) * lineHeight, newHeight);
    }

    newHeight = Math.max(newHeight, lineHeight);

    // Need a large enough different to update the height.
    // This prevents infinite rendering loop.
    if (Math.abs(this.state.height - newHeight) > 1) {
      this.setState({
        height: newHeight,
      });
    }
  }

  render() {
    const {
      classes,
      className,
      defaultValue,
      onChange,
      rows,
      rowsMax,
      style,
      textareaRef,
      value,
      ...other
    } = this.props;

    return (

        <EventListener target="window" onResize={this.handleResize} />
        <textarea
          aria-hidden="true"
          className={classnames(classes.textarea, classes.shadow)}
          readOnly
          ref={this.handleRefSinglelineShadow}
          rows="1"
          tabIndex={-1}
          value=""
        />
        <textarea
          aria-hidden="true"
          className={classnames(classes.textarea, classes.shadow)}
          defaultValue={defaultValue}
          readOnly
          ref={this.handleRefShadow}
          rows={rows}
          tabIndex={-1}
          value={value}
        />
        <textarea
          rows={rows}
          className={classnames(classes.textarea, className)}
          defaultValue={defaultValue}
          value={value}
          onChange={this.handleChange}
          ref={this.handleRefInput}
          style={{ height: this.state.height, ...style }}
          {...other}
        />

    );
  }
}

Textarea.propTypes = {
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css-api) below for more details.
   */
  classes: PropTypes.object.isRequired,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * @ignore
   */
  defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * @ignore
   */
  disabled: PropTypes.bool,
  /**
   * @ignore
   */
  onChange: PropTypes.func,
  /**
   * Number of rows to display when multiline option is set to true.
   */
  rows: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * Maximum number of rows to display when multiline option is set to true.
   */
  rowsMax: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * @ignore
   */
  style: PropTypes.object,
  /**
   * Use that property to pass a ref callback to the native textarea element.
   */
  textareaRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
  /**
   * @ignore
   */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

Textarea.defaultProps = {
  rows: 1,
};

export default withStyles(styles, { name: 'MuiPrivateTextarea' })(Textarea);
<commit_msg>[Textarea] Fix line height visibility issue on SSR (#14197)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import debounce from 'debounce'; // < 1kb payload overhead when lodash/debounce is > 3kb.
import EventListener from 'react-event-listener';
import withStyles from '../styles/withStyles';
import { setRef } from '../utils/reactHelpers';

const ROWS_HEIGHT = 19;

export const styles = {
  /* Styles applied to the root element. */
  root: {
    position: 'relative', // because the shadow has position: 'absolute',
    width: '100%',
  },
  textarea: {
    width: '100%',
    height: '100%',
    resize: 'none',
    font: 'inherit',
    padding: 0,
    cursor: 'inherit',
    boxSizing: 'border-box',
    lineHeight: 'inherit',
    border: 'none',
    outline: 'none',
    background: 'transparent',
  },
  shadow: {
    // Overflow also needed to here to remove the extra row
    // added to textareas in Firefox.
    overflow: 'hidden',
    // Visibility needed to hide the extra text area on iPads
    visibility: 'hidden',
    position: 'absolute',
    height: 'auto',
    whiteSpace: 'pre-wrap',
  },
};

/**
 * @ignore - internal component.
 */
class Textarea extends React.Component {
  constructor(props) {
    super();
    this.isControlled = props.value != null;
    // <Input> expects the components it renders to respond to 'value'
    // so that it can check whether they are filled.
    this.value = props.value || props.defaultValue || '';
    this.state = {
      height: Number(props.rows) * ROWS_HEIGHT,
    };

    if (typeof window !== 'undefined') {
      this.handleResize = debounce(() => {
        this.syncHeightWithShadow();
      }, 166); // Corresponds to 10 frames at 60 Hz.
    }
  }

  componentDidMount() {
    this.syncHeightWithShadow();
  }

  componentDidUpdate() {
    this.syncHeightWithShadow();
  }

  componentWillUnmount() {
    this.handleResize.clear();
  }

  handleRefInput = ref => {
    this.inputRef = ref;

    setRef(this.props.textareaRef, ref);
  };

  handleRefSinglelineShadow = ref => {
    this.singlelineShadowRef = ref;
  };

  handleRefShadow = ref => {
    this.shadowRef = ref;
  };

  handleChange = event => {
    this.value = event.target.value;

    if (!this.isControlled) {
      // The component is not controlled, we need to update the shallow value.
      this.shadowRef.value = this.value;
      this.syncHeightWithShadow();
    }

    if (this.props.onChange) {
      this.props.onChange(event);
    }
  };

  syncHeightWithShadow() {
    const props = this.props;

    // Guarding for **broken** shallow rendering method that call componentDidMount
    // but doesn't handle refs correctly.
    // To remove once the shallow rendering has been fixed.
    if (!this.shadowRef) {
      return;
    }

    if (this.isControlled) {
      // The component is controlled, we need to update the shallow value.
      this.shadowRef.value = props.value == null ? '' : String(props.value);
    }

    let lineHeight = this.singlelineShadowRef.scrollHeight;
    // The Textarea might not be visible (p.ex: display: none).
    // In this case, the layout values read from the DOM will be 0.
    lineHeight = lineHeight === 0 ? ROWS_HEIGHT : lineHeight;

    let newHeight = this.shadowRef.scrollHeight;

    // Guarding for jsdom, where scrollHeight isn't present.
    // See https://github.com/tmpvar/jsdom/issues/1013
    if (newHeight === undefined) {
      return;
    }

    if (Number(props.rowsMax) >= Number(props.rows)) {
      newHeight = Math.min(Number(props.rowsMax) * lineHeight, newHeight);
    }

    newHeight = Math.max(newHeight, lineHeight);

    // Need a large enough different to update the height.
    // This prevents infinite rendering loop.
    if (Math.abs(this.state.height - newHeight) > 1) {
      this.setState({
        height: newHeight,
      });
    }
  }

  render() {
    const {
      classes,
      className,
      defaultValue,
      onChange,
      rows,
      rowsMax,
      style,
      textareaRef,
      value,
      ...other
    } = this.props;

    return (

        <EventListener target="window" onResize={this.handleResize} />
        <textarea
          aria-hidden="true"
          className={classnames(classes.textarea, classes.shadow)}
          readOnly
          ref={this.handleRefSinglelineShadow}
          rows="1"
          tabIndex={-1}
          value=""
        />
        <textarea
          aria-hidden="true"
          className={classnames(classes.textarea, classes.shadow)}
          defaultValue={defaultValue}
          readOnly
          ref={this.handleRefShadow}
          rows={rows}
          tabIndex={-1}
          value={value}
        />
        <textarea
          rows={rows}
          className={classnames(classes.textarea, className)}
          defaultValue={defaultValue}
          value={value}
          onChange={this.handleChange}
          ref={this.handleRefInput}
          style={{ height: this.state.height, ...style }}
          {...other}
        />

    );
  }
}

Textarea.propTypes = {
  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css-api) below for more details.
   */
  classes: PropTypes.object.isRequired,
  /**
   * @ignore
   */
  className: PropTypes.string,
  /**
   * @ignore
   */
  defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * @ignore
   */
  disabled: PropTypes.bool,
  /**
   * @ignore
   */
  onChange: PropTypes.func,
  /**
   * Number of rows to display when multiline option is set to true.
   */
  rows: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * Maximum number of rows to display when multiline option is set to true.
   */
  rowsMax: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  /**
   * @ignore
   */
  style: PropTypes.object,
  /**
   * Use that property to pass a ref callback to the native textarea element.
   */
  textareaRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
  /**
   * @ignore
   */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

Textarea.defaultProps = {
  rows: 1,
};

export default withStyles(styles, { name: 'MuiPrivateTextarea' })(Textarea);
<|endoftext|>


<commit_before>'use strict';
var yeoman = require('yeoman-generator'),
     chalk = require('chalk'),
     yosay = require('yosay'),
         _ = require('lodash'),
         s = require('underscore.string'),
        fs = require('fs'),
    prompt = require('../../lib/option-or-prompt.js');

var modulesSource = 'src/';

module.exports = yeoman.generators.Base.extend({

  _prompt: prompt,

  // The name `constructor` is important here
  constructor: function () {
    // Calling the super constructor is important so our generator is correctly set up
    yeoman.Base.apply(this, arguments);

    // This method adds support for a `--vulgarcli` flag
    this.option('vulgarcli', { type: Boolean, defaults: false, hide: true });

    // This method adds support for a `--dest` flag
    // This allows a destination to be passed in at the command line
    // Note that this path will be the final path FROM the root of the
    // project where the cli should be run
    this.option('dest', { type: String });

    // This method adds support for a `--module` flag
    // This allows the module name to be passed in from the command line
    this.option('module', { type: String });

    // This method adds support for a `--name` flag
    // This allows the name of the routable Angular component(s) to be
    // passed in at the command line
    this.option('name', { type: String });

    // This method adds support for a `--path` flag
    // This allows a url to be passed in at which the children
    // components of the routable Angular component can be reached
    this.option('path', { type: String });
  },

  askForModuleDirectory: function (cb) {

    var done = cb || this.async();

    var prompts = [{
      type: 'list',
      name: 'module',
      message: 'Where would you like to create this component?',
      // Define some default choices
      choices: [{
        value: '../',
        name: '../'
      },
      {
        value: '',
        name: 'Select this directory'
      }]
    }];

    // Add module choices
    if (fs.existsSync(modulesSource)) {

      fs.readdirSync(modulesSource).forEach(function (folder) {
        var stat = fs.statSync(modulesSource + '/' + folder);

        if (stat.isDirectory()
              // Exclude the `assets` and `sass` directories
              && folder !== 'assets'
              && folder !== 'sass') {

          prompts[0].choices.push({
            value: folder,
            name: folder
          });
        }
      });
    }

    // Use custom prompt function which skips the prompt if
    // an option has been passed in
    this._prompt(prompts, function(props) {
      // this.props = props;
      // To access props later use this.props.someOption;

      modulesSource = modulesSource + '/' + props.module;

      // Continue to queue the user for a location
      // until no value is returned, which equates
      // to the `Select this directory` choice
      if(props.module) {
        this.askForModuleDirectory(done);
      } else {
        done();
      }
    }.bind(this));
  },

  askForModuleName: function () {

    var done = this.async();

    var prompts = [{
      type: 'input',
      name: 'name',
      message: 'What would you like to name this component?',
      default: ''
    }];

    // Use custom prompt function which skips the prompt if
    // an option has been passed in
    this._prompt(prompts, function(props) {
      this.props = props;
      // To access props later use this.props.someOption;

      this.name = this.props.name || 'ng-component';

      this.slugifiedName = s(this.name).humanize().slugify().value();
      this.classifiedName = s(this.slugifiedName).classify().value();
      this.humanizedName = s(this.slugifiedName).humanize().value();
      this.camelizedName = s(this.slugifiedName).camelize().value();
      this.decapitalizedName = s(this.name).humanize().decapitalize().value();

      if (this.options.dest) {

        this.destination = this.options.dest;
      } else {

        this.destination = modulesSource + '/' + this.decapitalizedName + '/';
      }

      done();
    }.bind(this));
  },

  writing: function () {

    // For dummy test
    this.fs.copy(
      this.templatePath('dummyfile.txt'),
      this.destinationPath(modulesSource + '/' +'dummyfile.txt')
    );

    //** Generate `Angular` component
    this.fs.copyTpl(
      this.templatePath('_.component.ts'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.ts'), {

        classifiedName: this.classifiedName,
        humanizedName: this.humanizedName,
        slugifiedName: this.slugifiedName
      }
    );

    //** Generate `Angular` component unit test
    this.fs.copyTpl(
      this.templatePath('_.component.spec.ts'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.spec.ts'), {

        classifiedName: this.classifiedName,
        slugifiedName: this.slugifiedName
      }
    );

    //** Generate `Angular` component template
    this.fs.copyTpl(
      this.templatePath('_.component.html'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.html'), {

        classifiedName: this.classifiedName
      }
    );

    //** Generate `Angular` component styles
    this.fs.copy(
      this.templatePath('_.component.scss'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.scss')
    );
  },

  end: function() {

    if(!this.options.vulgarcli) {
      // Terminate process if run from console
      process.exit(0);
    } else if(this.options.vulgarcli === true) {
      // `return 0` to let `vulgar-cli` know everything went okay on our end
      return 0;
    }
  }
});
<commit_msg>fix(ng-component): fix destination path & change default name value<commit_after>'use strict';
var yeoman = require('yeoman-generator'),
     chalk = require('chalk'),
     yosay = require('yosay'),
         _ = require('lodash'),
         s = require('underscore.string'),
        fs = require('fs'),
    prompt = require('../../lib/option-or-prompt.js');

var modulesSource = 'src/';

module.exports = yeoman.generators.Base.extend({

  _prompt: prompt,

  // The name `constructor` is important here
  constructor: function () {
    // Calling the super constructor is important so our generator is correctly set up
    yeoman.Base.apply(this, arguments);

    // This method adds support for a `--vulgarcli` flag
    this.option('vulgarcli', { type: Boolean, defaults: false, hide: true });

    // This method adds support for a `--dest` flag
    // This allows a destination to be passed in at the command line
    // Note that this path will be the final path FROM the root of the
    // project where the cli should be run
    this.option('dest', { type: String });

    // This method adds support for a `--module` flag
    // This allows the module name to be passed in from the command line
    this.option('module', { type: String });

    // This method adds support for a `--name` flag
    // This allows the name of the routable Angular component(s) to be
    // passed in at the command line
    this.option('name', { type: String });

    // This method adds support for a `--path` flag
    // This allows a url to be passed in at which the children
    // components of the routable Angular component can be reached
    this.option('path', { type: String });
  },

  askForModuleDirectory: function (cb) {

    var done = cb || this.async();

    var prompts = [{
      type: 'list',
      name: 'module',
      message: 'Where would you like to create this component?',
      // Define some default choices
      choices: [{
        value: '../',
        name: '../'
      },
      {
        value: '',
        name: 'Select this directory'
      }]
    }];

    // Add module choices
    if (fs.existsSync(modulesSource)) {

      fs.readdirSync(modulesSource).forEach(function (folder) {
        var stat = fs.statSync(modulesSource + '/' + folder);

        if (stat.isDirectory()
              // Exclude the `assets` and `sass` directories
              && folder !== 'assets'
              && folder !== 'sass') {

          prompts[0].choices.push({
            value: folder,
            name: folder
          });
        }
      });
    }

    // Use custom prompt function which skips the prompt if
    // an option has been passed in
    this._prompt(prompts, function(props) {
      // this.props = props;
      // To access props later use this.props.someOption;

      modulesSource = modulesSource + '/' + props.module;

      // Continue to queue the user for a location
      // until no value is returned, which equates
      // to the `Select this directory` choice
      if(props.module) {
        this.askForModuleDirectory(done);
      } else {
        done();
      }
    }.bind(this));
  },

  askForModuleName: function () {

    var done = this.async();

    var prompts = [{
      type: 'input',
      name: 'name',
      message: 'What would you like to name this component?',
      default: 'ng-component'
    }];

    // Use custom prompt function which skips the prompt if
    // an option has been passed in
    this._prompt(prompts, function(props) {
      this.props = props;
      // To access props later use this.props.someOption;

      this.name = this.props.name || 'ng-component';

      this.slugifiedName = s(this.name).humanize().slugify().value();
      this.classifiedName = s(this.slugifiedName).classify().value();
      this.humanizedName = s(this.slugifiedName).humanize().value();
      this.camelizedName = s(this.slugifiedName).camelize().value();
      this.decapitalizedName = s(this.name).humanize().decapitalize().value();

      if (this.options.dest) {

        this.destination = this.options.dest;
      } else {

        this.destination = modulesSource + '/' + this.slugifiedName + '/';
      }

      done();
    }.bind(this));
  },

  writing: function () {

    // For dummy test
    this.fs.copy(
      this.templatePath('dummyfile.txt'),
      this.destinationPath(modulesSource + '/' +'dummyfile.txt')
    );

    //** Generate `Angular` component
    this.fs.copyTpl(
      this.templatePath('_.component.ts'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.ts'), {

        classifiedName: this.classifiedName,
        humanizedName: this.humanizedName,
        slugifiedName: this.slugifiedName
      }
    );

    //** Generate `Angular` component unit test
    this.fs.copyTpl(
      this.templatePath('_.component.spec.ts'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.spec.ts'), {

        classifiedName: this.classifiedName,
        slugifiedName: this.slugifiedName
      }
    );

    //** Generate `Angular` component template
    this.fs.copyTpl(
      this.templatePath('_.component.html'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.html'), {

        classifiedName: this.classifiedName
      }
    );

    //** Generate `Angular` component styles
    this.fs.copy(
      this.templatePath('_.component.scss'),
      this.destinationPath(this.destination + this.slugifiedName + '.component.scss')
    );
  },

  end: function() {

    if(!this.options.vulgarcli) {
      // Terminate process if run from console
      process.exit(0);
    } else if(this.options.vulgarcli === true) {
      // `return 0` to let `vulgar-cli` know everything went okay on our end
      return 0;
    }
  }
});
<|endoftext|>


<commit_before>var request = require('request'),
    config = require('../config'),
    urlUtil = require('../util/url'),
    renderer = require('../renderer/position'),
    Canvas = require('canvas');

/**
 * Position render sizes and default company logo
 */
var DEFAULT_ORG_LOGO = 'https://www.workshape.io/images/assets/anonymous-org.png',
    POSITION_SIZES = {
        small   : [ 300, 400 ],
        medium  : [ 600, 800 ],
        large   : [ 1200, 1600 ],
        default : [ 600, 800 ]
    };

/**
 * Draw requested position by ID
 *
 * @param {Object} req Request
 * @param {Object} res Response
 * @param {Function} next Callback
 */
module.exports = function (req, res, next) {
    var sizeName = req.params.size || 'default',
        positionId = req.params.positionId,
        endpoint = config.WS_URL + 'api/1/positions/' + positionId + '/public',
        size = POSITION_SIZES[sizeName];

    // Validate size
    if (!size) {
        return res.status(400).send('Invalid `size`');
    }

    // Request position data
    request.get(endpoint, function (err, response, body) {

        // Handle request errors
        if (err) {
            return next(err);
        } else if (response.statusCode !== 200) {
            return res.sendStatus(response.statusCode);
        }

        var data = JSON.parse(body),
            logoUrl = urlUtil.assetUrl(data.organisation.logo, DEFAULT_ORG_LOGO);

        // Request comoany logo asset
        request.get({
            url      : logoUrl,
            encoding : null
        }, function (err, response, body) {

            // Handle request errors
            if (err) {
                return next(err);
            } else if (response.statusCode !== 200) {
                return res.sendStatus(response.statusCode);
            }

            // Create canvas
            var canvas = new Canvas(size[0], size[1]),
                ctx = canvas.getContext('2d'),
                options = {
                    size         : size,
                    organisation : {
                        name : data.organisation.name,
                        logo : body
                    },
                    position     : {
                        title  : data.title,
                        values : Object.keys(data.time_load).map(function (key) {
                            return data.time_load[key].value;
                        }),
                        skills : data.skills.map(function (s) {
                            return s.name;
                        }),
                        remote   : data.remote_ok,
                        contract : data.contract,
                        level    : data.level.name,
                        visa     : data.visa || false
                    }
                };
            // Render context
            renderer(ctx, size, options);

            // Stream rendered file contents
            canvas.toBuffer(function (err, buf) {

                if (err) {
                    return next(err);
                }

                res.set('Content-Type', 'image/png');
                res.status(200).send(buf);
            });

        });
    });
};<commit_msg>Limit skill tags<commit_after>var request = require('request'),
    config = require('../config'),
    urlUtil = require('../util/url'),
    renderer = require('../renderer/position'),
    Canvas = require('canvas');

/**
 * Position render sizes and default company logo
 */
var DEFAULT_ORG_LOGO = 'https://www.workshape.io/images/assets/anonymous-org.png',
    POSITION_SIZES = {
        small   : [ 300, 400 ],
        medium  : [ 600, 800 ],
        large   : [ 1200, 1600 ],
        default : [ 600, 800 ]
    };

/**
 * Draw requested position by ID
 *
 * @param {Object} req Request
 * @param {Object} res Response
 * @param {Function} next Callback
 */
module.exports = function (req, res, next) {
    var sizeName = req.params.size || 'default',
        positionId = req.params.positionId,
        endpoint = config.WS_URL + 'api/1/positions/' + positionId + '/public',
        size = POSITION_SIZES[sizeName];

    // Validate size
    if (!size) {
        return res.status(400).send('Invalid `size`');
    }

    // Request position data
    request.get(endpoint, function (err, response, body) {

        // Handle request errors
        if (err) {
            return next(err);
        } else if (response.statusCode !== 200) {
            return res.sendStatus(response.statusCode);
        }

        var data = JSON.parse(body),
            logoUrl = urlUtil.assetUrl(data.organisation.logo, DEFAULT_ORG_LOGO);

        // Request comoany logo asset
        request.get({
            url      : logoUrl,
            encoding : null
        }, function (err, response, body) {

            // Handle request errors
            if (err) {
                return next(err);
            } else if (response.statusCode !== 200) {
                return res.sendStatus(response.statusCode);
            }

            // Create canvas
            var canvas = new Canvas(size[0], size[1]),
                ctx = canvas.getContext('2d'),
                options = {
                    size         : size,
                    organisation : {
                        name : data.organisation.name,
                        logo : body
                    },
                    position     : {
                        title  : data.title,
                        values : Object.keys(data.time_load).map(function (key) {
                            return data.time_load[key].value;
                        }),
                        skills : data.skills.map(function (s) {
                            return s.name;
                        }).slice(0, 5),
                        remote   : data.remote_ok,
                        contract : data.contract,
                        level    : data.level.name,
                        visa     : data.visa || false
                    }
                };
            // Render context
            renderer(ctx, size, options);

            // Stream rendered file contents
            canvas.toBuffer(function (err, buf) {

                if (err) {
                    return next(err);
                }

                res.set('Content-Type', 'image/png');
                res.status(200).send(buf);
            });

        });
    });
};<|endoftext|>


<commit_before>#!/usr/bin/env node
/*
 * scrape_champ_ids.js -- scrape Mobafire for champion name -> ID mapping
 *
 * Copyright (C) 2017 Dan Poggi
 *
 * This software may be modified and distributed under the terms
 * of the MIT license. See the LICENSE file for details.
 */

const http = require("http");

http.get("http://www.mobafire.com/league-of-legends/champions", (response) => {
  const { statusCode } = response;
  const contentType = response.headers["content-type"];

  if (statusCode !== 200) {
    throw new Error(`HTTP request failed with status ${statusCode}`);
  } else if (!/text\/html/.test(contentType)) {
    throw new Error(`HTTP request failed with invalid Content-Type ${contentType}`);
  }

  let html = "";

  response.setEncoding("utf8");

  response.on("data", (chunk) => { html += chunk; });

  response.on("end", () => {
    const champions = {};
    const re = /\/champion\/([a-z\-]*)-(\d{1,3})/g;

    for (let match = re.exec(html); match != null; match = re.exec(html)) {
      const name = match[1].replace("-", " ");
      const id = Number.parseInt(match[2]);

      champions[name] = id;
    }

    process.stdout.write(JSON.stringify(champions, undefined, 2));

    if (process.stdout.isTTY) {
      console.error("\n\nSuccess! Pipe to scripts/pbcopy.sh to copy champion IDs as JSON.");
    } else {
      console.error("Success! Check the clipboard!");
    }
  });
});
<commit_msg>Mobafire uses https: now :)<commit_after>#!/usr/bin/env node
/*
 * scrape_champ_ids.js -- scrape Mobafire for champion name -> ID mapping
 *
 * Copyright (C) 2017 Dan Poggi
 *
 * This software may be modified and distributed under the terms
 * of the MIT license. See the LICENSE file for details.
 */

const https = require("https");

https.get("https://www.mobafire.com/league-of-legends/champions", (response) => {
  const { statusCode } = response;
  const contentType = response.headers["content-type"];

  if (statusCode !== 200) {
    throw new Error(`HTTP request failed with status ${statusCode}`);
  } else if (!/text\/html/.test(contentType)) {
    throw new Error(`HTTP request failed with invalid Content-Type ${contentType}`);
  }

  let html = "";

  response.setEncoding("utf8");

  response.on("data", (chunk) => { html += chunk; });

  response.on("end", () => {
    const champions = {};
    const re = /\/champion\/([a-z\-]*)-(\d{1,3})/g;

    for (let match = re.exec(html); match != null; match = re.exec(html)) {
      const name = match[1].replace("-", " ");
      const id = Number.parseInt(match[2]);

      champions[name] = id;
    }

    process.stdout.write(JSON.stringify(champions, undefined, 2));

    if (process.stdout.isTTY) {
      console.error("\n\nSuccess! Pipe to scripts/pbcopy.sh to copy champion IDs as JSON.");
    } else {
      console.error("Success! Check the clipboard!");
    }
  });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');

// Utils
const { models: { getValuePrimaryKey } } = require('strapi-utils');

module.exports = {
  getModel: function (model, plugin) {
    return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
  },

  update: async function (params) {
    const virtualFields = [];
    const response = await this
      .findOne({
        [this.primaryKey]: params[this.primaryKey] || params.id
      })
      .populate(this.associations.map(x => x.alias).join(' '))
      .lean();

    // Only update fields which are on this document.
    const values = params.parseRelationships === false ? params.values : Object.keys(JSON.parse(JSON.stringify(params.values))).reduce((acc, current) => {
      const association = this.associations.find(x => x.alias === current);
      const details = this._attributes[current];

      if (_.get(this._attributes, `${current}.isVirtual`) !== true && _.isUndefined(association)) {
        acc[current] = params.values[current];
      } else {
        switch (association.nature) {
          case 'oneWay':
            acc[current] = _.get(params.values[current], this.primaryKey, params.values[current]) || null;

            break;
          case 'oneToOne':
            if (response[current] !== params.values[current]) {
              const value = _.isNull(params.values[current]) ? response[current] : params.values;
              const recordId = _.isNull(params.values[current]) ? getValuePrimaryKey(value, this.primaryKey) : value[current];

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Remove relation in the user side.
              virtualFields.push(
                model
                  .findOne({ [model.primaryKey]: value[current] })
                  .populate(details.via)
                  .then(record => {
                    if (record && _.isObject(record[details.via]) && record._id.toString() !== record[details.via][current].toString()) {
                      return module.exports.update.call(this, {
                        id: getValuePrimaryKey(record[details.via], model.primaryKey),
                        values: {
                          [current]: null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
                  .then(() => {
                    return module.exports.update.call(model, {
                      id: getValuePrimaryKey(response[current] || {}, this.primaryKey) || value[current],
                      values: {
                        [details.via]: null
                      },
                      parseRelationships: false
                    });
                  })
                  .then(() => {
                    if (!_.isNull(params.values[current])) {
                      return module.exports.update.call(model, {
                        id: recordId,
                        values: {
                          [details.via]: getValuePrimaryKey(params, this.primaryKey) || null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
              );

              acc[current] = _.isNull(params.values[current]) ? null : value[current];
            }

            break;
          case 'oneToMany':
          case 'manyToOne':
          case 'manyToMany':
            if (association.nature === 'manyToMany' && details.dominant === true) {
              acc[current] = params.values[current];
            } else if (response[current] && _.isArray(response[current]) && current !== 'id') {
              // Records to add in the relation.
              const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
                a[this.primaryKey].toString() === b[this.primaryKey].toString()
              );

              // Records to remove in the relation.
              const toRemove = _.differenceWith(response[current], params.values[current], (a, b) =>
                a[this.primaryKey].toString() === b[this.primaryKey].toString()
              )
                .filter(x => toAdd.find(y => x.id === y.id) === undefined);

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Push the work into the flow process.
              toAdd.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = (value[details.via] || [])
                    .concat([(params.values[this.primaryKey] || params[this.primaryKey])])
                    .filter(x => {
                      return x !== null && x !== undefined;
                    });
                } else {
                  value[details.via] = getValuePrimaryKey(params, this.primaryKey);
                }

                virtualFields.push(
                  module.exports.addRelation.call(model, {
                    id: getValuePrimaryKey(value, this.primaryKey),
                    values: _.pick(value, [this.primaryKey, details.via]),
                    foreignKey: current
                  })
                );
              });

              toRemove.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = value[details.via].filter(x => _.toString(x) !== _.toString(params.values[this.primaryKey] || params[this.primaryKey]));
                } else {
                  value[details.via] = null;
                }

                virtualFields.push(
                  module.exports.removeRelation.call(model, {
                    id: getValuePrimaryKey(value, this.primaryKey),
                    values: _.pick(value, [this.primaryKey, details.via]),
                    foreignKey: current
                  })
                );
              });
            } else if (_.get(this._attributes, `${current}.isVirtual`) !== true) {
              acc[current] = params.values[current];
            }

            break;
          case 'manyMorphToMany':
          case 'manyMorphToOne':
            // Update the relational array.
            acc[current] = params.values[current].map(obj => {
              const globalId = obj.source && obj.source !== 'content-manager' ?
                strapi.plugins[obj.source].models[_.toLower(obj.ref)].globalId:
                strapi.models[_.toLower(obj.ref)].globalId;

              // Define the object stored in database.
              // The shape is this object is defined by the strapi-hook-mongoose connector.
              return {
                ref: obj.refId,
                kind: globalId,
                [association.filter]: obj.field
              };
            });
            break;
          case 'oneToManyMorph':
          case 'manyToManyMorph': {
            const transformToArrayID = (array) => {
              if (_.isArray(array)) {
                return array.map(value => {
                  if (_.isPlainObject(value)) {
                    return getValuePrimaryKey(value, this.primaryKey);
                  }

                  return value;
                });
              }

              if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) {
                return _.isEmpty(array) ? [] : transformToArrayID([array]);
              }

              return [];
            };

            // Compare array of ID to find deleted files.
            const currentValue = transformToArrayID(response[current]).map(id => id.toString());
            const storedValue = transformToArrayID(params.values[current]).map(id => id.toString());

            const toAdd = _.difference(storedValue, currentValue);
            const toRemove = _.difference(currentValue, storedValue);

            const model = module.exports.getModel(details.model || details.collection, details.plugin);

            // Remove relations in the other side.
            toAdd.forEach(id => {
              virtualFields.push(
                module.exports.addRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });

            // Remove relations in the other side.
            toRemove.forEach(id => {
              virtualFields.push(
                module.exports.removeRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });
            break;
          }
          case 'oneMorphToOne':
          case 'oneMorphToMany':
            break;
          default:
        }
      }

      return acc;
    }, {});

    virtualFields.push(
      this
        .update({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        }, values, {
          strict: false
        })
    );

    // Update virtuals fields.
    await Promise.all(virtualFields);

    return await this
      .findOne({
        [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
      })
      .populate(this.associations.map(x => x.alias).join(' '));
  },

  addRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  removeRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  addRelationMorph: async function (params) {
    /*
      TODO:
      Test this part because it has been coded during the development of the upload feature.
      However the upload doesn't need this method. It only uses the `removeRelationMorph`.
    */

    let entry = (
      await this
        .findOne({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        })
    );

    if (entry) {
      entry = entry.toJSON();
    }

    const value = [];

    // Retrieve association.
    const association = this.associations.find(association => association.alias === params.alias);

    if (!association) {
      throw Error(`Impossible to create relationship with ${params.ref} (${params.refId})`);
    }

    // Resolve if the association is already existing.
    const isExisting = value.find(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return true;
      }

      return false;
    });

    // Avoid duplicate.
    if (isExisting) {
      return Promise.resolve();
    }

    // Push new relation to the association array.
    value.push({
      ref: params.ref,
      refId: params.refId,
      kind: params.ref,
      field: params.field
    });

    entry[params.alias] = value;

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  },

  removeRelationMorph: async function (params) {
    const entry = await this.findOne({
      [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
    });

    // Filter the association array and remove the association.
    entry[params.alias] = entry[params.alias].filter(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return false;
      }

      return true;
    });

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  }
};
<commit_msg>Fix manyToMany relation when only sending ids<commit_after>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');

// Utils
const { models: { getValuePrimaryKey } } = require('strapi-utils');

module.exports = {
  getModel: function (model, plugin) {
    return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
  },

  update: async function (params) {
    const virtualFields = [];
    const response = await this
      .findOne({
        [this.primaryKey]: params[this.primaryKey] || params.id
      })
      .populate(this.associations.map(x => x.alias).join(' '))
      .lean();

    // Only update fields which are on this document.
    const values = params.parseRelationships === false ? params.values : Object.keys(JSON.parse(JSON.stringify(params.values))).reduce((acc, current) => {
      const association = this.associations.find(x => x.alias === current);
      const details = this._attributes[current];

      if (_.get(this._attributes, `${current}.isVirtual`) !== true && _.isUndefined(association)) {
        acc[current] = params.values[current];
      } else {
        switch (association.nature) {
          case 'oneWay':
            acc[current] = _.get(params.values[current], this.primaryKey, params.values[current]) || null;

            break;
          case 'oneToOne':
            if (response[current] !== params.values[current]) {
              const value = _.isNull(params.values[current]) ? response[current] : params.values;
              const recordId = _.isNull(params.values[current]) ? getValuePrimaryKey(value, this.primaryKey) : value[current];

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Remove relation in the user side.
              virtualFields.push(
                model
                  .findOne({ [model.primaryKey]: value[current] })
                  .populate(details.via)
                  .then(record => {
                    if (record && _.isObject(record[details.via]) && record._id.toString() !== record[details.via][current].toString()) {
                      return module.exports.update.call(this, {
                        id: getValuePrimaryKey(record[details.via], model.primaryKey),
                        values: {
                          [current]: null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
                  .then(() => {
                    return module.exports.update.call(model, {
                      id: getValuePrimaryKey(response[current] || {}, this.primaryKey) || value[current],
                      values: {
                        [details.via]: null
                      },
                      parseRelationships: false
                    });
                  })
                  .then(() => {
                    if (!_.isNull(params.values[current])) {
                      return module.exports.update.call(model, {
                        id: recordId,
                        values: {
                          [details.via]: getValuePrimaryKey(params, this.primaryKey) || null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
              );

              acc[current] = _.isNull(params.values[current]) ? null : value[current];
            }

            break;
          case 'oneToMany':
          case 'manyToOne':
          case 'manyToMany':
            if (association.nature === 'manyToMany' && details.dominant === true) {
              acc[current] = params.values[current];
            } else if (response[current] && _.isArray(response[current]) && current !== 'id') {
              // Records to add in the relation.
              const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
                (a[this.primaryKey] || a).toString() === (b[this.primaryKey] || b).toString()
              );

              // Records to remove in the relation.
              const toRemove = _.differenceWith(response[current], params.values[current], (a, b) =>
                (a[this.primaryKey] || a).toString() === (b[this.primaryKey] || b).toString()
              )
                .filter(x => toAdd.find(y => x.id === y.id) === undefined);

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Push the work into the flow process.
              toAdd.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = (value[details.via] || [])
                    .concat([(params.values[this.primaryKey] || params[this.primaryKey])])
                    .filter(x => {
                      return x !== null && x !== undefined;
                    });
                } else {
                  value[details.via] = getValuePrimaryKey(params, this.primaryKey);
                }

                virtualFields.push(
                  module.exports.addRelation.call(model, {
                    id: getValuePrimaryKey(value, this.primaryKey),
                    values: _.pick(value, [this.primaryKey, details.via]),
                    foreignKey: current
                  })
                );
              });

              toRemove.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = value[details.via].filter(x => _.toString(x) !== _.toString(params.values[this.primaryKey] || params[this.primaryKey]));
                } else {
                  value[details.via] = null;
                }

                virtualFields.push(
                  module.exports.removeRelation.call(model, {
                    id: getValuePrimaryKey(value, this.primaryKey),
                    values: _.pick(value, [this.primaryKey, details.via]),
                    foreignKey: current
                  })
                );
              });
            } else if (_.get(this._attributes, `${current}.isVirtual`) !== true) {
              acc[current] = params.values[current];
            }

            break;
          case 'manyMorphToMany':
          case 'manyMorphToOne':
            // Update the relational array.
            acc[current] = params.values[current].map(obj => {
              const globalId = obj.source && obj.source !== 'content-manager' ?
                strapi.plugins[obj.source].models[_.toLower(obj.ref)].globalId:
                strapi.models[_.toLower(obj.ref)].globalId;

              // Define the object stored in database.
              // The shape is this object is defined by the strapi-hook-mongoose connector.
              return {
                ref: obj.refId,
                kind: globalId,
                [association.filter]: obj.field
              };
            });
            break;
          case 'oneToManyMorph':
          case 'manyToManyMorph': {
            const transformToArrayID = (array) => {
              if (_.isArray(array)) {
                return array.map(value => {
                  if (_.isPlainObject(value)) {
                    return getValuePrimaryKey(value, this.primaryKey);
                  }

                  return value;
                });
              }

              if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) {
                return _.isEmpty(array) ? [] : transformToArrayID([array]);
              }

              return [];
            };

            // Compare array of ID to find deleted files.
            const currentValue = transformToArrayID(response[current]).map(id => id.toString());
            const storedValue = transformToArrayID(params.values[current]).map(id => id.toString());

            const toAdd = _.difference(storedValue, currentValue);
            const toRemove = _.difference(currentValue, storedValue);

            const model = module.exports.getModel(details.model || details.collection, details.plugin);

            // Remove relations in the other side.
            toAdd.forEach(id => {
              virtualFields.push(
                module.exports.addRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });

            // Remove relations in the other side.
            toRemove.forEach(id => {
              virtualFields.push(
                module.exports.removeRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });
            break;
          }
          case 'oneMorphToOne':
          case 'oneMorphToMany':
            break;
          default:
        }
      }

      return acc;
    }, {});

    virtualFields.push(
      this
        .update({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        }, values, {
          strict: false
        })
    );

    // Update virtuals fields.
    await Promise.all(virtualFields);

    return await this
      .findOne({
        [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
      })
      .populate(this.associations.map(x => x.alias).join(' '));
  },

  addRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  removeRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  addRelationMorph: async function (params) {
    /*
      TODO:
      Test this part because it has been coded during the development of the upload feature.
      However the upload doesn't need this method. It only uses the `removeRelationMorph`.
    */

    let entry = (
      await this
        .findOne({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        })
    );

    if (entry) {
      entry = entry.toJSON();
    }

    const value = [];

    // Retrieve association.
    const association = this.associations.find(association => association.alias === params.alias);

    if (!association) {
      throw Error(`Impossible to create relationship with ${params.ref} (${params.refId})`);
    }

    // Resolve if the association is already existing.
    const isExisting = value.find(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return true;
      }

      return false;
    });

    // Avoid duplicate.
    if (isExisting) {
      return Promise.resolve();
    }

    // Push new relation to the association array.
    value.push({
      ref: params.ref,
      refId: params.refId,
      kind: params.ref,
      field: params.field
    });

    entry[params.alias] = value;

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  },

  removeRelationMorph: async function (params) {
    const entry = await this.findOne({
      [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
    });

    // Filter the association array and remove the association.
    entry[params.alias] = entry[params.alias].filter(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return false;
      }

      return true;
    });

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  }
};
<|endoftext|>


<commit_before>Vps.EyeCandy.List.Plugins.ActiveChanger.NextPreviousLinks = Ext.extend(Vps.EyeCandy.List.Plugins.Abstract, {
    init: function() {

        this.previousLink = this.list.el.createChild({
            tag: 'a',
            cls: 'listSwitchPrevious',
            href: '#'
        });
        this.previousLink.on('click', function(ev) {
            ev.stopEvent();
            this.onPrevious();
        }, this);

        this.nextLink = this.list.el.createChild({
            tag: 'a',
            cls: 'listSwitchNext',
            href: '#'
        });
        this.nextLink.on('click', function(ev) {
            ev.stopEvent();
            this.onNext();
        }, this);

        this.list.on('activeChanged', function(item) {
            if (item == this.list.getFirstItem()) {
                this.previousLink.hide();
            } else {
                this.previousLink.show();
            }
            if (item == this.list.getLastItem()) {
                this.nextLink.hide();
            } else {
                this.nextLink.show();
            }
        }, this);
    },
    onPrevious: function() {
        this.list.setActiveItem(this.list.getItem(this.list.getActiveItem().listIndex-1));
    },
    onNext: function() {
        this.list.setActiveItem(this.list.getItem(this.list.getActiveItem().listIndex+1));
    }
});
<commit_msg>List NextPrevious Links do not show/hide the switchLinks, they get a css class.<commit_after>Vps.EyeCandy.List.Plugins.ActiveChanger.NextPreviousLinks = Ext.extend(Vps.EyeCandy.List.Plugins.Abstract, {
    init: function() {

        this.previousLink = this.list.el.createChild({
            tag: 'a',
            cls: 'listSwitchPrevious',
            href: '#'
        });
        this.previousLink.on('click', function(ev) {
            ev.stopEvent();
            this.onPrevious();
        }, this);

        this.nextLink = this.list.el.createChild({
            tag: 'a',
            cls: 'listSwitchNext',
            href: '#'
        });
        this.nextLink.on('click', function(ev) {
            ev.stopEvent();
            this.onNext();
        }, this);

        this.list.on('activeChanged', function(item) {
            if (item == this.list.getFirstItem()) {
                this.previousLink.addClass('listSwitchInactive');
            } else {
                this.previousLink.removeClass('listSwitchInactive');
            }
            if (item == this.list.getLastItem()) {
                this.nextLink.addClass('listSwitchInactive');
            } else {
                this.nextLink.removeClass('listSwitchInactive');
            }
        }, this);
    },
    onPrevious: function() {
        var item = this.list.getItem(this.list.getActiveItem().listIndex-1);
        if (item) this.list.setActiveItem(item);
    },
    onNext: function() {
        var item = this.list.getItem(this.list.getActiveItem().listIndex+1);
        if (item) this.list.setActiveItem(item);
    }
});
<|endoftext|>


<commit_before>import debugModule from "debug";
const debug = debugModule("test:data:decode");

import Ganache from "ganache-core";
import { assert } from "chai";
import changeCase from "change-case";
import BN from "bn.js";

import { prepareContracts } from "test/helpers";

import Debugger from "lib/debugger";

import solidity from "lib/solidity/selectors";

export function* generateUints() {
  let x = 0;
  while (true) {
    yield x;
    x++;
  }
}

function contractName(testName) {
  return testName.replace(/ /g, "");
}

function fileName(testName) {
  return `${contractName(testName)}.sol`;
}

function generateTests(fixtures) {
  for (let { name, value: expected } of fixtures) {
    it(`correctly decodes ${name}`, async () => {
      const response = await this.decode(name);
      if (expected instanceof Map) {
        assert.instanceOf(response, Map);
        assert.sameDeepMembers(
          Array.from(response.keys()),
          Array.from(expected.keys())
        );
        for (let key of expected.keys()) {
          //no mappings in this test are nested so this will do fine
          assert.deepEqual(response[key], expected[key]);
        }
      } else {
        assert.deepEqual(response, expected);
      }
    });
  }
}

function lastStatementLine(source) {
  const lines = source.split("\n");
  for (let i = lines.length - 1; i >= 0; i--) {
    let line = lines[i];
    if (line.indexOf(";") != -1) {
      return i;
    }
  }
}

async function prepareDebugger(testName, sources) {
  const provider = Ganache.provider({ seed: "debugger", gasLimit: 7000000 });

  let { abstractions, artifacts: contracts, files } = await prepareContracts(
    provider,
    sources
  );

  let instance = await abstractions[contractName(testName)].deployed();
  let receipt = await instance.run();
  let txHash = receipt.tx;

  let bugger = await Debugger.forTx(txHash, { provider, files, contracts });

  let session = bugger.connect();

  let source = sources[fileName(testName)];

  //we'll need the debugger-internal ID of this source
  let debuggerSources = session.view(solidity.info.sources);
  let matchingSources = Object.values(debuggerSources).filter(sourceObject =>
    sourceObject.sourcePath.includes(contractName(testName))
  );
  let sourceId = matchingSources[0].id;

  let breakpoint = {
    sourceId,
    line: lastStatementLine(source)
  };

  await session.addBreakpoint(breakpoint);

  await session.continueUntilBreakpoint();

  return session;
}

async function decode(name) {
  let result = await this.session.variable(name);

  if (Array.isArray(result)) {
    result = result.map(element => {
      if (BN.isBN(element)) {
        // We're assuming these tests have small numbers
        return element.toNumber();
      } else if (typeof element.toString === "function") {
        return element.toString();
      } else {
        return element;
      }
    });
  } else if (typeof result === "object") {
    switch (result.type) {
      case "mapping": {
        result = Object.assign(
          {},
          ...Object.entries(result.members).map(([key, value]) => {
            if (BN.isBN(value)) {
              // We're assuming these tests have small numbers
              value = value.toNumber();
            } else if (typeof value.toString === "function") {
              value = value.toString();
            }

            return {
              [key]: value
            };
          })
        );
        break;
      }
    }
  }

  return result;
}

export function describeDecoding(testName, fixtures, selector, generateSource) {
  const sources = {
    [fileName(testName)]: generateSource(contractName(testName), fixtures)
  };

  describe(testName, function() {
    const testDebug = debugModule(
      `test:data:decode:${changeCase.paramCase(testName)}`
    );

    testDebug("source %s", Object.values(sources)[0]);

    this.timeout(30000);

    before("runs and observes debugger", async () => {
      this.session = await prepareDebugger(testName, sources);
      this.decode = decode;

      if (selector) {
        debug("selector %O", this.session.view(selector));
      }
    });

    generateTests.bind(this)(fixtures);
  });
}
<commit_msg>Use DebugUtils.nativize in decoding tests<commit_after>import debugModule from "debug";
const debug = debugModule("test:data:decode");

import Ganache from "ganache-core";
import { assert } from "chai";
import changeCase from "change-case";

import { prepareContracts } from "test/helpers";
import DebugUtils from "truffle-debug-utils";

import Debugger from "lib/debugger";

import solidity from "lib/solidity/selectors";

export function* generateUints() {
  let x = 0;
  while (true) {
    yield x;
    x++;
  }
}

function contractName(testName) {
  return testName.replace(/ /g, "");
}

function fileName(testName) {
  return `${contractName(testName)}.sol`;
}

function generateTests(fixtures) {
  for (let { name, value: expected } of fixtures) {
    it(`correctly decodes ${name}`, async () => {
      const response = await this.decode(name);
      if (expected instanceof Map) {
        assert.sameDeepMembers(
          Array.from(response.keys()),
          Array.from(expected.keys())
        );
        for (let key of expected.keys()) {
          //no mappings in this test are nested so this will do fine
          assert.deepEqual(response[key], expected[key]);
        }
      } else {
        assert.deepEqual(response, expected);
      }
    });
  }
}

function lastStatementLine(source) {
  const lines = source.split("\n");
  for (let i = lines.length - 1; i >= 0; i--) {
    let line = lines[i];
    if (line.indexOf(";") != -1) {
      return i;
    }
  }
}

async function prepareDebugger(testName, sources) {
  const provider = Ganache.provider({ seed: "debugger", gasLimit: 7000000 });

  let { abstractions, artifacts: contracts, files } = await prepareContracts(
    provider,
    sources
  );

  let instance = await abstractions[contractName(testName)].deployed();
  let receipt = await instance.run();
  let txHash = receipt.tx;

  let bugger = await Debugger.forTx(txHash, { provider, files, contracts });

  let session = bugger.connect();

  let source = sources[fileName(testName)];

  //we'll need the debugger-internal ID of this source
  let debuggerSources = session.view(solidity.info.sources);
  let matchingSources = Object.values(debuggerSources).filter(sourceObject =>
    sourceObject.sourcePath.includes(contractName(testName))
  );
  let sourceId = matchingSources[0].id;

  let breakpoint = {
    sourceId,
    line: lastStatementLine(source)
  };

  await session.addBreakpoint(breakpoint);

  await session.continueUntilBreakpoint();

  return session;
}

async function decode(name) {
  let result = await this.session.variable(name);

  return DebugUtils.nativize(result);
}

export function describeDecoding(testName, fixtures, selector, generateSource) {
  const sources = {
    [fileName(testName)]: generateSource(contractName(testName), fixtures)
  };

  describe(testName, function() {
    const testDebug = debugModule(
      `test:data:decode:${changeCase.paramCase(testName)}`
    );

    testDebug("source %s", Object.values(sources)[0]);

    this.timeout(30000);

    before("runs and observes debugger", async () => {
      this.session = await prepareDebugger(testName, sources);
      this.decode = decode;

      if (selector) {
        debug("selector %O", this.session.view(selector));
      }
    });

    generateTests.bind(this)(fixtures);
  });
}
<|endoftext|>


<commit_before>'use strict';

const Promise = require('bluebird'),
  log = require('intel'),
  merge = require('lodash.merge'),
  webdriver = require('selenium-webdriver'),
  Condition = require('selenium-webdriver/lib/webdriver').Condition,
  WebDriverError = webdriver.error.WebDriverError,
  encodeError = webdriver.error.encodeError,
  builder = require('./webdriver'),
  UrlLoadError = require('../support/errors').UrlLoadError,
  BrowserError = require('../support/errors').BrowserError;

const defaultPageCompleteCheck =
  'return (function() {try { var end = window.performance.timing.loadEventEnd;' +
  'return (end > 0) && (Date.now() > end + 2000);} catch(e) {return true;}})()';
const defaults = {
  timeouts: {
    browserStart: 60000,
    pageLoad: 300000,
    script: 80000,
    logs: 90000,
    pageCompleteCheck: 300000
  },
  index: 0
};

/**
 * Timeout a promise after ms. Use promise.race to compete
 * about the timeout and the promise.
 * @param {promise} promise - The promise to wait for
 * @param {int} ms - how long in ms to wait for the promise to fininsh
 * @param {string} errorMessage - the error message in the Error if we timeouts
 */
function timeout(promise, ms, errorMessage) {
  let timer = null;

  return Promise.race([
    new Promise((resolve, reject) => {
      timer = setTimeout(reject, ms, new BrowserError(errorMessage));
      return timer;
    }),
    promise.then(value => {
      clearTimeout(timer);
      return value;
    })
  ]);
}

/**
 * Wrapper for Selenium.
 * @class
 */
class SeleniumRunner {
  constructor(options) {
    this.options = merge({}, defaults, options);
  }

  /**
   * Start the browser. Will timout after
   * --timeouts.browserStart time. It will try to start the
   * browser 3 times.
   * @throws {BrowserError} if the browser can't start
   */
  async start() {
    const tries = 3;
    for (let i = 0; i < tries; ++i) {
      try {
        this.driver = await timeout(
          builder.createWebDriver(this.options),
          this.options.timeouts.browserStart,
          `Failed to start browser in ${this.options.timeouts.browserStart /
            1000} seconds.`
        );
        break;
      } catch (e) {
        log.info(`Browser failed to start, trying one more time: ${e.message}`);
      }
    }
    if (!this.driver) {
      throw new BrowserError(`Could not start the browser with ${tries} tries`);
    }

    try {
      await this.driver.manage().setTimeouts({
        script: this.options.timeouts.script,
        pageLoad: this.options.timeouts.pageLoad
      });

      let viewPort = this.options.viewPort;
      if (viewPort) {
        if (viewPort !== 'maximize') {
          viewPort = viewPort.split('x');
        }
        const window = this.driver.manage().window();

        if (viewPort === 'maximize') {
          if (this.options.xvfb) {
            log.info(
              'Maximizing window in XVFB may not work, make sure that you verify that it works.'
            );
          }
          await window.maximize();
        } else {
          await window.setPosition(0, 0);
          await window.setSize(Number(viewPort[0]), Number(viewPort[1]));
        }
      }
    } catch (e) {
      throw new BrowserError(e.message, {
        cause: e
      });
    }
  }

  /**
   * Load and and wait for pageCompleteCheck to end before we return.
   * @param {string} url -The URL that will be tested.
   * @param {string} pageCompleteCheck - JavaScript that checks if the page has finished loading
   * @throws {UrlLoadError}
   */
  async loadAndWait(url, pageCompleteCheck = defaultPageCompleteCheck) {
    let driver = this.driver,
      pageCompleteCheckTimeout = this.options.timeouts.pageCompleteCheck;

    try {
      await driver.get(url);

      // E.g. when loading about:blank for the info page.s
      if (url.match(/^http(s)?:\/\//i)) {
        const uri = await driver.executeScript('return document.documentURI;');
        // Verify that the page succesfully loaded
        if (!uri.match(/^http(s)?:\/\//i)) {
          throw new UrlLoadError('Failed to load ' + url, url);
        }
      }

      const pageCompleteCheckCondition = new Condition(
        'for page complete check script to return true',
        function(d) {
          return d.executeScript(pageCompleteCheck).then(function(t) {
            return t === true;
          });
        }
      );

      log.debug(
        "Waiting for script '%s' at most %d ms",
        pageCompleteCheck,
        pageCompleteCheckTimeout
      );
      const tries = 2;
      for (let i = 0; i < tries; ++i) {
        try {
          await timeout(
            driver.wait(pageCompleteCheckCondition, pageCompleteCheckTimeout),
            pageCompleteCheckTimeout,
            "Running page complete check '" +
              pageCompleteCheck +
              "' took too long."
          );
          break;
        } catch (e) {
          if (e instanceof WebDriverError) {
            log.info(
              'Catched a WebDriverError [' +
                e.message +
                ']. Try one more time. Cause: ' +
                encodeError(e).message
            );
            // Last run, throw the error
            if ((i = tries - 1)) {
              throw e;
            }
          } else throw e;
        }
      }
    } catch (e) {
      log.error('Could not load URL' + e);
      throw new UrlLoadError('Failed to load ' + url, url, {
        cause: e
      });
    }
  }
  /**
   * Take a screenshot.
   *  @throws {BrowserError}
   */
  async takeScreenshot() {
    try {
      const base64EncodedPng = await this.driver.takeScreenshot();
      if (typeof base64EncodedPng === 'string') {
        return Buffer.from(base64EncodedPng, 'base64');
      } else {
        // Sometimes for Chrome, driver.takeScreenshot seems to succeed,
        // but the result is not a string.
        throw new BrowserError(
          `Failed to take screenshot (type was ${typeof base64EncodedPng}`
        );
      }
    } catch (e) {
      throw new BrowserError('Failed to take screenshot', { cause: e });
    }
  }

  /**
   * Run a synchrously JavaScript with args.
   * @param {string} script - the actual script
   * @param {string} name - the name of the script (for logging)
   * @param {*} args - arguments to the script
   * @throws {BrowserError}
   */
  async runScript(script, name, args) {
    let scriptTimeout = this.options.timeouts.script;

    if (log.isEnabledFor(log.TRACE)) {
      log.verbose('Executing script %s', script);
    } else if (log.isEnabledFor(log.VERBOSE)) {
      log.verbose('Executing script %s', name);
    }

    try {
      return await timeout(
        this.driver.executeScript(script, args),
        scriptTimeout,
        `Running script ${script} took too long.`
      );
    } catch (e) {
      log.error("Couldn't execute script named " + name + ' error:' + e);
      throw e;
    }
  }

  /**
   * Run a asynchrously JavaScript.
   * @param {string} script - the actual script
   * @param {string} name - the name of the script (for logging)
   * @param {*} args - arguments to the script
   * @throws {BrowserError}
   */
  async runAsyncScript(script, name, args) {
    if (log.isEnabledFor(log.TRACE)) {
      log.verbose('Executing async script %s', script);
    } else if (log.isEnabledFor(log.VERBOSE)) {
      log.verbose('Executing async script %s', name);
    }
    try {
      return await this.driver.executeAsyncScript(script, args);
    } catch (e) {
      log.error("Couldn't execute async script named " + name + ' error:' + e);
      throw e;
    }
  }

  /**
   * Run script with driver.
   * @param {*} driverScript
   */
  runWithDriver(driverScript) {
    return driverScript(this.driver);
  }

  /**
   * Get logs from the browser.
   * @param {*} logType
   * @throws {BrowserError}
   */
  async getLogs(logType) {
    return await timeout(
      this.driver
        .manage()
        .logs()
        .get(logType),
      this.options.timeouts.logs,
      `Extracting logs from browser took more than ${this.options.timeouts
        .logs / 1000} seconds.`
    );
  }

  /**
   * Stop the driver/browser.
   * @throws {BrowserError}
   */
  async stop() {
    if (this.driver) {
      log.debug('Telling browser to quit.');
      try {
        await this.driver.quit();
      } catch (e) {
        throw new BrowserError(e.message, {
          cause: e
        });
      }
    }
  }

  /**
   *
   * Scripts should be valid statements or IIFEs '(function() {...})()' that can run
   * on their own in the browser console. Prepend with 'return' to return result of statement to Browsertime.
   * @param {*} script - the script
   * @param {boolean} isAsync - is the script synchrously or async?
   * @param {*} name - the name of the script
   */
  async runScriptFromCategory(script, isAsync, name) {
    // Scripts should be valid statements or IIFEs '(function() {...})()' that can run
    // on their own in the browser console. Prepend with 'return' to return result of statement to Browsertime.
    if (isAsync) {
      const source = `
            var callback = arguments[arguments.length - 1];
            return (${script})
              .then((r) => callback({'result': r}))
              .catch((e) => callback({'error': e}));
            `;

      const result = await this.runAsyncScript(source, name);
      if (result.error) {
        throw result.error;
      } else {
        return result.result;
      }
    } else {
      const source = 'return ' + script;
      return await this.runScript(source, name);
    }
  }

  /**
   * Get the driver from Selenium.
   */
  getDriver() {
    return this.driver;
  }

  /**
   * Run scripts by category.
   * @param {*} scriptsByCategory
   * @param {boolean} isAsync - is the script synchrously or async?
   */
  async runScripts(scriptsByCategory, isAsync) {
    const categoryNames = Object.keys(scriptsByCategory);
    const results = {};
    for (let categoryName of categoryNames) {
      const category = scriptsByCategory[categoryName];
      results[categoryName] = await this.runScriptInCategory(category, isAsync);
    }
    return results;
  }

  /**
   * Run scripts in category.
   * @param {*} category
   * @param {boolean} isAsync - is the script synchrously or async?
   */
  async runScriptInCategory(category, isAsync) {
    const scriptNames = Object.keys(category);
    const results = {};
    for (let scriptName of scriptNames) {
      const script = category[scriptName];
      const result = await this.runScriptFromCategory(
        script,
        isAsync,
        scriptName
      );
      if (!(result === null || result === undefined)) {
        results[scriptName] = result;
      }
    }
    return results;
  }
}

module.exports = SeleniumRunner;
<commit_msg>better logging/error messages<commit_after>'use strict';

const Promise = require('bluebird'),
  log = require('intel'),
  merge = require('lodash.merge'),
  webdriver = require('selenium-webdriver'),
  Condition = require('selenium-webdriver/lib/webdriver').Condition,
  WebDriverError = webdriver.error.WebDriverError,
  encodeError = webdriver.error.encodeError,
  builder = require('./webdriver'),
  UrlLoadError = require('../support/errors').UrlLoadError,
  BrowserError = require('../support/errors').BrowserError;

const defaultPageCompleteCheck =
  'return (function() {try { var end = window.performance.timing.loadEventEnd;' +
  'return (end > 0) && (Date.now() > end + 2000);} catch(e) {return true;}})()';
const defaults = {
  timeouts: {
    browserStart: 60000,
    pageLoad: 300000,
    script: 80000,
    logs: 90000,
    pageCompleteCheck: 300000
  },
  index: 0
};

/**
 * Timeout a promise after ms. Use promise.race to compete
 * about the timeout and the promise.
 * @param {promise} promise - The promise to wait for
 * @param {int} ms - how long in ms to wait for the promise to fininsh
 * @param {string} errorMessage - the error message in the Error if we timeouts
 */
function timeout(promise, ms, errorMessage) {
  let timer = null;

  return Promise.race([
    new Promise((resolve, reject) => {
      timer = setTimeout(reject, ms, new BrowserError(errorMessage));
      return timer;
    }),
    promise.then(value => {
      clearTimeout(timer);
      return value;
    })
  ]);
}

/**
 * Wrapper for Selenium.
 * @class
 */
class SeleniumRunner {
  constructor(options) {
    this.options = merge({}, defaults, options);
  }

  /**
   * Start the browser. Will timout after
   * --timeouts.browserStart time. It will try to start the
   * browser 3 times.
   * @throws {BrowserError} if the browser can't start
   */
  async start() {
    const tries = 3;
    for (let i = 0; i < tries; ++i) {
      try {
        this.driver = await timeout(
          builder.createWebDriver(this.options),
          this.options.timeouts.browserStart,
          `Failed to start browser in ${this.options.timeouts.browserStart /
            1000} seconds.`
        );
        break;
      } catch (e) {
        log.info(`Browser failed to start, trying one more time: ${e.message}`);
      }
    }
    if (!this.driver) {
      throw new BrowserError(`Could not start the browser with ${tries} tries`);
    }

    try {
      await this.driver.manage().setTimeouts({
        script: this.options.timeouts.script,
        pageLoad: this.options.timeouts.pageLoad
      });

      let viewPort = this.options.viewPort;
      if (viewPort) {
        if (viewPort !== 'maximize') {
          viewPort = viewPort.split('x');
        }
        const window = this.driver.manage().window();

        if (viewPort === 'maximize') {
          if (this.options.xvfb) {
            log.info(
              'Maximizing window in XVFB may not work, make sure that you verify that it works.'
            );
          }
          await window.maximize();
        } else {
          await window.setPosition(0, 0);
          await window.setSize(Number(viewPort[0]), Number(viewPort[1]));
        }
      }
    } catch (e) {
      throw new BrowserError(e.message, {
        cause: e
      });
    }
  }

  /**
   * Load and and wait for pageCompleteCheck to end before we return.
   * @param {string} url -The URL that will be tested.
   * @param {string} pageCompleteCheck - JavaScript that checks if the page has finished loading
   * @throws {UrlLoadError}
   */
  async loadAndWait(url, pageCompleteCheck = defaultPageCompleteCheck) {
    let driver = this.driver,
      pageCompleteCheckTimeout = this.options.timeouts.pageCompleteCheck;

    try {
      await driver.get(url);

      // E.g. when loading about:blank for the info page.s
      if (url.match(/^http(s)?:\/\//i)) {
        const uri = await driver.executeScript('return document.documentURI;');
        // Verify that the page succesfully loaded
        if (!uri.match(/^http(s)?:\/\//i)) {
          throw new UrlLoadError('Failed to load ' + url, url);
        }
      }

      const pageCompleteCheckCondition = new Condition(
        'for page complete check script to return true',
        function(d) {
          return d.executeScript(pageCompleteCheck).then(function(t) {
            return t === true;
          });
        }
      );

      log.debug(
        `Waiting for script ${pageCompleteCheck} at most ${pageCompleteCheckTimeout} ms`
      );
      const tries = 2;
      for (let i = 0; i < tries; ++i) {
        try {
          await timeout(
            driver.wait(pageCompleteCheckCondition, pageCompleteCheckTimeout),
            pageCompleteCheckTimeout,
            `Running page complete check ${pageCompleteCheck} took too long`
          );
          break;
        } catch (e) {
          if (e instanceof WebDriverError) {
            log.info(
              'Catched a WebDriverError [' +
                e.message +
                ']. Try one more time. Cause: ' +
                encodeError(e).message
            );
            // Last run, throw the error
            if ((i = tries - 1)) {
              throw e;
            }
          } else throw e;
        }
      }
    } catch (e) {
      log.error('Could not load URL' + e);
      throw new UrlLoadError('Failed to load ' + url, url, {
        cause: e
      });
    }
  }
  /**
   * Take a screenshot.
   *  @throws {BrowserError}
   */
  async takeScreenshot() {
    try {
      const base64EncodedPng = await this.driver.takeScreenshot();
      if (typeof base64EncodedPng === 'string') {
        return Buffer.from(base64EncodedPng, 'base64');
      } else {
        // Sometimes for Chrome, driver.takeScreenshot seems to succeed,
        // but the result is not a string.
        throw new BrowserError(
          `Failed to take screenshot (type was ${typeof base64EncodedPng}`
        );
      }
    } catch (e) {
      throw new BrowserError('Failed to take screenshot', { cause: e });
    }
  }

  /**
   * Run a synchrously JavaScript with args.
   * @param {string} script - the actual script
   * @param {string} name - the name of the script (for logging)
   * @param {*} args - arguments to the script
   * @throws {BrowserError}
   */
  async runScript(script, name, args) {
    let scriptTimeout = this.options.timeouts.script;

    if (log.isEnabledFor(log.TRACE)) {
      log.verbose('Executing script %s', script);
    } else if (log.isEnabledFor(log.VERBOSE)) {
      log.verbose('Executing script %s', name);
    }

    try {
      return await timeout(
        this.driver.executeScript(script, args),
        scriptTimeout,
        `Running script ${script} took too long (${scriptTimeout} ms).`
      );
    } catch (e) {
      log.error("Couldn't execute script named " + name + ' error:' + e);
      throw e;
    }
  }

  /**
   * Run a asynchrously JavaScript.
   * @param {string} script - the actual script
   * @param {string} name - the name of the script (for logging)
   * @param {*} args - arguments to the script
   * @throws {BrowserError}
   */
  async runAsyncScript(script, name, args) {
    if (log.isEnabledFor(log.TRACE)) {
      log.verbose('Executing async script %s', script);
    } else if (log.isEnabledFor(log.VERBOSE)) {
      log.verbose('Executing async script %s', name);
    }
    try {
      return await this.driver.executeAsyncScript(script, args);
    } catch (e) {
      log.error("Couldn't execute async script named " + name + ' error:' + e);
      throw e;
    }
  }

  /**
   * Run script with driver.
   * @param {*} driverScript
   */
  runWithDriver(driverScript) {
    return driverScript(this.driver);
  }

  /**
   * Get logs from the browser.
   * @param {*} logType
   * @throws {BrowserError}
   */
  async getLogs(logType) {
    return await timeout(
      this.driver
        .manage()
        .logs()
        .get(logType),
      this.options.timeouts.logs,
      `Extracting logs from browser took more than ${this.options.timeouts
        .logs / 1000} seconds.`
    );
  }

  /**
   * Stop the driver/browser.
   * @throws {BrowserError}
   */
  async stop() {
    if (this.driver) {
      log.debug('Telling browser to quit.');
      try {
        await this.driver.quit();
      } catch (e) {
        throw new BrowserError(e.message, {
          cause: e
        });
      }
    }
  }

  /**
   *
   * Scripts should be valid statements or IIFEs '(function() {...})()' that can run
   * on their own in the browser console. Prepend with 'return' to return result of statement to Browsertime.
   * @param {*} script - the script
   * @param {boolean} isAsync - is the script synchrously or async?
   * @param {*} name - the name of the script
   */
  async runScriptFromCategory(script, isAsync, name) {
    // Scripts should be valid statements or IIFEs '(function() {...})()' that can run
    // on their own in the browser console. Prepend with 'return' to return result of statement to Browsertime.
    if (isAsync) {
      const source = `
            var callback = arguments[arguments.length - 1];
            return (${script})
              .then((r) => callback({'result': r}))
              .catch((e) => callback({'error': e}));
            `;

      const result = await this.runAsyncScript(source, name);
      if (result.error) {
        throw result.error;
      } else {
        return result.result;
      }
    } else {
      const source = 'return ' + script;
      return await this.runScript(source, name);
    }
  }

  /**
   * Get the driver from Selenium.
   */
  getDriver() {
    return this.driver;
  }

  /**
   * Run scripts by category.
   * @param {*} scriptsByCategory
   * @param {boolean} isAsync - is the script synchrously or async?
   */
  async runScripts(scriptsByCategory, isAsync) {
    const categoryNames = Object.keys(scriptsByCategory);
    const results = {};
    for (let categoryName of categoryNames) {
      const category = scriptsByCategory[categoryName];
      results[categoryName] = await this.runScriptInCategory(category, isAsync);
    }
    return results;
  }

  /**
   * Run scripts in category.
   * @param {*} category
   * @param {boolean} isAsync - is the script synchrously or async?
   */
  async runScriptInCategory(category, isAsync) {
    const scriptNames = Object.keys(category);
    const results = {};
    for (let scriptName of scriptNames) {
      const script = category[scriptName];
      const result = await this.runScriptFromCategory(
        script,
        isAsync,
        scriptName
      );
      if (!(result === null || result === undefined)) {
        results[scriptName] = result;
      }
    }
    return results;
  }
}

module.exports = SeleniumRunner;
<|endoftext|>