<commit_before>import assert from 'assert';
import chroma from 'chroma-js';
import { Swig } from 'swig';
import swigExtras from 'swig-extras';
import swigFilters from 'swig/lib/filters';

const swig = new Swig();
export default swig;

swigExtras.useFilter(swig, 'split');
swigExtras.useFilter(swig, 'trim');
swigExtras.useFilter(swig, 'groupby');

const safe = fn =>
  fn.safe = true && fn;

const isColor = value => {
  try {
    chroma(value);
    return true;
  } catch (e) {
    return false;
  }
};

const displayAsType = input =>
  input.split('|')
    .map(x => x.trim())
    .map(swigFilters.capitalize)
    .join(' or ');

const yiq = ([red, green, blue]) =>
  ((red * 299) + (green * 587) + (blue * 114)) / 1000;

const yiqContrast = rgb =>
  (yiq(rgb) >= 128) ? '#000' : '#fff';

const getChannel = (start, hex) =>
  parseInt(hex.substr(start, 2), 16);

const hexTorgb = hex =>
  [0, 2, 4].map(x => getChannel(x, hex));

const colorToHex = color =>
  chroma(color).hex().substr(1);

/**
 * Normalises a CSS color, then uses the YIQ algorithm to get the
 * correct contrast.
 *
 * @param {String} color
 * @return {String} `#000` or `#fff` depending on which one is a better.
 * @see {@link http://stackoverflow.com/questions/11867545/change-text-color-based-on-brightness-of-the-covered-background-area}
 */
const maybeYiqContrast = color =>
  isColor(color) ?
    yiqContrast(hexToRgb(colorToHex(color))) :
    '#000';

swig.setFilter('in', (key, object) => key in object);
swig.setFilter('is_color', isColor);
swig.setFilter('display_as_type', safe(displayAsType));
swig.setFilter('yiq', maybeYiqContrast);
<commit_msg>Fix typo in variable name<commit_after>import assert from 'assert';
import chroma from 'chroma-js';
import { Swig } from 'swig';
import swigExtras from 'swig-extras';
import swigFilters from 'swig/lib/filters';

const swig = new Swig();
export default swig;

swigExtras.useFilter(swig, 'split');
swigExtras.useFilter(swig, 'trim');
swigExtras.useFilter(swig, 'groupby');

const safe = fn =>
  fn.safe = true && fn;

const isColor = value => {
  try {
    chroma(value);
    return true;
  } catch (e) {
    return false;
  }
};

const displayAsType = input =>
  input.split('|')
    .map(x => x.trim())
    .map(swigFilters.capitalize)
    .join(' or ');

const yiq = ([red, green, blue]) =>
  ((red * 299) + (green * 587) + (blue * 114)) / 1000;

const yiqContrast = rgb =>
  (yiq(rgb) >= 128) ? '#000' : '#fff';

const getChannel = (start, hex) =>
  parseInt(hex.substr(start, 2), 16);

const hexToRgb = hex =>
  [0, 2, 4].map(x => getChannel(x, hex));

const colorToHex = color =>
  chroma(color).hex().substr(1);

/**
 * Normalises a CSS color, then uses the YIQ algorithm to get the
 * correct contrast.
 *
 * @param {String} color
 * @return {String} `#000` or `#fff` depending on which one is a better.
 * @see {@link http://stackoverflow.com/questions/11867545/change-text-color-based-on-brightness-of-the-covered-background-area}
 */
const maybeYiqContrast = color =>
  isColor(color) ?
    yiqContrast(hexToRgb(colorToHex(color))) :
    '#000';

swig.setFilter('in', (key, object) => key in object);
swig.setFilter('is_color', isColor);
swig.setFilter('display_as_type', safe(displayAsType));
swig.setFilter('yiq', maybeYiqContrast);
<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import ProgressBar from './ProgressBar';
import Button from './Button';
import CardIcons from './CardIcons';
import Image from './Image';
import * as UserActionCreators from '../../actions/UserActionCreators'
import translate from '../../i18n/Translate';

@translate('CardContent')
export default class CardContent extends Component {

    static propTypes = {
        contentId     : PropTypes.number.isRequired,
        title         : PropTypes.string,
        description   : PropTypes.string,
        types         : PropTypes.array.isRequired,
        url           : PropTypes.string.isRequired,
        embed_id      : PropTypes.string,
        embed_type    : PropTypes.string,
        thumbnail     : PropTypes.string,
        synonymous    : PropTypes.array.isRequired,
        matching      : PropTypes.number,
        rate          : PropTypes.number,
        hideLikeButton: PropTypes.bool.isRequired,
        fixedHeight   : PropTypes.bool,
        loggedUserId  : PropTypes.number.isRequired,
        onClickHandler: PropTypes.func,
        // Injected by @translate:
        strings       : PropTypes.object
    };

    constructor(props) {
        super(props);

        this.onRate = this.onRate.bind(this);
        this.onShare = this.onShare.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.onShareSuccess = this.onShareSuccess.bind(this);
        this.onShareError = this.onShareError.bind(this);

        this.state = {
            embedHtml: null
        }
    }

    onRate() {
        const {loggedUserId, contentId, rate} = this.props;
        if (!rate) {
            UserActionCreators.likeContent(loggedUserId, contentId);
        } else {
            UserActionCreators.deleteLikeContent(loggedUserId, contentId);
        }
    }

    onShare() {
        const {title, url, strings} = this.props;
        if (window.cordova) {
            // this is the complete list of currently supported params you can pass to the plugin (all optional)
            var options = {
                //message: 'share this', // not supported on some apps (Facebook, Instagram)
                subject: title, // fi. for email
                url: url
                //chooserTitle: 'Pick an app' // Android only, you can override the default share sheet title
            };
            window.plugins.socialsharing.shareWithOptions(options, this.onShareSuccess, this.onShareError);
        } else {
            window.prompt(strings.copyToClipboard, url);
            this.onShareSuccess();
        }
    }

    onShareSuccess() {
        const {loggedUserId, contentId, rate} = this.props;
        if (!rate) {
            UserActionCreators.likeContent(loggedUserId, contentId);
        }
    }

    onShareError() {
        nekunoApp.alert(this.props.strings.shareError)
    }

    handleClick() {
        const {url, types, embed_type, embed_id, onClickHandler} = this.props;
        if (typeof onClickHandler !== 'undefined' && onClickHandler) {
            onClickHandler();
        } else {
            const isVideo = types.indexOf('Video') > -1;
            if (isVideo) {
                this.preVisualizeYoutube(embed_type, embed_id);
            } else {
                window.cordova ? document.location = url : window.open(url);
            }
        }
    }

    preVisualizeYoutube = function (embed_type, embed_id) {
        let html = null;
        switch (embed_type) {
            case 'youtube':
                html = ;
                break;
            default:
                break;
        }

        this.setState({
            embedHtml: html
        });
    };

    preventDefault(e) {
        e.preventDefault();
    }

    render() {
        const {title, description, types, rate, hideLikeButton, fixedHeight, thumbnail, url, matching, strings} = this.props;
        const cardTitle = title ?
{title.substr(0, 20)}{title.length > 20 ? '...' : ''}
 :
 {strings.emptyTitle}
;
        const subTitle = description ?
{description.substr(0, 20)}{description.length > 20 ? '...' : ''}
 : fixedHeight ?

 : '';
        const likeButtonText = rate === null ? strings.saving : rate ? strings.unlike : strings.like;
        const likeButton = hideLikeButton ? '' :
<Button onClick={this.onRate} disabled={rate === null ? 'disabled' : null}>{likeButtonText}</Button>
;
        const imageClass = fixedHeight ? 'image fixed-height-image' : 'image';
        const isImage = types.indexOf('Image') > -1;
        const defaultSrc = 'img/default-content-image.jpg';
        let imgSrc = defaultSrc;
        if (thumbnail) {
            imgSrc = thumbnail;
        } else if (isImage) {
            imgSrc = url;
        }
        return (

                {isImage ? '' :

                                {cardTitle}

                            {subTitle}

                }

                    <CardIcons types={types}/>

                        {this.state.embedHtml ? this.state.embedHtml :

                                    <Image src={imgSrc} defaultSrc={defaultSrc}/>

                        }
                        {!this.state.embedHtml && typeof matching !== 'undefined' ?

{strings.compatibility} {matching ? matching + '%' : '?'}

                                <ProgressBar percentage={matching}/>

                            :
                            null
                        }

                {likeButton ?

                        {likeButton}

                    : ''
                }

        );
    }

}

CardContent.defaultProps = {
    strings: {
        like           : 'Like',
        unlike         : 'Remove',
        compatibility  : 'Compatibility',
        emptyTitle     : 'Title',
        copyToClipboard: 'Copy to clipboard: Ctrl+C, Enter',
        shareError     : 'An error occurred sharing the content',
        saving         : 'Saving...'
    }
};<commit_msg>Fix show FB videos<commit_after>import React, { PropTypes, Component } from 'react';
import ProgressBar from './ProgressBar';
import Button from './Button';
import CardIcons from './CardIcons';
import Image from './Image';
import * as UserActionCreators from '../../actions/UserActionCreators'
import translate from '../../i18n/Translate';

@translate('CardContent')
export default class CardContent extends Component {

    static propTypes = {
        contentId     : PropTypes.number.isRequired,
        title         : PropTypes.string,
        description   : PropTypes.string,
        types         : PropTypes.array.isRequired,
        url           : PropTypes.string.isRequired,
        embed_id      : PropTypes.string,
        embed_type    : PropTypes.string,
        thumbnail     : PropTypes.string,
        synonymous    : PropTypes.array.isRequired,
        matching      : PropTypes.number,
        rate          : PropTypes.number,
        hideLikeButton: PropTypes.bool.isRequired,
        fixedHeight   : PropTypes.bool,
        loggedUserId  : PropTypes.number.isRequired,
        onClickHandler: PropTypes.func,
        // Injected by @translate:
        strings       : PropTypes.object
    };

    constructor(props) {
        super(props);

        this.onRate = this.onRate.bind(this);
        this.onShare = this.onShare.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.onShareSuccess = this.onShareSuccess.bind(this);
        this.onShareError = this.onShareError.bind(this);

        this.state = {
            embedHtml: null
        }
    }

    onRate() {
        const {loggedUserId, contentId, rate} = this.props;
        if (!rate) {
            UserActionCreators.likeContent(loggedUserId, contentId);
        } else {
            UserActionCreators.deleteLikeContent(loggedUserId, contentId);
        }
    }

    onShare() {
        const {title, url, strings} = this.props;
        if (window.cordova) {
            // this is the complete list of currently supported params you can pass to the plugin (all optional)
            var options = {
                //message: 'share this', // not supported on some apps (Facebook, Instagram)
                subject: title, // fi. for email
                url: url
                //chooserTitle: 'Pick an app' // Android only, you can override the default share sheet title
            };
            window.plugins.socialsharing.shareWithOptions(options, this.onShareSuccess, this.onShareError);
        } else {
            window.prompt(strings.copyToClipboard, url);
            this.onShareSuccess();
        }
    }

    onShareSuccess() {
        const {loggedUserId, contentId, rate} = this.props;
        if (!rate) {
            UserActionCreators.likeContent(loggedUserId, contentId);
        }
    }

    onShareError() {
        nekunoApp.alert(this.props.strings.shareError)
    }

    handleClick() {
        const {url, types, embed_type, embed_id, onClickHandler} = this.props;
        if (typeof onClickHandler !== 'undefined' && onClickHandler) {
            onClickHandler();
        } else {
            // TODO: Embed videos from FB too
            const isVideo = types.indexOf('Video') > -1 && embed_type === 'youtube';
            if (isVideo) {
                this.preVisualizeYoutube(embed_type, embed_id);
            } else {
                window.cordova ? document.location = url : window.open(url);
            }
        }
    }

    preVisualizeYoutube = function (embed_type, embed_id) {
        let html = null;
        switch (embed_type) {
            case 'youtube':
                html = ;
                break;
            default:
                break;
        }

        this.setState({
            embedHtml: html
        });
    };

    preventDefault(e) {
        e.preventDefault();
    }

    render() {
        const {title, description, types, rate, hideLikeButton, fixedHeight, thumbnail, url, matching, strings} = this.props;
        const cardTitle = title ?
{title.substr(0, 20)}{title.length > 20 ? '...' : ''}
 :
 {strings.emptyTitle}
;
        const subTitle = description ?
{description.substr(0, 20)}{description.length > 20 ? '...' : ''}
 : fixedHeight ?

 : '';
        const likeButtonText = rate === null ? strings.saving : rate ? strings.unlike : strings.like;
        const likeButton = hideLikeButton ? '' :
<Button onClick={this.onRate} disabled={rate === null ? 'disabled' : null}>{likeButtonText}</Button>
;
        const imageClass = fixedHeight ? 'image fixed-height-image' : 'image';
        const isImage = types.indexOf('Image') > -1;
        const defaultSrc = 'img/default-content-image.jpg';
        let imgSrc = defaultSrc;
        if (thumbnail) {
            imgSrc = thumbnail;
        } else if (isImage) {
            imgSrc = url;
        }
        return (

                {isImage ? '' :

                                {cardTitle}

                            {subTitle}

                }

                    <CardIcons types={types}/>

                        {this.state.embedHtml ? this.state.embedHtml :

                                    <Image src={imgSrc} defaultSrc={defaultSrc}/>

                        }
                        {!this.state.embedHtml && typeof matching !== 'undefined' ?

{strings.compatibility} {matching ? matching + '%' : '?'}

                                <ProgressBar percentage={matching}/>

                            :
                            null
                        }

                {likeButton ?

                        {likeButton}

                    : ''
                }

        );
    }

}

CardContent.defaultProps = {
    strings: {
        like           : 'Like',
        unlike         : 'Remove',
        compatibility  : 'Compatibility',
        emptyTitle     : 'Title',
        copyToClipboard: 'Copy to clipboard: Ctrl+C, Enter',
        shareError     : 'An error occurred sharing the content',
        saving         : 'Saving...'
    }
};<|endoftext|>


<commit_before>
import _ from 'lodash';
import Character from './character';
import Factions from '../constants/factions';
import GameState from '../init/gamestate';

import Settings from '../constants/settings';

export default class Monster extends Character {
  constructor(x, y, z, opts) {
    opts.stats.profession = 'Monster';
    opts.attributes = _.extend({}, Settings.game.defaultStats.monster, opts.attributes);
    super(opts.glyph, x, y, z, opts);
    this.difficulty = opts.difficulty;
    this.antiFactions.push(Factions.PLAYER);
    if(opts.addFactions) this.factions.push(...opts.addFactions);
    if(opts.startingEquipment) this.loadStartingEquipment(opts.startingEquipment);
  }

  arePlayersAPossibility() {
    const minZ = _.min(GameState.players, 'z').z;
    return minZ <= this.z;
  }

  act() {
    if(!this.arePlayersAPossibility()) {
      return this.removeSelf() && this.cleanUp();
    }
    super.act();
  }

  cleanUp() {
    super.cleanUp();
    this._attackedBy = null;
    this.target = null;
    this._current = null;
  }

  toJSON() {
    const base = JSON.parse(super.toJSON());
    const me = _.omit(base, 'target');
    return JSON.stringify(me);
  }
}<commit_msg>attempted monster cleanup<commit_after>
import _ from 'lodash';
import Character from './character';
import Factions from '../constants/factions';
import GameState from '../init/gamestate';

import Settings from '../constants/settings';

export default class Monster extends Character {
  constructor(x, y, z, opts) {
    opts.stats.profession = 'Monster';
    opts.attributes = _.extend({}, Settings.game.defaultStats.monster, opts.attributes);
    super(opts.glyph, x, y, z, opts);
    this.difficulty = opts.difficulty;
    this.antiFactions.push(Factions.PLAYER);
    if(opts.addFactions) this.factions.push(...opts.addFactions);
    if(opts.startingEquipment) this.loadStartingEquipment(opts.startingEquipment);
  }

  arePlayersAPossibility() {
    const minZ = _.min(GameState.players, 'z').z;
    return minZ <= this.z;
  }

  act() {
    if(!this.arePlayersAPossibility()) {
      return this.removeSelf() && this.cleanUp();
    }
    super.act();
  }

  cleanUp() {
    super.cleanUp();
    this._attackedBy = null;
    this.target = null;
    this._current = null;
  }

  removeSelf() {
    super.removeSelf();
    this.cleanUp();
  }

  toJSON() {
    const base = JSON.parse(super.toJSON());
    const me = _.omit(base, 'target');
    return JSON.stringify(me);
  }
}<|endoftext|>


<commit_before>// ES6 - 81: Number - parseInt
// To do: make all tests pass, leave the assert lines unchanged!
// Follow the hints of the failure messages!

describe('`Number.parseInt()` parses a string and returns an integer.', () => {
  it('it is a static function on `Number`', () => {
    const whatType = 'function';
    assert.equal(typeof Number.isNaN, whatType);
  });
  it('is the same as the global function `parseInt`', () => {
    assert.equal(Number.parseInt, global().parseInt);
  });
});

const global = () => globalThis || window || global;

<commit_msg>Katafy, now it generates failing tests.<commit_after>// ES6 - 81: Number - parseInt
// To do: make all tests pass, leave the assert lines unchanged!
// Follow the hints of the failure messages!

describe('`Number.parseInt()` parses a string and returns an integer.', () => {
  it('it is a static function on `Number`', () => {
    //// const whatType = 'global';
    const whatType = 'function';
    assert.equal(typeof Number.isNaN, whatType);
  });
  it('is the same as the global function `parseInt`', () => {
    //// const parseIntFunction = Number.parse_int;
    const parseIntFunction = Number.parseInt;
    assert.equal(parseIntFunction, global().parseInt);
  });
});

const global = () => globalThis || window || global;

<|endoftext|>


<commit_before>import {
  assertThat, equalTo, not,
} from 'hamjest';

describe('`falsy()` (also `falsey()`), matches falsy values', () => {

});
<commit_msg>Implement `falsy()` tests.<commit_after>import {
  assertThat, equalTo, not, strictlyEqualTo,
  falsy, falsey,
  Matcher,
} from 'hamjest';

describe('The matcher `falsy()`, matches every falsy value', () => {
  it('can also be written as `falsey()`', () => {
    assertThat(falsy, strictlyEqualTo(falsey));
  });
  describe('for example', () => {
    it('`undefined`', () => {
      assertThat(undefined, falsy());
    });
    it('`null`', () => {
      assertThat(null, falsy());
    });
    it('the number 0', () => {
      assertThat(0, falsy());
    });
    it('an empty string', () => {
      assertThat('', falsy());
    });
  });
  describe('but not', () => {
    it('an empty object', () => {
      assertThat({}, not(falsy()));
    });
    it('an empty array', () => {
      assertThat([], not(falsy()));
    });
    it('any number', () => {
      assertThat(42, not(falsy()));
    });
    it('an object', () => {
      assertThat({any: 'object'}, not(falsy()));
    });
    it('an `Array`', () => {
      assertThat(Array, not(falsy()));
    });
    it('just about anything', () => {
      assertThat(Math.PI, not(falsy()));
      assertThat(Number.MAX_SAFE_INTEGER, not(falsy()));
      assertThat(new Array(3), not(falsy()));
    });
  });

  it('and returns a hamjest matcher', () => {
    const value = falsy();
    assertThat('Expected to get a hamjest Matcher.', Matcher.isMatcher(value), equalTo(true));
  });
});

<|endoftext|>


<commit_before>/*
test - simple asyncronous unit testing for javascript
*/

/*global TJP*/
//context:browser
//context:console

var test = TJP.test = TJP.test || {};

var oneTimer = TJP.event.oneTimer,
    dispatch = TJP.event.dispatch,
    clone = TJP.base.clone,
    extend = TJP.base.extend;

var PASS = test.PASS = 0,
    FAIL = test.FAIL = 1,
    ERROR = test.ERROR = 2;

var TestComplete = function(result) {
  this.result = result;
  this.name = "TestComplete";
};
TestComplete.prototype = new Error();

var scopeExtension = function(run) {
  return {
    '__run': run,
    'pass': function() { throw new TestComplete(PASS); },
    'fail': function() { throw new TestComplete(FAIL); },
    'assert': function(truth) { if (!truth) throw new TestComplete(FAIL); },
    'setTimeout': function(func, interval) {
      var i,
          scope = this,
          args = Array.prototype.slice.call(arguments);

      args[0] = function() {
        try {
          func.apply(scope, args.slice(2));
        } catch (error) {
          if (error instanceof TestComplete)
            scope.__run.complete(error.result);
          else
            scope.__run.complete(ERROR, error);
        }
      };
      setTimeout.apply(null, args);
    }
  };
};

var TestRun = {
  'suiteMember': false, // default value

  'create': function(test) {
    return clone(this, {'template': test, 'func': test.func, 'done': false});
	},

  'setUp': function(scope) {
    var self = this;

    scope = this.scope = clone(scope);

    this.template.setUp.call(scope);

    oneTimer(this, "finished", function(target, type, data) {
      dispatch(self.template, type, data);
    });

    extend(scope, scopeExtension(this));
	},

  'complete': function(result, error) {
    if (this.done) return;
    this.done = true;

    dispatch(this, "finished", {
      'result': result,
      'passed': !result,
      'time': ((new Date()).getTime() - this.started) / 1000,
      'error': error
    });

    if (!this.suiteMember) this.tearDown();
  },

  'tearDown': function() {
    this.template.tearDown.call(this.scope);

    delete this.scope;
	},

  'run': function() {
    this.started = (new Date()).getTime();

    try {
      this.func.apply(this.scope, this.arguments);
    } catch (error) {
      if (error instanceof TestComplete)
        this.complete(error.result);
      else
        this.complete(ERROR, error);
    }
	}
};

var SuiteRun = {
  'suiteMember': false, // default value

  'create': function(suite) {
    var i,
        child,
        children = [];

    for (i = 0; i < suite.children.length; i++) {
      child = suite.children[i]._makeRun();
      child.suiteMember = true;
      children.push(child);
    }

    return clone(this, {'template': suite, 'children': children});
  },

  'setUp': function(scope) {
    var i,
        self = this,
        suiteSetUp = this.template.setUp,
        child,
        children = this.children,
        run = this; // for closures below

    scope = this.scope = clone(scope);
    this.results = new Array(children.length);
    this.doneCount = 0;

    if (suiteSetUp) suiteSetUp.call(scope);

    for (i = 0; i < children.length; i++) {
      child = children[i];

      (function(i) { // freeze i
        oneTimer(child, "finished", function(target, type, data) {
          run.results[i] = data;
          if (++run.doneCount === children.length)
            run.complete();
        });
      })(i);

      child.setUp(scope);
    }

    oneTimer(this, "finished", function(target, type, data) {
      dispatch(self.template, type, data);
    });
  },

  'complete': function() {
    var i,
        foundFail = false,
        foundError = false,
        error,
        result,
        results = this.results,
        ended = (new Date()).getTime();

    for (i = 0; i < results.length; i++) {
      result = results[i];
      if (result.result === ERROR) {
        foundError = true;
        error = result.error;
        break;
      } else if (result.result === FAIL) {
        foundFail = true;
      }
    }

    dispatch(this, "finished", {
      'result': foundError ? ERROR: (foundFail ? FAIL : PASS),
      'passed': !result,
      'time': (ended - this.started) / 1000,
      'error': foundError ? error : undefined,
      'children': results
    });

    if (!this.suiteMember) this.tearDown();
  },

  'tearDown': function() {
    var i,
        suiteTearDown = this.template.tearDown,
        scope = this.scope,
        children = this.children;

    for (i = 0; i < children.length; i++)
      children[i].tearDown();

    if (suiteTearDown) suiteTearDown.call(scope);

    delete this.scope;
  },

  'run': function() {
    var i,
        children = this.children;

    this.started = (new Date()).getTime();

    for (i = 0; i < children.length; i++) {
      children[i].run();
    }
  }
};

var Runnable = {
  '_makeRun': function() {
    return this._runType.create(this);
  },

  'run': function() {
    var run,
        self = this;

    if (!TJP.event)
      throw new Error("TJP.test requires TJP.event");

    run = this._makeRun();
    run.setUp({});

    run.run();
  }
};

var TestObject = clone(Runnable, {
  '_runType': TestRun,

  'create': function(func, options) {
    options = options || {};

    return clone(this, {
      'func': func,
      'arguments': options.arguments || [],
      'setUp': options.setUp || function() {},
      'tearDown': options.tearDown || function() {}
    });
  }
});

var SuiteObject = clone(Runnable, {
  '_runType': SuiteRun,

  'create': function(children, options) {
    options = options || {};

    return clone(this, {
      'children': (children && children.length) ? children.slice() : [],
      'setUp': options.setUp || function() {},
      'tearDown': options.tearDown || function() {}
    });
  },

  'addChild': function(child) {
    this.children.push(child);
  }
});

test.test = function(func, options) {
  return TestObject.create(func, options);
};

test.suite = function(children, options) {
  return SuiteObject.create(children, options);
};
<commit_msg>don't mix tabs and spaces<commit_after>/*
test - simple asyncronous unit testing for javascript
*/

/*global TJP*/
//context:browser
//context:console

var test = TJP.test = TJP.test || {};

var oneTimer = TJP.event.oneTimer,
    dispatch = TJP.event.dispatch,
    clone = TJP.base.clone,
    extend = TJP.base.extend;

var PASS = test.PASS = 0,
    FAIL = test.FAIL = 1,
    ERROR = test.ERROR = 2;

var TestComplete = function(result) {
  this.result = result;
  this.name = "TestComplete";
};
TestComplete.prototype = new Error();

var scopeExtension = function(run) {
  return {
    '__run': run,
    'pass': function() { throw new TestComplete(PASS); },
    'fail': function() { throw new TestComplete(FAIL); },
    'assert': function(truth) { if (!truth) throw new TestComplete(FAIL); },
    'setTimeout': function(func, interval) {
      var i,
          scope = this,
          args = Array.prototype.slice.call(arguments);

      args[0] = function() {
        try {
          func.apply(scope, args.slice(2));
        } catch (error) {
          if (error instanceof TestComplete)
            scope.__run.complete(error.result);
          else
            scope.__run.complete(ERROR, error);
        }
      };
      setTimeout.apply(null, args);
    }
  };
};

var TestRun = {
  'suiteMember': false, // default value

  'create': function(test) {
    return clone(this, {'template': test, 'func': test.func, 'done': false});
  },

  'setUp': function(scope) {
    var self = this;

    scope = this.scope = clone(scope);

    this.template.setUp.call(scope);

    oneTimer(this, "finished", function(target, type, data) {
      dispatch(self.template, type, data);
    });

    extend(scope, scopeExtension(this));
  },

  'complete': function(result, error) {
    if (this.done) return;
    this.done = true;

    dispatch(this, "finished", {
      'result': result,
      'passed': !result,
      'time': ((new Date()).getTime() - this.started) / 1000,
      'error': error
    });

    if (!this.suiteMember) this.tearDown();
  },

  'tearDown': function() {
    this.template.tearDown.call(this.scope);

    delete this.scope;
  },

  'run': function() {
    this.started = (new Date()).getTime();

    try {
      this.func.apply(this.scope, this.arguments);
    } catch (error) {
      if (error instanceof TestComplete)
        this.complete(error.result);
      else
        this.complete(ERROR, error);
    }
	}
};

var SuiteRun = {
  'suiteMember': false, // default value

  'create': function(suite) {
    var i,
        child,
        children = [];

    for (i = 0; i < suite.children.length; i++) {
      child = suite.children[i]._makeRun();
      child.suiteMember = true;
      children.push(child);
    }

    return clone(this, {'template': suite, 'children': children});
  },

  'setUp': function(scope) {
    var i,
        self = this,
        suiteSetUp = this.template.setUp,
        child,
        children = this.children,
        run = this; // for closures below

    scope = this.scope = clone(scope);
    this.results = new Array(children.length);
    this.doneCount = 0;

    if (suiteSetUp) suiteSetUp.call(scope);

    for (i = 0; i < children.length; i++) {
      child = children[i];

      (function(i) { // freeze i
        oneTimer(child, "finished", function(target, type, data) {
          run.results[i] = data;
          if (++run.doneCount === children.length)
            run.complete();
        });
      })(i);

      child.setUp(scope);
    }

    oneTimer(this, "finished", function(target, type, data) {
      dispatch(self.template, type, data);
    });
  },

  'complete': function() {
    var i,
        foundFail = false,
        foundError = false,
        error,
        result,
        results = this.results,
        ended = (new Date()).getTime();

    for (i = 0; i < results.length; i++) {
      result = results[i];
      if (result.result === ERROR) {
        foundError = true;
        error = result.error;
        break;
      } else if (result.result === FAIL) {
        foundFail = true;
      }
    }

    dispatch(this, "finished", {
      'result': foundError ? ERROR: (foundFail ? FAIL : PASS),
      'passed': !result,
      'time': (ended - this.started) / 1000,
      'error': foundError ? error : undefined,
      'children': results
    });

    if (!this.suiteMember) this.tearDown();
  },

  'tearDown': function() {
    var i,
        suiteTearDown = this.template.tearDown,
        scope = this.scope,
        children = this.children;

    for (i = 0; i < children.length; i++)
      children[i].tearDown();

    if (suiteTearDown) suiteTearDown.call(scope);

    delete this.scope;
  },

  'run': function() {
    var i,
        children = this.children;

    this.started = (new Date()).getTime();

    for (i = 0; i < children.length; i++) {
      children[i].run();
    }
  }
};

var Runnable = {
  '_makeRun': function() {
    return this._runType.create(this);
  },

  'run': function() {
    var run,
        self = this;

    if (!TJP.event)
      throw new Error("TJP.test requires TJP.event");

    run = this._makeRun();
    run.setUp({});

    run.run();
  }
};

var TestObject = clone(Runnable, {
  '_runType': TestRun,

  'create': function(func, options) {
    options = options || {};

    return clone(this, {
      'func': func,
      'arguments': options.arguments || [],
      'setUp': options.setUp || function() {},
      'tearDown': options.tearDown || function() {}
    });
  }
});

var SuiteObject = clone(Runnable, {
  '_runType': SuiteRun,

  'create': function(children, options) {
    options = options || {};

    return clone(this, {
      'children': (children && children.length) ? children.slice() : [],
      'setUp': options.setUp || function() {},
      'tearDown': options.tearDown || function() {}
    });
  },

  'addChild': function(child) {
    this.children.push(child);
  }
});

test.test = function(func, options) {
  return TestObject.create(func, options);
};

test.suite = function(children, options) {
  return SuiteObject.create(children, options);
};
<|endoftext|>


<commit_before>function trak() {
	'use strict';
	console.log('trak loaded');

	/**
	 * dataAttrEvent
	 * This is called when any element with the [data-trak] attribute is clicked.
	 * It calls trak.event()
	 */
	function dataAttrEvent() {
		var _options  = JSON.parse(this.getAttribute('data-trak'));
		var _category = wildcard.call(this, _options.category);
		var _action   = wildcard.call(this, _options.action);
		var _label    = wildcard.call(this, _options.label);
		trak.event(_category, _action, _label);
	}

	var trakElements = document.querySelectorAll('[data-trak]');
	for (var i = 0; i < trakElements.length ; i++) {
		if (trakElements[i].addEventListener) {
			trakElements[i].addEventListener('click', dataAttrEvent, true);
		} else if (trakElements[i].attachEvent) {
			trakElements[i].attachEvent('onclick', dataAttrEvent);
		}
	}

	/**
	 * Function to convert wildcards into real values
	 * @param  string str
	 * @return string     The converted ouput from str
	 */
	function wildcard(str) {
		var output;

		switch(str) {
			case 'page.title':
				output = document.title;
				break;
			case 'page.href':
				output = window.location.href;
				break;
			case 'link.href':
				output = this.href;
				break;
			case 'link.title':
				output = this.title;
				break;
			case 'referrer':
				output = document.referrer ? document.referrer : 'No referrer';
				break;
			default:
				output = str;
				break;
		}
		return output;
	}
}

/**
 * trak.clean()
 * Cleans the input replacing spaces with a specified delimeter (see trak.options) and converts to lower case
 * @param  string str
 * @return string cleaned string
 */
trak.clean = function(str) {
	if (!trak.options.clean) {
		return str;
	} else {
	return str.toString().replace(/\s|'|"/g, this.options.delimeter).toLowerCase();
	}
};

/**
 * trak.event()
 * Wrapper function for various analytics APIs.
 * Enables you to add more than one, or change mid-project without changing anything else in your code
 * @param  string category        The category of the tracking event
 * @param  string action          The action of the tracking event
 * @param  string label           The label of the tracking event
 * @param  number value           Use this to assign a numerical value to a tracked page object
 * @param  boolean nonInteraction Used if trak.options.trackType = 'ga': you might want to send an event, but not impact your bounce rate.
 */
trak.event = function(category, action, label, value, nonInteraction) {
	value          = value || 0;
	nonInteraction = nonInteraction || false;

	if (trak.options.trackType === 'ga' && typeof ga !== 'undefined') {
		ga('send', 'event', trak.clean(category), trak.clean(action), trak.clean(label), value, {'nonInteraction': nonInteraction});

		/**
		 * Could use the below instead:
		 *
		ga('send', {
			'hitType': 'event',
			'eventCategory': trak.clean(category),
			'eventAction': trak.clean(action),
			'eventLabel': trak.clean(label),
			'eventValue': value,
			{
				'nonInteraction': nonInteraction
			}
		});
		*/
	} else if (trak.options.trackType === '_gaq' && typeof _gaq !== 'undefined') {
		_gaq.push(['_trackEvent', trak.clean(category), trak.clean(action), trak.clean(label), value]);
	}

	/**
	 * Add any others that you would like here:
	 */
	trak.options.additionalTypes();

	if (trak.options.debug) {
		console.log('Debug:\n Category:', trak.clean(category), '\n Action:', trak.clean(action), '\n abel:', trak.clean(label), '\n GTM Event name:', eventName);
	}
};

/**
 * trak.options
 * These are the default options for trak.js.
 * To override these, you have to reassign the values to
 * @type {Object}
 */
trak.options = {
	clean     : true, // trak.options.clean     = false
	delimeter : '_', // trak.options.delimeter = '-'
	trackType : 'ga', // trak.options.trackType = 'ga' Available options: ga, _gaq & gtm
	additionalTypes : undefined,
	debug     : true
};<commit_msg>update trak.options messaging<commit_after>function trak() {
	'use strict';
	console.log('trak loaded');

	/**
	 * dataAttrEvent
	 * This is called when any element with the [data-trak] attribute is clicked.
	 * It calls trak.event()
	 */
	function dataAttrEvent() {
		var _options  = JSON.parse(this.getAttribute('data-trak'));
		var _category = wildcard.call(this, _options.category);
		var _action   = wildcard.call(this, _options.action);
		var _label    = wildcard.call(this, _options.label);
		trak.event(_category, _action, _label);
	}

	var trakElements = document.querySelectorAll('[data-trak]');
	for (var i = 0; i < trakElements.length ; i++) {
		if (trakElements[i].addEventListener) {
			trakElements[i].addEventListener('click', dataAttrEvent, true);
		} else if (trakElements[i].attachEvent) {
			trakElements[i].attachEvent('onclick', dataAttrEvent);
		}
	}

	/**
	 * Function to convert wildcards into real values
	 * @param  string str
	 * @return string     The converted ouput from str
	 */
	function wildcard(str) {
		var output;

		switch(str) {
			case 'page.title':
				output = document.title;
				break;
			case 'page.href':
				output = window.location.href;
				break;
			case 'link.href':
				output = this.href;
				break;
			case 'link.title':
				output = this.title;
				break;
			case 'referrer':
				output = document.referrer ? document.referrer : 'No referrer';
				break;
			default:
				output = str;
				break;
		}
		return output;
	}
}

/**
 * trak.clean()
 * Cleans the input replacing spaces with a specified delimeter (see trak.options) and converts to lower case
 * @param  string str
 * @return string cleaned string
 */
trak.clean = function(str) {
	if (!trak.options.clean) {
		return str;
	} else {
	return str.toString().replace(/\s|'|"/g, this.options.delimeter).toLowerCase();
	}
};

/**
 * trak.event()
 * Wrapper function for various analytics APIs.
 * Enables you to add more than one, or change mid-project without changing anything else in your code
 * @param  string category        The category of the tracking event
 * @param  string action          The action of the tracking event
 * @param  string label           The label of the tracking event
 * @param  number value           Use this to assign a numerical value to a tracked page object
 * @param  boolean nonInteraction Used if trak.options.trackType = 'ga': you might want to send an event, but not impact your bounce rate.
 */
trak.event = function(category, action, label, value, nonInteraction) {
	value          = value || 0;
	nonInteraction = nonInteraction || false;

	if (trak.options.trackType === 'ga' && typeof ga !== 'undefined') {
		ga('send', 'event', trak.clean(category), trak.clean(action), trak.clean(label), value, {'nonInteraction': nonInteraction});

		/**
		 * Could use the below instead:
		 *
		ga('send', {
			'hitType': 'event',
			'eventCategory': trak.clean(category),
			'eventAction': trak.clean(action),
			'eventLabel': trak.clean(label),
			'eventValue': value,
			{
				'nonInteraction': nonInteraction
			}
		});
		*/
	} else if (trak.options.trackType === '_gaq' && typeof _gaq !== 'undefined') {
		_gaq.push(['_trackEvent', trak.clean(category), trak.clean(action), trak.clean(label), value]);
	}

	/**
	 * Add any others that you would like here:
	 */
	trak.options.additionalTypes();

	if (trak.options.debug) {
		console.log('Debug:\n Category:', trak.clean(category), '\n Action:', trak.clean(action), '\n abel:', trak.clean(label), '\n GTM Event name:', eventName);
	}
};

/**
 * trak.options
 * These are the default options for trak.js
 * To override them, reassign these values in your code
 * @type {Object}
 */
trak.options = {
	clean     : true, // trak.options.clean     = false
	delimeter : '_', // trak.options.delimeter = '-'
	trackType : 'ga', // trak.options.trackType = 'ga' Available options: ga, _gaq & gtm
	additionalTypes : undefined,
	debug     : true
};<|endoftext|>


<commit_before>function trak() {
	'use strict';

	/**
	 * dataAttrEvent
	 * This is called when any element with the [data-trak] attribute is clicked.
	 * It calls trak.event()
	 */
	function dataAttrEvent() {
		var _options  = JSON.parse(this.getAttribute('data-trak'));
		var _category = wildcard.call(this, _options.category);
		var _action   = wildcard.call(this, _options.action);
		var _label    = wildcard.call(this, _options.label);
		trak.event(_category, _action, _label);
	}

	var trakElements = document.querySelectorAll('[data-trak]');
	for (var i = 0; i < trakElements.length ; i++) {
		if (trakElements[i].addEventListener) {
			trakElements[i].addEventListener('click', dataAttrEvent, true);
		} else if (trakElements[i].attachEvent) {
			trakElements[i].attachEvent('onclick', dataAttrEvent);
		}
	}

	/**
	 * Function to convert wildcards into real values
	 * @param  string str
	 * @return string     The converted ouput from str
	 */
	function wildcard(str) {
		var output;

		switch(str) {
			case 'page.title':
				output = document.title;
				break;
			case 'page.href':
				output = window.location.href;
				break;
			case 'link.href':
				output = this.href;
				break;
			case 'link.title':
				output = this.title;
				break;
			case 'referrer':
				output = document.referrer ? document.referrer : 'No referrer';
				break;
			default:
				output = str;
				break;
		}
		return output;
	}
}

/**
 * trak.clean()
 * Cleans the input replacing spaces with a specified delimeter (see trak.options) and converts to lower case
 * @param  string str
 * @return string cleaned string
 */
trak.clean = function(str) {
	if (!trak.options.clean) {
		return str;
	} else {
		return str.toString().replace(/\s|'|"/g, this.options.delimeter).toLowerCase();
	}
};

/**
 * trak.event()
 * Wrapper function for various analytics APIs.
 * Enables you to add more than one, or change mid-project without changing anything else in your code
 * @param  string category        The category of the tracking event
 * @param  string action          The action of the tracking event
 * @param  string label           The label of the tracking event
 * @param  number value           Use this to assign a numerical value to a tracked page object
 * @param  boolean nonInteraction Used if trak.options.trackType = 'ga': you might want to send an event, but not impact your bounce rate.
 */
trak.event = function(category, action, label, value, nonInteraction, eventName) {
	value          = value || 0;
	nonInteraction = nonInteraction || false;
	eventName      = eventName || false;

	if (trak.options.trackType === 'ga' && typeof ga !== 'undefined') {
		ga('send', 'event', trak.clean(category), trak.clean(action), trak.clean(label), value, {'nonInteraction': nonInteraction});
		if (trak.options.debug) { console.log('ga event fired'); }
		/**
		 * Could use the below instead:
		 *
		ga('send', {
			'hitType': 'event',
			'eventCategory': trak.clean(category),
			'eventAction': trak.clean(action),
			'eventLabel': trak.clean(label),
			'eventValue': value,
			{
				'nonInteraction': nonInteraction
			}
		});
		*/
	} else if (trak.options.trackType === '_gaq' && typeof _gaq !== 'undefined') {
		_gaq.push(['_trackEvent', trak.clean(category), trak.clean(action), trak.clean(label), value]);
		if (trak.options.debug) { console.log('_gaq event fired'); }

	} else if (trak.options.trackType === 'gtm' && typeof dataLayer !== 'undefined') {
		dataLayer.push({
			'event': eventName
		});
		if (trak.options.debug) { console.log('gtm event fired'); }
	}

	/**
	 * Add any others that you would like here:
	 */
	trak.options.additionalTypes();

	if (trak.options.debug) {
		console.log('Debug:\n Category:', trak.clean(category), '\n Action:', trak.clean(action), '\n Label:', trak.clean(label), '\n GTM Event name:', eventName);
	}
};

/**
 * trak.options
 * These are the default options for trak.js
 * To override them, reassign these values in your code
 * @type {Object}
 */
trak.options = {
	clean           : true, // trak.options.clean     = false
	delimeter       : '_',  // trak.options.delimeter = '-'
	trackType       : 'ga', // trak.options.trackType = 'ga' Available options: ga, _gaq & gtm
	additionalTypes : undefined,
	debug           : true
};<commit_msg>add more options to GTM event<commit_after>function trak() {
	'use strict';

	/**
	 * dataAttrEvent
	 * This is called when any element with the [data-trak] attribute is clicked.
	 * It calls trak.event()
	 */
	function dataAttrEvent() {
		var _options  = JSON.parse(this.getAttribute('data-trak'));
		var _category = wildcard.call(this, _options.category);
		var _action   = wildcard.call(this, _options.action);
		var _label    = wildcard.call(this, _options.label);
		trak.event(_category, _action, _label);
	}

	var trakElements = document.querySelectorAll('[data-trak]');
	for (var i = 0; i < trakElements.length ; i++) {
		if (trakElements[i].addEventListener) {
			trakElements[i].addEventListener('click', dataAttrEvent, true);
		} else if (trakElements[i].attachEvent) {
			trakElements[i].attachEvent('onclick', dataAttrEvent);
		}
	}

	/**
	 * Function to convert wildcards into real values
	 * @param  string str
	 * @return string     The converted ouput from str
	 */
	function wildcard(str) {
		var output;

		switch(str) {
			case 'page.title':
				output = document.title;
				break;
			case 'page.href':
				output = window.location.href;
				break;
			case 'link.href':
				output = this.href;
				break;
			case 'link.title':
				output = this.title;
				break;
			case 'referrer':
				output = document.referrer ? document.referrer : 'No referrer';
				break;
			default:
				output = str;
				break;
		}
		return output;
	}
}

/**
 * trak.clean()
 * Cleans the input replacing spaces with a specified delimeter (see trak.options) and converts to lower case
 * @param  string str
 * @return string cleaned string
 */
trak.clean = function(str) {
	if (!trak.options.clean) {
		return str;
	} else {
		return str.toString().replace(/\s|'|"/g, this.options.delimeter).toLowerCase();
	}
};

/**
 * trak.event()
 * Wrapper function for various analytics APIs.
 * Enables you to add more than one, or change mid-project without changing anything else in your code
 * @param  string category        The category of the tracking event
 * @param  string action          The action of the tracking event
 * @param  string label           The label of the tracking event
 * @param  number value           Use this to assign a numerical value to a tracked page object
 * @param  boolean nonInteraction Used if trak.options.trackType = 'ga': you might want to send an event, but not impact your bounce rate.
 */
trak.event = function(category, action, label, value, nonInteraction, eventName) {
	value          = value || 0;
	nonInteraction = nonInteraction || false;
	eventName      = eventName || false;

	if (trak.options.trackType === 'ga' && typeof ga !== 'undefined') {
		ga('send', 'event', trak.clean(category), trak.clean(action), trak.clean(label), value, {'nonInteraction': nonInteraction});
		if (trak.options.debug) { console.log('ga event fired'); }
		/**
		 * Could use the below instead:
		 *
		ga('send', {
			'hitType': 'event',
			'eventCategory': trak.clean(category),
			'eventAction': trak.clean(action),
			'eventLabel': trak.clean(label),
			'eventValue': value,
			{
				'nonInteraction': nonInteraction
			}
		});
		*/
	} else if (trak.options.trackType === '_gaq' && typeof _gaq !== 'undefined') {
		_gaq.push(['_trackEvent', trak.clean(category), trak.clean(action), trak.clean(label), value]);
		if (trak.options.debug) { console.log('_gaq event fired'); }

	} else if (trak.options.trackType === 'gtm' && typeof dataLayer !== 'undefined') {
		dataLayer.push({
			'event': eventName,
			'eventCategory': trak.clean(category),
			'eventAction': trak.clean(action),
			'eventLabel': trak.clean(label),
			'eventValue': value
		});
		if (trak.options.debug) { console.log('gtm event fired'); }
	}

	/**
	 * Add any others that you would like here:
	 */
	trak.options.additionalTypes();

	if (trak.options.debug) {
		console.log('Debug:\n Category:', trak.clean(category), '\n Action:', trak.clean(action), '\n Label:', trak.clean(label), '\n GTM Event name:', eventName);
	}
};

/**
 * trak.options
 * These are the default options for trak.js
 * To override them, reassign these values in your code
 * @type {Object}
 */
trak.options = {
	clean           : true, // trak.options.clean     = false
	delimeter       : '_',  // trak.options.delimeter = '-'
	trackType       : 'ga', // trak.options.trackType = 'ga' Available options: ga, _gaq & gtm
	additionalTypes : undefined,
	debug           : true
};<|endoftext|>


<commit_before>const {Launcher} = require('chrome-launcher')
const path = require('path')

// borrow from: http://qiita.com/saekis/items/c2b41cd8940923863791
function escapeHtml (string) {
  if (typeof string !== 'string') {
    return string
  }
  return string.replace(/[&'`"<>]/g, function (match) {
    return {
      '&': '&',
      '\'': ''',
      '`': '`',
      '"': '"',
      '<': '<',
      '>': '>'
    }[match]
  })
}

function escapeSingleQuote (string) {
  if (typeof string !== 'string') {
    return string
  }
  return string.replace(/'/g, '\\\'')
}

function createChromeLauncher (startingUrl, options) {
  const flags = []
  // TODO: Remove this after chrome60 is released.
  flags.push('--disable-gpu')
  // Lighthouse adds '--disable-setuid-sandbox' flag automatically.
  // The flag causes an error on linux when staring headless chrome.
  // '--no-sandbox' suppresses an error caused by '--disable-setuid-sandbox'.
  if (process.platform === 'linux') {
    flags.push('--no-sandbox')
  }
  if (!options.visible) {
    flags.push('--headless')
    flags.push('--hide-scrollbars')
  }
  if (options.chromeFlags && Array.isArray(options.chromeFlags)) {
    options.chromeFlags.forEach(f => {
      if (f.indexOf('--') === -1) {
        throw new Error('chrome flag must start "--". flag: ' + f)
      }
      flags.push(f)
    })
  }
  if (options.additionalChromeFlags && Array.isArray(options.additionalChromeFlags)) {
    console.warn('[chromy] additionalChromeFlags is deprecated. Use chromeFlags instead of this.')
    options.additionalChromeFlags.forEach(f => {
      if (f.indexOf('--') === -1) {
        throw new Error('chrome flag must start "--". flag: ' + f)
      }
      flags.push(f)
    })
  }
  return new Launcher({
    port: options.port,
    chromePath: options.chromePath,
    chromeFlags: flags,
    startingUrl: startingUrl,
    logLevel: 'error'
  })
}

function completeUrl (url) {
  const reg = new RegExp('^(?:[a-z]+:)?//', 'i');
  if (reg.test(url) || url.indexOf('about:') === 0) {
    return url
  } else {
    return path.join('file://', process.cwd(), url)
  }
}

exports.escapeHtml = escapeHtml
exports.escapeSingleQuote = escapeSingleQuote
exports.createChromeLauncher = createChromeLauncher
exports.completeUrl = completeUrl

<commit_msg>fix lint error<commit_after>const {Launcher} = require('chrome-launcher')
const path = require('path')

// borrow from: http://qiita.com/saekis/items/c2b41cd8940923863791
function escapeHtml (string) {
  if (typeof string !== 'string') {
    return string
  }
  return string.replace(/[&'`"<>]/g, function (match) {
    return {
      '&': '&',
      '\'': ''',
      '`': '`',
      '"': '"',
      '<': '<',
      '>': '>'
    }[match]
  })
}

function escapeSingleQuote (string) {
  if (typeof string !== 'string') {
    return string
  }
  return string.replace(/'/g, '\\\'')
}

function createChromeLauncher (startingUrl, options) {
  const flags = []
  // TODO: Remove this after chrome60 is released.
  flags.push('--disable-gpu')
  // Lighthouse adds '--disable-setuid-sandbox' flag automatically.
  // The flag causes an error on linux when staring headless chrome.
  // '--no-sandbox' suppresses an error caused by '--disable-setuid-sandbox'.
  if (process.platform === 'linux') {
    flags.push('--no-sandbox')
  }
  if (!options.visible) {
    flags.push('--headless')
    flags.push('--hide-scrollbars')
  }
  if (options.chromeFlags && Array.isArray(options.chromeFlags)) {
    options.chromeFlags.forEach(f => {
      if (f.indexOf('--') === -1) {
        throw new Error('chrome flag must start "--". flag: ' + f)
      }
      flags.push(f)
    })
  }
  if (options.additionalChromeFlags && Array.isArray(options.additionalChromeFlags)) {
    console.warn('[chromy] additionalChromeFlags is deprecated. Use chromeFlags instead of this.')
    options.additionalChromeFlags.forEach(f => {
      if (f.indexOf('--') === -1) {
        throw new Error('chrome flag must start "--". flag: ' + f)
      }
      flags.push(f)
    })
  }
  return new Launcher({
    port: options.port,
    chromePath: options.chromePath,
    chromeFlags: flags,
    startingUrl: startingUrl,
    logLevel: 'error'
  })
}

function completeUrl (url) {
  const reg = new RegExp('^(?:[a-z]+:)?//', 'i')
  if (reg.test(url) || url.indexOf('about:') === 0) {
    return url
  } else {
    return path.join('file://', process.cwd(), url)
  }
}

exports.escapeHtml = escapeHtml
exports.escapeSingleQuote = escapeSingleQuote
exports.createChromeLauncher = createChromeLauncher
exports.completeUrl = completeUrl

<|endoftext|>


<commit_before>
  // Default parameters for all charts
  var default_vars = {
    // PUBLIC (set by the user)
    container : "",
    this_chart: null,

    new_data: null,
    time_data: null,

    dev : false,
    id : "id",
    id_var : "id",
    var_group: null,
    data: [],
    links: [],
    var_node_id: 'id', // ID to join the nodes/link data

    title: "",

    // Default dimensions
    margin: {top: 0, right: 0, bottom: 0, left: 0},
    rotate: 0,

    // Default Variables mapping
    var_color: null,
    var_sort_asc: false,

    // Interaction
    highlight: [],
    selection: [],
    filter: [],
    zoom: [],

    time: {
      var_time: null,
      current_time: null,
      parse: function(d) { return d; }
    },

    // TABLE
    columns: [],
    sort_by: {'column': 'name', 'asc': true},

    // DOTPLOT
    x_type: "linear",
    x_scale: [],
    x_ticks: 5,
    x_axis: null,
    x_format: function(d) { return d; },
    x_tickSize: 10,
    x_tickPadding: 0,
    x_tickValues: null,
    x_axis_show: false,
    x_axis_orient: "bottom",
    x_grid_show: false,
    x_text: true,
    x_text_custom: "",
    x_axis_translate: [0, 0],
    x_invert: false,

    // SCATTERPLOT (INCLUDES DOTPLOT)
    y_type: "linear",
    y_scale: [],
    y_ticks: 5,
    y_axis: null,
    y_format: function(d) { return d; },
    y_tickSize: 10,
    y_tickPadding: 0,
    y_tickValues: null,
    y_axis_show: false,
    y_grid_show: false,
    y_text: true,
    y_text_custom: "",
    y_axis_translate: [0, 0],
    y_invert: false,

    r_scale: null,
    r_cutoff: function(d) { return d > 30; },

    // Automatically generate UI elements
    ui: true,

    lang: 'en_US', // 'es_ES, fr_FR'
    locales: {
        'en_US': {
          'complexity': 'Complexity',
          'low': 'Low',
          'high': 'High',
          'export': 'Export',
          'non-export': 'Non-Export',
          'similarity_link': 'Similarity Link'
        },
        'en_EN': {
          'complexity': 'Complexity',
          'low': 'Low',
          'high': 'High',
          'export': 'Export',
          'non-export': 'Non-Export',
          'similarity_link': 'Similarity Link'
        },
        'es_ES': {
          'complexity': 'Complejidad',
          'low': 'Bajo',
          'high': 'Alto',
          'export': 'Exportación',
          'non-export': 'No-Exportación',
          'similarity_link': 'Enlar de similitud'
        },
        'fr_FR': {
          'complexity': 'Complexité',
          'low': 'Basse',
          'high': 'Haute',
          'export': 'Export',
          'non-export': 'Non-Export',
          'similarity_link': 'Lien de similarité'
        }
    },
    // Graphical properties for graphical marks
    color: d3.scale.category20c(),
    size: d3.scale.linear(),

    dispatch: [],
    evt: {register: function() {}, call: function() {} },

    // SVG Container
    svg: null,
    ratio: 0.5, // Visualization aspect ratio

    duration: 1000,
    interpolate: "monotone",

    layout: {},

    padding: 1,
    treemap_mode: 'squarify',

    treemap: {
      padding: 1,
      d_x: 30,
      d_y: 30,
      depth_text: 1,
      depth_rect: 2
    },

    radius: 5,

    radius_min: 2,
    radius_max: 10,

    mark: {
      height: 10,
      width: 10,
      rotate: 0,
      radius: 5,
      fill: function(d) { return vars.color(vars.accessor_values(d)[vars.var_color]); }
    },

    accessor_values: function(d) { return d; },
    accessor_data: function(d) { return d; },
    accessor_items: function(d) { return d; },

    container: "#viz",

    refresh: true,
    init: true,
    _user_vars: {},

    list: {type: ["sparkline", "dotplot", "barchart", "linechart", "scatterplot", "grid",
                  "stacked", "piechart", "slopegraph", "productspace", "treemap", "geomap",
                  "stackedbar", "ordinal_vertical", "ordinal_horizontal", "matrix", "radial", "rectmap"],
      mark: ['rect', 'circle', 'star', 'shape']
    },

    default_params: {},

    z_index: [
 //     {selector: '.mark__group', attribute: '__aggregated', type: 'productspace', weight: 1},
      {selector: '.connect__group', type: 'productspace', weight: 1, event: 'highlightOut'},
      {selector: '.mark__group', type: 'productspace', weight: 1, event: 'highlightOut'},
      {selector: '.connect__group', attribute: '__highlighted', type: 'productspace', weight: 1, event: 'highlightOn'},
      {selector: '.mark__group', attribute: '__highlighted', type: 'productspace', weight: 1, event: 'highlightOn'},
      {selector: '.mark__group', attribute: '__highlighted__adjacent', type: 'productspace', weight: 1, event: 'highlightOn'},
    ],

    set: [],

    scale: 1,
    translate_x: 0,
    translate_y: 0,
    translate: [0, 0],

    flat_scene: false

  };

  vars = vistk.utils.merge(vars, default_vars);
<commit_msg>Adding notification messages<commit_after>
  // Default parameters for all charts
  var default_vars = {
    // PUBLIC (set by the user)
    container : "",
    this_chart: null,

    new_data: null,
    time_data: null,

    dev : false,
    id : "id",
    id_var : "id",
    var_group: null,
    data: [],
    links: [],
    var_node_id: 'id', // ID to join the nodes/link data

    title: "",

    // Default dimensions
    margin: {top: 0, right: 0, bottom: 0, left: 0},
    rotate: 0,

    // Default Variables mapping
    var_color: null,
    var_sort_asc: false,

    // Interaction
    highlight: [],
    selection: [],
    filter: [],
    zoom: [],

    time: {
      var_time: null,
      current_time: null,
      parse: function(d) { return d; }
    },

    // TABLE
    columns: [],
    sort_by: {'column': 'name', 'asc': true},

    // DOTPLOT
    x_type: "linear",
    x_scale: [],
    x_ticks: 5,
    x_axis: null,
    x_format: function(d) { return d; },
    x_tickSize: 10,
    x_tickPadding: 0,
    x_tickValues: null,
    x_axis_show: false,
    x_axis_orient: "bottom",
    x_grid_show: false,
    x_text: true,
    x_text_custom: "",
    x_axis_translate: [0, 0],
    x_invert: false,

    // SCATTERPLOT (INCLUDES DOTPLOT)
    y_type: "linear",
    y_scale: [],
    y_ticks: 5,
    y_axis: null,
    y_format: function(d) { return d; },
    y_tickSize: 10,
    y_tickPadding: 0,
    y_tickValues: null,
    y_axis_show: false,
    y_grid_show: false,
    y_text: true,
    y_text_custom: "",
    y_axis_translate: [0, 0],
    y_invert: false,

    r_scale: null,
    r_cutoff: function(d) { return d > 30; },

    // Automatically generate UI elements
    ui: true,

    lang: 'en_US', // 'es_ES, fr_FR'
    locales: {
        'en_US': {
          'complexity': 'Complexity',
          'low': 'Low',
          'high': 'High',
          'export': 'Export',
          'non-export': 'Non-Export',
          'similarity_link': 'Similarity Link',
          'loading': 'Loading...',
          'no-data': 'No data is available'
        },
        'en_EN': {
          'complexity': 'Complexity',
          'low': 'Low',
          'high': 'High',
          'export': 'Export',
          'non-export': 'Non-Export',
          'similarity_link': 'Similarity Link',
          'loading': 'Loading...',
          'no-data': 'No data is available'
        },
        'es_ES': {
          'complexity': 'Complejidad',
          'low': 'Bajo',
          'high': 'Alto',
          'export': 'Exportación',
          'non-export': 'No-Exportación',
          'similarity_link': 'Enlar de similitud',
          'loading': 'Cargando...',
          'no-data': 'Sin datos disponibles'
        },
        'fr_FR': {
          'complexity': 'Complexité',
          'low': 'Basse',
          'high': 'Haute',
          'export': 'Export',
          'non-export': 'Non-Export',
          'similarity_link': 'Lien de similarité',
          'loading': 'Chargement...',
          'no-data': 'Aucune donnée disponible'
        }
    },
    // Graphical properties for graphical marks
    color: d3.scale.category20c(),
    size: d3.scale.linear(),

    dispatch: [],
    evt: {register: function() {}, call: function() {} },

    // SVG Container
    svg: null,
    ratio: 0.5, // Visualization aspect ratio

    duration: 1000,
    interpolate: "monotone",

    layout: {},

    padding: 1,
    treemap_mode: 'squarify',

    treemap: {
      padding: 1,
      d_x: 30,
      d_y: 30,
      depth_text: 1,
      depth_rect: 2
    },

    radius: 5,

    radius_min: 2,
    radius_max: 10,

    mark: {
      height: 10,
      width: 10,
      rotate: 0,
      radius: 5,
      fill: function(d) { return vars.color(vars.accessor_values(d)[vars.var_color]); }
    },

    accessor_values: function(d) { return d; },
    accessor_data: function(d) { return d; },
    accessor_items: function(d) { return d; },

    container: "#viz",

    refresh: true,
    init: true,
    _user_vars: {},

    list: {type: ["sparkline", "dotplot", "barchart", "linechart", "scatterplot", "grid",
                  "stacked", "piechart", "slopegraph", "productspace", "treemap", "geomap",
                  "stackedbar", "ordinal_vertical", "ordinal_horizontal", "matrix", "radial", "rectmap"],
      mark: ['rect', 'circle', 'star', 'shape']
    },

    default_params: {},

    z_index: [
 //     {selector: '.mark__group', attribute: '__aggregated', type: 'productspace', weight: 1},
      {selector: '.connect__group', type: 'productspace', weight: 1, event: 'highlightOut'},
      {selector: '.mark__group', type: 'productspace', weight: 1, event: 'highlightOut'},
      {selector: '.connect__group', attribute: '__highlighted', type: 'productspace', weight: 1, event: 'highlightOn'},
      {selector: '.mark__group', attribute: '__highlighted', type: 'productspace', weight: 1, event: 'highlightOn'},
      {selector: '.mark__group', attribute: '__highlighted__adjacent', type: 'productspace', weight: 1, event: 'highlightOn'},
    ],

    set: [],

    scale: 1,
    translate_x: 0,
    translate_y: 0,
    translate: [0, 0],

    flat_scene: false

  };

  vars = vistk.utils.merge(vars, default_vars);
<|endoftext|>


<commit_before>import {
  applyProp,
  attr,
  attributes,
  elementClose,
  elementOpen,
  elementOpenEnd,
  elementOpenStart,
  skip,
  text
} from 'incremental-dom';

// Specify an environment for iDOM in case we haven't yet.
if (typeof process === 'undefined') {
  process = { env: { NODE_ENV: 'production' } };
}

const factories = {};
const slotAttributeName = 'slot-name';
const slotElementName = 'slot';

// The "key" and "statics" are specified as arguments in iDOM. For the purposes
// of this API it's simpler to use the attributes object.
attributes.key = attributes.statics = function () {};
attributes.value = applyProp;

// Creates a factory and returns it.
function bind (tname) {
  if (typeof tname === 'function') {
    tname = tname.id || tname.name;
  }

  return factories[tname] = function (attrs, chren) {
    const slot = tname === slotElementName;

    if (typeof attrs === 'object') {
      elementOpenStart(tname, attrs.key, attrs.statics);
      for (let a in attrs) {
        attr(slot && a === 'name' ? slotAttributeName : a, attrs[a]);
      }
      elementOpenEnd();
    } else {
      elementOpen(tname);
      chren = attrs;
    }

    if (slot) {
      skip();
    } else {
      const chrenType = typeof chren;
      if (chrenType === 'function') {
        chren();
      } else if (chrenType === 'string' || chrenType === 'number') {
        text(chren);
      }
    }

    return elementClose(tname);
  };
}

// The default function requries a tag name.
export default function (tname, attrs, chren) {
  return (factories[tname] || bind(tname))(attrs, chren);
}

// Export the Incremental DOM text() function directly as we don't need to do
// any special processing for it.
export { text };

// Create factories for all HTML elements except for ones that match keywords
// such as "var".
export const a = bind('a');
export const abbr = bind('abbr');
export const address = bind('address');
export const area = bind('area');
export const article = bind('article');
export const aside = bind('aside');
export const audio = bind('audio');
export const b = bind('b');
export const base = bind('base');
export const bdi = bind('bdi');
export const bdo = bind('bdo');
export const bgsound = bind('bgsound');
export const blockquote = bind('blockquote');
export const body = bind('body');
export const br = bind('br');
export const button = bind('button');
export const canvas = bind('canvas');
export const caption = bind('caption');
export const cite = bind('cite');
export const code = bind('code');
export const col = bind('col');
export const colgroup = bind('colgroup');
export const command = bind('command');
export const content = bind('content');
export const data = bind('data');
export const datalist = bind('datalist');
export const dd = bind('dd');
export const del = bind('del');
export const details = bind('details');
export const dfn = bind('dfn');
export const dialog = bind('dialog');
export const div = bind('div');
export const dl = bind('dl');
export const dt = bind('dt');
export const element = bind('element');
export const em = bind('em');
export const embed = bind('embed');
export const fieldset = bind('fieldset');
export const figcaption = bind('figcaption');
export const figure = bind('figure');
export const font = bind('font');
export const footer = bind('footer');
export const form = bind('form');
export const h1 = bind('h1');
export const h2 = bind('h2');
export const h3 = bind('h3');
export const h4 = bind('h4');
export const h5 = bind('h5');
export const h6 = bind('h6');
export const head = bind('head');
export const header = bind('header');
export const hgroup = bind('hgroup');
export const hr = bind('hr');
export const html = bind('html');
export const i = bind('i');
export const iframe = bind('iframe');
export const image = bind('image');
export const img = bind('img');
export const input = bind('input');
export const ins = bind('ins');
export const kbd = bind('kbd');
export const keygen = bind('keygen');
export const label = bind('label');
export const legend = bind('legend');
export const li = bind('li');
export const link = bind('link');
export const main = bind('main');
export const map = bind('map');
export const mark = bind('mark');
export const marquee = bind('marquee');
export const menu = bind('menu');
export const menuitem = bind('menuitem');
export const meta = bind('meta');
export const meter = bind('meter');
export const multicol = bind('multicol');
export const nav = bind('nav');
export const nobr = bind('nobr');
export const noembed = bind('noembed');
export const noframes = bind('noframes');
export const noscript = bind('noscript');
export const object = bind('object');
export const ol = bind('ol');
export const optgroup = bind('optgroup');
export const option = bind('option');
export const output = bind('output');
export const p = bind('p');
export const param = bind('param');
export const picture = bind('picture');
export const pre = bind('pre');
export const progress = bind('progress');
export const q = bind('q');
export const rp = bind('rp');
export const rt = bind('rt');
export const rtc = bind('rtc');
export const ruby = bind('ruby');
export const s = bind('s');
export const samp = bind('samp');
export const script = bind('script');
export const section = bind('section');
export const select = bind('select');
export const shadow = bind('shadow');
export const slot = bind(slotElementName);
export const small = bind('small');
export const source = bind('source');
export const span = bind('span');
export const strong = bind('strong');
export const style = bind('style');
export const sub = bind('sub');
export const summary = bind('summary');
export const sup = bind('sup');
export const table = bind('table');
export const tbody = bind('tbody');
export const td = bind('td');
export const template = bind('template');
export const textarea = bind('textarea');
export const tfoot = bind('tfoot');
export const th = bind('th');
export const thead = bind('thead');
export const time = bind('time');
export const title = bind('title');
export const tr = bind('tr');
export const track = bind('track');
export const u = bind('u');
export const ul = bind('ul');
export const video = bind('video');
export const wbr = bind('wbr');
<commit_msg>Allow "skip" to be passed as a property.<commit_after>import {
  applyProp,
  attr,
  attributes,
  elementClose,
  elementOpen,
  elementOpenEnd,
  elementOpenStart,
  skip,
  text
} from 'incremental-dom';

// Specify an environment for iDOM in case we haven't yet.
if (typeof process === 'undefined') {
  process = { env: { NODE_ENV: 'production' } };
}

const factories = {};
const slotAttributeName = 'slot-name';
const slotElementName = 'slot';

// The "key" and "statics" are specified as arguments in iDOM. For the purposes
// of this API it's simpler to use the attributes object.
attributes.key = attributes.skip = attributes.statics = function () {};
attributes.value = applyProp;

// Creates a factory and returns it.
function bind (tname) {
  if (typeof tname === 'function') {
    tname = tname.id || tname.name;
  }

  return factories[tname] = function (attrs, chren) {
    const slot = tname === slotElementName;

    if (typeof attrs === 'object') {
      elementOpenStart(tname, attrs.key, attrs.statics);
      for (let a in attrs) {
        attr(slot && a === 'name' ? slotAttributeName : a, attrs[a]);
      }
      elementOpenEnd();
    } else {
      elementOpen(tname);
      chren = attrs;
    }

    if (slot || attrs.skip) {
      skip();
    } else {
      const chrenType = typeof chren;
      if (chrenType === 'function') {
        chren();
      } else if (chrenType === 'string' || chrenType === 'number') {
        text(chren);
      }
    }

    return elementClose(tname);
  };
}

// The default function requries a tag name.
export default function (tname, attrs, chren) {
  return (factories[tname] || bind(tname))(attrs, chren);
}

// Export the Incremental DOM text() function directly as we don't need to do
// any special processing for it.
export { text };

// Create factories for all HTML elements except for ones that match keywords
// such as "var".
export const a = bind('a');
export const abbr = bind('abbr');
export const address = bind('address');
export const area = bind('area');
export const article = bind('article');
export const aside = bind('aside');
export const audio = bind('audio');
export const b = bind('b');
export const base = bind('base');
export const bdi = bind('bdi');
export const bdo = bind('bdo');
export const bgsound = bind('bgsound');
export const blockquote = bind('blockquote');
export const body = bind('body');
export const br = bind('br');
export const button = bind('button');
export const canvas = bind('canvas');
export const caption = bind('caption');
export const cite = bind('cite');
export const code = bind('code');
export const col = bind('col');
export const colgroup = bind('colgroup');
export const command = bind('command');
export const content = bind('content');
export const data = bind('data');
export const datalist = bind('datalist');
export const dd = bind('dd');
export const del = bind('del');
export const details = bind('details');
export const dfn = bind('dfn');
export const dialog = bind('dialog');
export const div = bind('div');
export const dl = bind('dl');
export const dt = bind('dt');
export const element = bind('element');
export const em = bind('em');
export const embed = bind('embed');
export const fieldset = bind('fieldset');
export const figcaption = bind('figcaption');
export const figure = bind('figure');
export const font = bind('font');
export const footer = bind('footer');
export const form = bind('form');
export const h1 = bind('h1');
export const h2 = bind('h2');
export const h3 = bind('h3');
export const h4 = bind('h4');
export const h5 = bind('h5');
export const h6 = bind('h6');
export const head = bind('head');
export const header = bind('header');
export const hgroup = bind('hgroup');
export const hr = bind('hr');
export const html = bind('html');
export const i = bind('i');
export const iframe = bind('iframe');
export const image = bind('image');
export const img = bind('img');
export const input = bind('input');
export const ins = bind('ins');
export const kbd = bind('kbd');
export const keygen = bind('keygen');
export const label = bind('label');
export const legend = bind('legend');
export const li = bind('li');
export const link = bind('link');
export const main = bind('main');
export const map = bind('map');
export const mark = bind('mark');
export const marquee = bind('marquee');
export const menu = bind('menu');
export const menuitem = bind('menuitem');
export const meta = bind('meta');
export const meter = bind('meter');
export const multicol = bind('multicol');
export const nav = bind('nav');
export const nobr = bind('nobr');
export const noembed = bind('noembed');
export const noframes = bind('noframes');
export const noscript = bind('noscript');
export const object = bind('object');
export const ol = bind('ol');
export const optgroup = bind('optgroup');
export const option = bind('option');
export const output = bind('output');
export const p = bind('p');
export const param = bind('param');
export const picture = bind('picture');
export const pre = bind('pre');
export const progress = bind('progress');
export const q = bind('q');
export const rp = bind('rp');
export const rt = bind('rt');
export const rtc = bind('rtc');
export const ruby = bind('ruby');
export const s = bind('s');
export const samp = bind('samp');
export const script = bind('script');
export const section = bind('section');
export const select = bind('select');
export const shadow = bind('shadow');
export const slot = bind(slotElementName);
export const small = bind('small');
export const source = bind('source');
export const span = bind('span');
export const strong = bind('strong');
export const style = bind('style');
export const sub = bind('sub');
export const summary = bind('summary');
export const sup = bind('sup');
export const table = bind('table');
export const tbody = bind('tbody');
export const td = bind('td');
export const template = bind('template');
export const textarea = bind('textarea');
export const tfoot = bind('tfoot');
export const th = bind('th');
export const thead = bind('thead');
export const time = bind('time');
export const title = bind('title');
export const tr = bind('tr');
export const track = bind('track');
export const u = bind('u');
export const ul = bind('ul');
export const video = bind('video');
export const wbr = bind('wbr');
<|endoftext|>


<commit_before>
function Vec2(x, y) {
  this.x = x
  this.y = y
}

Vec2.prototype.add = function(p) {
  return new Vec2(this.x + p.x, this.y + p.y)
}

Vec2.prototype.sub = function(p) {
  return new Vec2(this.x - p.x, this.y - p.y)
}

Vec2.prototype.scale = function(f) {
  return new Vec2(this.x * f, this.y * f)
}

Vec2.prototype.transform = function(m) {
  return new Vec2(m.a * this.x + m.c * this.y + m.e, m.b * this.x + m.d * this.y + m.f)
}

Vec2.prototype.length = function() {
  return Math.sqrt(this.x * this.x + this.y * this.y)
}

Vec2.prototype.lengthSquared = function() {
  return this.x * this.x + this.y * this.y
}

SVGRect.prototype.topLeft = function() {
  return new Vec2(this.x, this.y)
}
SVGRect.prototype.topRight = function() {
  return new Vec2(this.x + this.width, this.y)
}
SVGRect.prototype.bottomLeft = function() {
  return new Vec2(this.x, this.y + this.height)
}
SVGRect.prototype.bottomRight = function() {
  return new Vec2(this.x + this.width, this.y + this.height)
}

exports = Vec2
<commit_msg>Adjust spacing in vec2<commit_after>
function Vec2(x, y) {
  this.x = x
  this.y = y
}

Vec2.prototype.add = function(p) {
  return new Vec2(this.x + p.x, this.y + p.y)
}
Vec2.prototype.sub = function(p) {
  return new Vec2(this.x - p.x, this.y - p.y)
}
Vec2.prototype.scale = function(f) {
  return new Vec2(this.x * f, this.y * f)
}

Vec2.prototype.transform = function(m) {
  return new Vec2(m.a * this.x + m.c * this.y + m.e, m.b * this.x + m.d * this.y + m.f)
}

Vec2.prototype.length = function() {
  return Math.sqrt(this.x * this.x + this.y * this.y)
}

Vec2.prototype.lengthSquared = function() {
  return this.x * this.x + this.y * this.y
}

SVGRect.prototype.topLeft = function() {
  return new Vec2(this.x, this.y)
}
SVGRect.prototype.topRight = function() {
  return new Vec2(this.x + this.width, this.y)
}
SVGRect.prototype.bottomLeft = function() {
  return new Vec2(this.x, this.y + this.height)
}
SVGRect.prototype.bottomRight = function() {
  return new Vec2(this.x + this.width, this.y + this.height)
}

exports = Vec2
<|endoftext|>


<commit_before>"use strict";

import _ from "underscore";
import $ from "jquery";
import "chart.js";
import "chartjs-plugin-annotation";

import * as model from "./model";
import * as utils from "./utils";

// https://en.wikipedia.org/wiki/Wikipedia:Index_of_United_Kingdom_political_parties_meta_attributes
const PARTY_COLORS = {
    Con: "#0087DC",
    Lab: "#DC241f",
    LD: "#FAA61A",
    DUP: "#D46A4C",
    SNP: "#FFFF00",
    PC: "#008142",
    SDLP: "#99FF66",
    Green: "#6AB023",
    Alliance: "#F6CB2F",
    UUP: "#9999FF",
    UKIP: "#70147A",
    SF: "#008800",
    Speaker: "#444444"
};

const GREY = "#CCCCCC";
function party_color(party) {
    party = model.party_name(party);
    return PARTY_COLORS[party] || GREY;
}

// These are the party names we'll actually show on our charts, in order.
const NAMES = [
    "Con",
    "Lab",
    "LD",
    "UKIP",
    "SNP",
    "Green",
    "Other"
];

const ZEROES = _.map(NAMES, function(name) { return 0; });
const COLORS = _.map(NAMES, function(name) { return party_color(name); });

export class View {

    constructor(div, json) {
        this.div = div;
        this.json = json;
        this.checkbox_map = {};
        this.vote_charts = [];
        this.seat_charts = [];

        let view = this;

        div.find("table").each(function(ix, table) {
            for (let name of NAMES) {
                let tr;
                if (name === "Other") {
                    // No tactical voting checkbox for 'Other'
                    tr = $(`

                        ${name}

`);
                } else {
                    // No tactical voting checkbox for 'Other'
                    tr = $(`

                        ${name}

                        <input type='checkbox' name='${name}'>

`);
                }
                tr.click(function(event) {
                    if (event.target.tagName.toLowerCase() === "input") {
                        // Click was on a checkbox, it will already be handled correctly
                    } else {
                        // Click on another part of the row -- simulate click on checkbox
                        tr.find("input").trigger("click");
                    }
                });
                tr.appendTo(table);
            }
        });

        div.find("input:checkbox").each(function(ix, checkbox) {
            view.checkbox_map[checkbox.name] = checkbox;
        });
        div.find("input:checkbox").change(function(element) {
            view.update();
        });

        div.find("canvas.votes").each(function(ix, canvas) {
            let chart = new Chart(canvas, {
                type: "bar",
                options: {
                    legend: {
                        display: false
                    },
                    scales: {
                        yAxes: [{
                            ticks: {
                                beginAtZero: true,
                                max: 0.5,
                                callback: function(value) {
                                    return (value * 100).toFixed(0) + "%";
                                }
                            }
                        }]
                    },
                    tooltips: {
                        enabled: false
                    },
                    hover: {
                        mode: null
                    },
                },
                data: {
                    labels: NAMES,
                    datasets: [{
                        label: "Votes",
                        data: ZEROES.slice(),
                        backgroundColor: COLORS
                    }]
                }
            });
            view.vote_charts.push(chart);
        });

        div.find("canvas.seats").each(function(ix, canvas) {
            let chart = new Chart(canvas, {
                type: "bar",
                options: {
                    legend: {
                        display: false
                    },
                    scales: {
                        xAxes: [{
                            id: "party-axis",
                            gridLines: {
                                display: false,
                                color: "white",
                                lineWidth: 0
                            },
                            categoryPercentage: 1.0,
                            barPerentage: 0.9
                        }],
                        yAxes: [{
                            id: "seats-axis",
                            display: false,
                            ticks: {
                                beginAtZero: true,
                                max: 400
                            }
                        }]
                    },
                    annotation: {
                        annotations: [{
                            type: "line",
                            mode: "horizontal",
                            scaleID: "seats-axis",
                            borderColor: GREY,
                            borderWidth: 3,
                            borderDash: [3, 3],
                            value: 330,
                            label: {
                                content: "330 needed for majority",
                                enabled: true,
                                position: "right",
                                fontColor: Chart.defaults.global.defaultFontColor,
                                fontStyle: "normal",
                                backgroundColor: "white"
                            }
                        }],
                    },
                    tooltips: {
                        enabled: false
                    },
                    hover: {
                        mode: null
                    },
                },
                data: {
                    labels: NAMES,
                    datasets: [{
                        label: "Seats",
                        data: ZEROES.slice(),
                        backgroundColor: COLORS
                    }]
                }
            });
            view.seat_charts.push(chart);
        });

        div.find("canvas.pie").each(function(ix, canvas) {
            let chart = new Chart(canvas, {
                type: "doughnut",
                options: {
                    legend: {
                        display: false
                    },
                    tooltips: {
                        enabled: false
                    },
                    hover: {
                        mode: null
                    },
                },
                data: {
                    labels: NAMES,
                    datasets: [{
                        label: "Seats",
                        borderWidth: 0,
                        data: ZEROES.slice(),
                        backgroundColor: COLORS
                    }]
                }
            });
            view.seat_charts.push(chart);
        });

        this.update();
    }

    update() {
        let bloc = [];
        for (let party in this.checkbox_map) {
            let checkbox = this.checkbox_map[party];
            if (checkbox.checked) {
                bloc.push(party);
            }
        }

        let data = model.adjust_data_with_tactics(this.json, bloc);

        let vote_map = model.normalize_votes(model.get_votes(data));
        let seat_map = model.get_seats(data);
        let used_seat_map = model.remove_unused_seats(seat_map);
        console.log(seat_map);
        console.log(used_seat_map);

        let votes = model.count_by_party(vote_map, NAMES);
        let seats = model.count_by_party(seat_map, NAMES);
        let used_seats = model.count_by_party(used_seat_map, NAMES);
        console.log(seats);
        console.log(used_seats);

        for (let chart of this.vote_charts) {
            for (let i = 0; i < NAMES.length; ++i) {
                chart.data.datasets[0].data[i] = votes[i];
            }
            chart.update();
        }
        for (let chart of this.seat_charts) {
            for (let i = 0; i < NAMES.length; ++i) {
                let n = used_seats[i];
                if (n > 0) {
                    // HACK: add 1 seat, to ensure that at least one pixel is drawn
                    // The difference between 0 seats and 1 seat is pretty significant!
                    n += 1;
                }
                chart.data.datasets[0].data[i] = n;
            }
            if (chart.options.annotation) {
                let needed = Math.ceil(utils.sum(used_seats) / 2 + 0.01);
                for (let annotation of chart.options.annotation.annotations) {
                    annotation.value = needed + 1; // See HACK above
                    annotation.label.content = needed + " needed for majority";
                }
            }
            chart.update();
        }
    }
}
<commit_msg>Dynamically update % column<commit_after>"use strict";

import _ from "underscore";
import $ from "jquery";
import "chart.js";
import "chartjs-plugin-annotation";

import * as model from "./model";
import * as utils from "./utils";

// https://en.wikipedia.org/wiki/Wikipedia:Index_of_United_Kingdom_political_parties_meta_attributes
const PARTY_COLORS = {
    Con: "#0087DC",
    Lab: "#DC241f",
    LD: "#FAA61A",
    DUP: "#D46A4C",
    SNP: "#FFFF00",
    PC: "#008142",
    SDLP: "#99FF66",
    Green: "#6AB023",
    Alliance: "#F6CB2F",
    UUP: "#9999FF",
    UKIP: "#70147A",
    SF: "#008800",
    Speaker: "#444444"
};

const GREY = "#CCCCCC";
function party_color(party) {
    party = model.party_name(party);
    return PARTY_COLORS[party] || GREY;
}

// These are the party names we'll actually show on our charts, in order.
const NAMES = [
    "Con",
    "Lab",
    "LD",
    "UKIP",
    "SNP",
    "Green",
    "Other"
];

const ZEROES = _.map(NAMES, function(name) { return 0; });
const COLORS = _.map(NAMES, function(name) { return party_color(name); });

export class View {

    constructor(div, json) {
        this.div = div;
        this.json = json;
        this.checkbox_map = {};
        this.vote_charts = [];
        this.seat_charts = [];

        let view = this;

        div.find("table").each(function(ix, table) {
            for (let name of NAMES) {
                let tr;
                if (name === "Other") {
                    // No tactical voting checkbox for 'Other'
                    tr = $(`

                        ${name}

`);
                } else {
                    // No tactical voting checkbox for 'Other'
                    tr = $(`

                        ${name}

                        <input type='checkbox' name='${name}'>

`);
                }
                tr.click(function(event) {
                    if (event.target.tagName.toLowerCase() === "input") {
                        // Click was on a checkbox, it will already be handled correctly
                    } else {
                        // Click on another part of the row -- simulate click on checkbox
                        tr.find("input").trigger("click");
                    }
                });
                tr.appendTo(table);
            }
        });

        div.find("input:checkbox").each(function(ix, checkbox) {
            view.checkbox_map[checkbox.name] = checkbox;
        });
        div.find("input:checkbox").change(function(element) {
            view.update();
        });

        div.find("canvas.votes").each(function(ix, canvas) {
            let chart = new Chart(canvas, {
                type: "bar",
                options: {
                    legend: {
                        display: false
                    },
                    scales: {
                        yAxes: [{
                            ticks: {
                                beginAtZero: true,
                                max: 0.5,
                                callback: function(value) {
                                    return (value * 100).toFixed(0) + "%";
                                }
                            }
                        }]
                    },
                    tooltips: {
                        enabled: false
                    },
                    hover: {
                        mode: null
                    },
                },
                data: {
                    labels: NAMES,
                    datasets: [{
                        label: "Votes",
                        data: ZEROES.slice(),
                        backgroundColor: COLORS
                    }]
                }
            });
            view.vote_charts.push(chart);
        });

        div.find("canvas.seats").each(function(ix, canvas) {
            let chart = new Chart(canvas, {
                type: "bar",
                options: {
                    legend: {
                        display: false
                    },
                    scales: {
                        xAxes: [{
                            id: "party-axis",
                            gridLines: {
                                display: false,
                                color: "white",
                                lineWidth: 0
                            },
                            categoryPercentage: 1.0,
                            barPerentage: 0.9
                        }],
                        yAxes: [{
                            id: "seats-axis",
                            display: false,
                            ticks: {
                                beginAtZero: true,
                                max: 400
                            }
                        }]
                    },
                    annotation: {
                        annotations: [{
                            type: "line",
                            mode: "horizontal",
                            scaleID: "seats-axis",
                            borderColor: GREY,
                            borderWidth: 3,
                            borderDash: [3, 3],
                            value: 330,
                            label: {
                                content: "330 needed for majority",
                                enabled: true,
                                position: "right",
                                fontColor: Chart.defaults.global.defaultFontColor,
                                fontStyle: "normal",
                                backgroundColor: "white"
                            }
                        }],
                    },
                    tooltips: {
                        enabled: false
                    },
                    hover: {
                        mode: null
                    },
                },
                data: {
                    labels: NAMES,
                    datasets: [{
                        label: "Seats",
                        data: ZEROES.slice(),
                        backgroundColor: COLORS
                    }]
                }
            });
            view.seat_charts.push(chart);
        });

        div.find("canvas.pie").each(function(ix, canvas) {
            let chart = new Chart(canvas, {
                type: "doughnut",
                options: {
                    legend: {
                        display: false
                    },
                    tooltips: {
                        enabled: false
                    },
                    hover: {
                        mode: null
                    },
                },
                data: {
                    labels: NAMES,
                    datasets: [{
                        label: "Seats",
                        borderWidth: 0,
                        data: ZEROES.slice(),
                        backgroundColor: COLORS
                    }]
                }
            });
            view.seat_charts.push(chart);
        });

        this.update();
    }

    update() {
        let bloc = [];
        for (let party in this.checkbox_map) {
            let checkbox = this.checkbox_map[party];
            if (checkbox.checked) {
                bloc.push(party);
            }
        }

        let data = model.adjust_data_with_tactics(this.json, bloc);

        let vote_map = model.normalize_votes(model.get_votes(data));
        let seat_map = model.get_seats(data);
        let used_seat_map = model.remove_unused_seats(seat_map);
        console.log(seat_map);
        console.log(used_seat_map);

        let votes = model.count_by_party(vote_map, NAMES);
        let seats = model.count_by_party(seat_map, NAMES);
        let used_seats = model.count_by_party(used_seat_map, NAMES);
        console.log(seats);
        console.log(used_seats);

        for (let chart of this.vote_charts) {
            for (let i = 0; i < NAMES.length; ++i) {
                chart.data.datasets[0].data[i] = votes[i];
            }
            chart.update();
        }
        for (let chart of this.seat_charts) {
            for (let i = 0; i < NAMES.length; ++i) {
                let n = used_seats[i];
                if (n > 0) {
                    // HACK: add 1 seat, to ensure that at least one pixel is drawn
                    // The difference between 0 seats and 1 seat is pretty significant!
                    // (This doesn't affect the actual results, just the visualization)
                    n += 1;
                }
                chart.data.datasets[0].data[i] = n;
            }
            if (chart.options.annotation) {
                let needed = Math.ceil(utils.sum(used_seats) / 2 + 0.01);
                for (let annotation of chart.options.annotation.annotations) {
                    annotation.value = needed + 1; // See HACK above
                    annotation.label.content = needed + " needed for majority";
                }
            }
            chart.update();
        }
        this.div.find("tr").each(function(ix, tr) {
            let party = tr.id;
            $(tr).find("td.vote").each(function(ix, td) {
                let oldText = $(td).text();
                let newText = (100 * vote_map[party]).toFixed(1) + "%";
                console.log(oldText);
                console.log(newText);
                if (!(oldText === newText)) {
                    console.log("Updating " + td);
                    $(td).fadeOut(200, function() {
                        $(td).text(newText).fadeIn(200);
                    });
                }
            });
        });
    }
}
<|endoftext|>