<commit_before>const {Cu, Cc, Ci} = require('chrome');
Cu.import('resource://gre/modules/Preferences.jsm'); /* globals Preferences */
Cu.import('resource://gre/modules/Task.jsm'); /* globals Task */
const {CanonicalJSON} = Cu.import('resource://gre/modules/CanonicalJSON.jsm');
const {prefs} = require('sdk/simple-prefs');

const {Http} = require('./Http.js');
const {Log} = require('./Log.js');

exports.NormandyApi = {
  apiCall(method, endpoint, data={}) {
    const api_url = prefs.api_url;
    let url = `${api_url}/${endpoint}`;
    method = method.toLowerCase();
    if (method === 'get') {
      let params = [];
      for (let key in data) {
        params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
      }
      url += '?' + params.join('&');
      data = undefined;
    }
    const headers = {'Accept': 'application/json'};
    return Http[method]({url, data, headers});
  },

  get(endpoint, data) {
    return this.apiCall('get', endpoint, data);
  },

  post(endpoint, data) {
    return this.apiCall('post', endpoint, data);
  },

  fetchRecipes: Task.async(function* (filters={}) {
    const rawText = (yield this.get('recipe/signed/', filters)).text;
    const recipesWithSigs = JSON.parse(rawText);

    const verifiedRecipes = [];

    for (let {recipe, signature: {signature, x5u}} of recipesWithSigs) {
      const serialized = CanonicalJSON.stringify(recipe);
      if (rawText.indexOf(serialized) === -1) {
        throw new Error('Canonical recipe serialization does not match!');
      }

      let certChain = (yield Http.get({url: x5u})).text;

      const verifier = Cc['@mozilla.org/security/contentsignatureverifier;1']
        .createInstance(Ci.nsIContentSignatureVerifier);

      if (!verifier.verifyContentSignature(serialized, signature, certChain, 'normandy.content-signature.mozilla.org')) {
        throw new Error('Recipe signature is not valid');
      } else {
        verifiedRecipes.push(recipe);
      }
    }

    return verifiedRecipes;
  }),

  /**
   * Fetch metadata about this client determined by the server.
   * @return {object} Metadata specified by the server
   */
  classifyClient() {
    return this.get('classify_client/')
    .then(req => req.json)
    .then(clientData => {
      clientData.request_time = new Date(clientData.request_time);
      return clientData;
    });
  },

  fetchAction(name) {
    return this.get(`action/${name}/`).then(req => req.json);
  },
};
<commit_msg>Expect bare signatures from Normandy API, not content-signature encoded signatures<commit_after>const {Cu, Cc, Ci} = require('chrome');
Cu.import('resource://gre/modules/Preferences.jsm'); /* globals Preferences */
Cu.import('resource://gre/modules/Task.jsm'); /* globals Task */
const {CanonicalJSON} = Cu.import('resource://gre/modules/CanonicalJSON.jsm');
const {prefs} = require('sdk/simple-prefs');

const {Http} = require('./Http.js');
const {Log} = require('./Log.js');

exports.NormandyApi = {
  apiCall(method, endpoint, data={}) {
    const api_url = prefs.api_url;
    let url = `${api_url}/${endpoint}`;
    method = method.toLowerCase();
    if (method === 'get') {
      let params = [];
      for (let key in data) {
        params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
      }
      url += '?' + params.join('&');
      data = undefined;
    }
    const headers = {'Accept': 'application/json'};
    return Http[method]({url, data, headers});
  },

  get(endpoint, data) {
    return this.apiCall('get', endpoint, data);
  },

  post(endpoint, data) {
    return this.apiCall('post', endpoint, data);
  },

  fetchRecipes: Task.async(function* (filters={}) {
    const rawText = (yield this.get('recipe/signed/', filters)).text;
    const recipesWithSigs = JSON.parse(rawText);

    const verifiedRecipes = [];

    for (let {recipe, signature: {signature, x5u}} of recipesWithSigs) {
      const serialized = CanonicalJSON.stringify(recipe);
      if (rawText.indexOf(serialized) === -1) {
        throw new Error('Canonical recipe serialization does not match!');
      }

      let certChain = (yield Http.get({url: x5u})).text;
      let builtSignature = `x5u="${x5u}";p384ecdsa=${signature}`;

      const verifier = Cc['@mozilla.org/security/contentsignatureverifier;1']
        .createInstance(Ci.nsIContentSignatureVerifier);

      if (!verifier.verifyContentSignature(serialized, builtSignature, certChain, 'normandy.content-signature.mozilla.org')) {
        throw new Error('Recipe signature is not valid');
      } else {
        verifiedRecipes.push(recipe);
      }
    }

    return verifiedRecipes;
  }),

  /**
   * Fetch metadata about this client determined by the server.
   * @return {object} Metadata specified by the server
   */
  classifyClient() {
    return this.get('classify_client/')
    .then(req => req.json)
    .then(clientData => {
      clientData.request_time = new Date(clientData.request_time);
      return clientData;
    });
  },

  fetchAction(name) {
    return this.get(`action/${name}/`).then(req => req.json);
  },
};
<|endoftext|>


<commit_before>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

require('node-path')(module);

/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var Comment = mongoose.model('Comment');
var utils = require('lib/utils');
var pluck = utils.pluck;
var config = require('lib/config');
var t = require('t-component');
var log = require('debug')('democracyos:db-api:comment');

/**
 * Get all comments
 *
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @return {Module} `comment` module
 * @api public
 */

exports.all = function all(fn) {
  log('Looking for all comments.')

  Comment.find(function (err, comments) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    log('Delivering comments %j', pluck(comments, 'id'));
    fn(null, proposals);
  });

  return this;
};

/**
 * Create comment for `proposal` by `author`
 * with `text`
 *
 * @param {String} proposal to submit comment
 * @param {Object} comment comment vars like `text` and `author`
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.create = function create(comment, fn) {
  log('Creating new comment %j for %s %s', comment.text, comment.context, comment.reference);

  var comment = new Comment(comment);

  comment.save(function (err) {
    if (err) {
      log('Found error %s', err);
      return fn(err);
    };
    comment.populate('author', function(err) {
      if (err) {
        log('Found error %s', err)
        return fn(err);
      };

      log('Delivering comment %j', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Get comments for proposal
 *
 * @param {String} proposal to get comments from
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.getFor = function getFor(query, paging, fn) {
  log('Looking for comments for %s %s', query.context, query.reference);

  paging = paging || { page: 0, limit: config('comments per page'), sort: 'score', exclude_user: null };

  Comment
  .find(query)
  .populate('author', 'id firstName lastName fullName email')
  .sort(paging.sort || 'score')
  .skip(paging.page * paging.limit || 0)
  .limit(paging.limit || config('comments per page'))
  .exec(function(err, comments) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    log('Delivering comments %j', pluck(comments, 'id'));
    fn(null, comments);
  });
};

/**
 * Get replies for comment
 *
 * @param {String} id
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.replies = function replies(id, fn) {
  log('Looking for replies for comment %s', id);

  Comment
  .findOne({ _id: id })
  .populate('replies.author', 'id firstName lastName fullName email')
  .exec(function(err, comment) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    var replies = comment && comment.replies ? comment.replies : [];

    log('Delivering replies %j', pluck(replies, 'id'));
    fn(null, replies);
  });
};

/**
 * Reply to comment
 *
 * @param {String} commentId to attach reply
 * @param {Object} reply object with params
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.reply = function reply(commentId, reply, fn) {
  log('Looking for comment %s to reply with %j', commentId, reply);

  Comment.findById(commentId, function(err, comment) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    log('Creating reply %j for comment %j', reply, comment);
    var doc = comment.replies.create(reply);
    comment.replies.push(doc);

    comment.save(function(err, saved) {
      if (err) {
        log('Found error %j', err)
      };

      comment
      .populate('replies.author', 'id firstName lastName fullName email', function(err) {
        if (err) {
          log('Found error %j', err);
          return fn(err);
        };

        reply = comment.replies.id(doc.id);

        log('Delivering reply %s', reply.id);
        fn(null, reply);
      });

    });
  });
};

/**
 * Edit a reply
 *
 * @param {Object} comment to attach reply
 * @param {Object} reply object with params
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.editReply = function editReply(comment, reply, fn) {
  log('Looking for comment %s to reply with %s', comment.id, reply.id);

  reply.editedAt = Date.now();

  Comment.update(
    { _id: comment.id, 'replies._id': reply.id },
    { $set: { 'replies.$.text': reply.text, 'replies.$.editedAt': reply.editedAt } },
    function (err) {
      if (err) {
        log('Found error %j', err);
        return fn(err);
      }

      log('Delivering reply %s', reply.id);
      fn(null, reply);
  });
};

/**
 * Upvote comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comment' list items found or `undefined`
 * @api public
 */

exports.upvote = function upvote(id, citizen, fn) {
  Comment.findById(id).populate('author').exec(function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    if (comment.author.id == citizen.id) {
      log('Author %s tried to vote their own comment %s', citizen.id, comment.id);
      return fn(t('You\'re not allowed to vote your own argument'), comment);
    }

    log('Upvoting comment %s', comment.id);
    comment.vote(citizen, 'positive', function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Downvote comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.downvote = function downvote(id, citizen, fn) {
  Comment.findById(id).populate('author').exec(function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    if (comment.author.id == citizen.id) {
      log('Author %s tried to vote their own comment %s', citizen.id, comment.id);
      return fn(t('You\'re not allowed to vote your own argument'), comment);
    }

    log('Downvoting comment %s', comment.id);
    comment.vote(citizen, 'negative', function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Flag comment as spam
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comment' list items found or `undefined`
 * @api public
 */

exports.flag = function flag(id, citizen, fn) {
  Comment.findById(id, function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    log('Upvoting comment %s', comment.id);
    comment.flag(citizen, 'spam', function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Unflag comment as spam
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.unflag = function unflag(id, citizen, fn) {
  Comment.findById(id, function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    log('Downvoting comment %s', comment.id);
    comment.unflag(citizen, function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Edit comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 * @api public
 */

exports.edit = function edit(comment, fn) {
  log('Updating comment %s', comment.id);

  comment.save(function (err, comment) {
    if (!err) return log('Updated comment %s', comment.id), fn(null, comment);
    return log('Found error %s', err), fn(err);
  });

  return this;
};

/**
 * Remove comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 * @api public
 */

exports.remove = function remove(comment, fn) {
  comment.remove(function(err) {
    if (err) return log('Found error %s', err), fn(err);

    log('Comment %s removed', comment.id);
    fn(null);
  });
};<commit_msg>Fix loading comments per page<commit_after>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

require('node-path')(module);

/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var Comment = mongoose.model('Comment');
var utils = require('lib/utils');
var pluck = utils.pluck;
var config = require('lib/config');
var t = require('t-component');
var log = require('debug')('democracyos:db-api:comment');

/**
 * Get all comments
 *
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @return {Module} `comment` module
 * @api public
 */

exports.all = function all(fn) {
  log('Looking for all comments.')

  Comment.find(function (err, comments) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    log('Delivering comments %j', pluck(comments, 'id'));
    fn(null, proposals);
  });

  return this;
};

/**
 * Create comment for `proposal` by `author`
 * with `text`
 *
 * @param {String} proposal to submit comment
 * @param {Object} comment comment vars like `text` and `author`
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.create = function create(comment, fn) {
  log('Creating new comment %j for %s %s', comment.text, comment.context, comment.reference);

  var comment = new Comment(comment);

  comment.save(function (err) {
    if (err) {
      log('Found error %s', err);
      return fn(err);
    };
    comment.populate('author', function(err) {
      if (err) {
        log('Found error %s', err)
        return fn(err);
      };

      log('Delivering comment %j', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Get comments for proposal
 *
 * @param {String} proposal to get comments from
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.getFor = function getFor(query, paging, fn) {
  log('Looking for comments for %s %s', query.context, query.reference);

  paging = paging || { page: 0, limit: config('comments per page'), sort: 'score', exclude_user: null };

  Comment
  .find(query)
  .populate('author', 'id firstName lastName fullName email')
  .sort(paging.sort || 'score')
  .skip(paging.page * paging.limit)
  .limit(paging.limit)
  .exec(function(err, comments) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    log('Delivering comments %j', pluck(comments, 'id'));
    fn(null, comments);
  });
};

/**
 * Get replies for comment
 *
 * @param {String} id
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.replies = function replies(id, fn) {
  log('Looking for replies for comment %s', id);

  Comment
  .findOne({ _id: id })
  .populate('replies.author', 'id firstName lastName fullName email')
  .exec(function(err, comment) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    var replies = comment && comment.replies ? comment.replies : [];

    log('Delivering replies %j', pluck(replies, 'id'));
    fn(null, replies);
  });
};

/**
 * Reply to comment
 *
 * @param {String} commentId to attach reply
 * @param {Object} reply object with params
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.reply = function reply(commentId, reply, fn) {
  log('Looking for comment %s to reply with %j', commentId, reply);

  Comment.findById(commentId, function(err, comment) {
    if (err) {
      log('Found error %j', err);
      return fn(err);
    };

    log('Creating reply %j for comment %j', reply, comment);
    var doc = comment.replies.create(reply);
    comment.replies.push(doc);

    comment.save(function(err, saved) {
      if (err) {
        log('Found error %j', err)
      };

      comment
      .populate('replies.author', 'id firstName lastName fullName email', function(err) {
        if (err) {
          log('Found error %j', err);
          return fn(err);
        };

        reply = comment.replies.id(doc.id);

        log('Delivering reply %s', reply.id);
        fn(null, reply);
      });

    });
  });
};

/**
 * Edit a reply
 *
 * @param {Object} comment to attach reply
 * @param {Object} reply object with params
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.editReply = function editReply(comment, reply, fn) {
  log('Looking for comment %s to reply with %s', comment.id, reply.id);

  reply.editedAt = Date.now();

  Comment.update(
    { _id: comment.id, 'replies._id': reply.id },
    { $set: { 'replies.$.text': reply.text, 'replies.$.editedAt': reply.editedAt } },
    function (err) {
      if (err) {
        log('Found error %j', err);
        return fn(err);
      }

      log('Delivering reply %s', reply.id);
      fn(null, reply);
  });
};

/**
 * Upvote comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comment' list items found or `undefined`
 * @api public
 */

exports.upvote = function upvote(id, citizen, fn) {
  Comment.findById(id).populate('author').exec(function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    if (comment.author.id == citizen.id) {
      log('Author %s tried to vote their own comment %s', citizen.id, comment.id);
      return fn(t('You\'re not allowed to vote your own argument'), comment);
    }

    log('Upvoting comment %s', comment.id);
    comment.vote(citizen, 'positive', function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Downvote comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.downvote = function downvote(id, citizen, fn) {
  Comment.findById(id).populate('author').exec(function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    if (comment.author.id == citizen.id) {
      log('Author %s tried to vote their own comment %s', citizen.id, comment.id);
      return fn(t('You\'re not allowed to vote your own argument'), comment);
    }

    log('Downvoting comment %s', comment.id);
    comment.vote(citizen, 'negative', function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Flag comment as spam
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comment' list items found or `undefined`
 * @api public
 */

exports.flag = function flag(id, citizen, fn) {
  Comment.findById(id, function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    log('Upvoting comment %s', comment.id);
    comment.flag(citizen, 'spam', function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Unflag comment as spam
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 *   - 'comments' list items found or `undefined`
 * @api public
 */

exports.unflag = function unflag(id, citizen, fn) {
  Comment.findById(id, function(err, comment) {
    if (err) return log('Found error %s', err), fn(err);

    log('Downvoting comment %s', comment.id);
    comment.unflag(citizen, function(err) {
      if (err) return log('Found error %s', err), fn(err);

      log('Delivering comment %s', comment.id);
      fn(null, comment);
    });
  });
};

/**
 * Edit comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 * @api public
 */

exports.edit = function edit(comment, fn) {
  log('Updating comment %s', comment.id);

  comment.save(function (err, comment) {
    if (!err) return log('Updated comment %s', comment.id), fn(null, comment);
    return log('Found error %s', err), fn(err);
  });

  return this;
};

/**
 * Remove comment
 *
 * @param {String} id
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} fn callback function
 *   - 'err' error found while process or `null`
 * @api public
 */

exports.remove = function remove(comment, fn) {
  comment.remove(function(err) {
    if (err) return log('Found error %s', err), fn(err);

    log('Comment %s removed', comment.id);
    fn(null);
  });
};<|endoftext|>


<commit_before>var assert = require('chai').assert;
var dispatcher = require('../');

describe('channelDispatcher', function() {
  describe('#subscribe()', function() {
    it('should return subscriber ident as a string value', function() {
      var callback = function() {};
      var ident = dispatcher.subscribe('test', callback);
      assert(typeof ident === "string");
    });

    it('should return undefined when callback is not a function', function() {
      var ident1 = dispatcher.subscribe('test', null);
      var ident2 = dispatcher.subscribe('test', 1);
      var ident3 = dispatcher.subscribe('test', "123");
      var ident4 = dispatcher.subscribe('test', {});
      assert(typeof ident1 === "undefined");
      assert(typeof ident2 === "undefined");
      assert(typeof ident3 === "undefined");
      assert(typeof ident4 === "undefined");
    });
  });

  describe('#unsubscribe()', function() {
    it('shouldn\'t thow error with valid ident', function() {
      var ident = dispatcher.subscribe('test', function() {});
      dispatcher.unsubscribe('test', ident);
    });

    it('shouldn\'t thow error with invalid ident', function() {
      dispatcher.unsubscribe('test', "ident");
      dispatcher.unsubscribe('test', 123);
      dispatcher.unsubscribe('test', null);
    });
  });

  describe('#publish()', function() {
    it('calls subscribed callbacks with passed data', function(done) {
      var ident = dispatcher.subscribe('test', function(data) {
        assert.equal(data, 'success');
        dispatcher.unsubscribe('test', ident);
        done();
      });
      dispatcher.publish('test', 'success');
    });

    it('calls subscribed callbacks with correct context', function(done) {
      var ident = dispatcher.subscribe('test', function() {
        assert.equal(this.value, "success");
        dispatcher.unsubscribe('test', ident);
        done();
      }, {value: "success"});
      dispatcher.publish('test', 'success');
    });

  });
});
<commit_msg>Added tests for dispatcher.publish() and dispatcher.getChannel()<commit_after>var assert = require('chai').assert;
var dispatcher = require('../');

describe('channelDispatcher', function() {
  describe('#subscribe()', function() {
    it('should return subscriber ident as a string value', function() {
      var callback = function() {};
      var ident = dispatcher.subscribe('test', callback);
      assert(typeof ident === "string");
    });

    it('should return undefined when callback is not a function', function() {
      var ident1 = dispatcher.subscribe('test', null);
      var ident2 = dispatcher.subscribe('test', 1);
      var ident3 = dispatcher.subscribe('test', "123");
      var ident4 = dispatcher.subscribe('test', {});
      assert(typeof ident1 === "undefined");
      assert(typeof ident2 === "undefined");
      assert(typeof ident3 === "undefined");
      assert(typeof ident4 === "undefined");
    });
  });

  describe('#unsubscribe()', function() {
    it('shouldn\'t thow error with valid ident', function() {
      var ident = dispatcher.subscribe('test', function() {});
      dispatcher.unsubscribe('test', ident);
    });

    it('shouldn\'t thow error with invalid ident', function() {
      dispatcher.unsubscribe('test', "ident");
      dispatcher.unsubscribe('test', 123);
      dispatcher.unsubscribe('test', null);
    });
  });

  describe('#publish()', function() {
    it('calls subscribed callbacks with passed data', function(done) {
      var ident = dispatcher.subscribe('test', function(data) {
        assert.equal(data, 'success');
        dispatcher.unsubscribe('test', ident);
        done();
      });
      dispatcher.publish('test', 'success');
    });

    it('calls subscribed callbacks with correct context', function(done) {
      var ident = dispatcher.subscribe('test', function() {
        assert.equal(this.value, "success");
        dispatcher.unsubscribe('test', ident);
        done();
      }, {value: "success"});
      dispatcher.publish('test', 'success');
    });

    it('shouldn\'t throw error if there is no subcribers', function() {
      dispatcher.publish('test', "data");
    });
  });

  describe('#getChannel', function() {
    it('should return fully functional channel object', function() {
      var channel = dispatcher.getChannel('test');
      assert.equal(typeof channel.subscribe, 'function');
      assert.equal(typeof channel.unsubscribe, 'function');
      assert.equal(typeof channel.publish, 'function');
    });
  });
});
<|endoftext|>


<commit_before>import { Putro } from 'MAINTAINERS';

export default [
  {
    date: new Date('2017-12-22'),
    changes: 'Updated to the new checklist format, and updated AlwaysBeCasting thresholds',
    contributors: [Putro],
  },
  {
    date: new Date('2017-12-04'),
    changes: 'Added  talents and traits. Moved them into a singular box to improve visibility.',
    contributors: [Putro],
  },
  {
    date: new Date('2017-12-03'),
    changes: 'Upgraded spec completeness to good, added t192p support, added t21 support and added a suggestion for Killer Cobra',
    contributors: [Putro],
  },
  {
    date: new Date('2017-11-01'),
    changes: 'Added Mark of the claw, Bestial Wrath modules, Dire Beast modules, Qapla module, Titan\'s Thunder module, Killer Cobra module.',
    contributors: [Putro],
  },
  {
    date: new Date('2017-10-29'),
    changes: 'Added base files for Beast Mastery.',
    contributors: [Putro],
  },
];
<commit_msg>update changelog<commit_after>import React from 'react';

import { Putro } from 'MAINTAINERS';
import SpellLink from 'common/SpellLink';
import Wrapper from 'common/Wrapper';
import SPELLS from 'common/SPELLS';

export default [
  {
    date: new Date('2017-12-22'),
    changes: <Wrapper>Added support for <SpellLink id={SPELLS.DIRE_FRENZY_TALENT.id} icon /> and <SpellLink id={SPELLS.ASPECT_OF_THE_WILD.id} icon /> and gave them suggestions. </Wrapper>,
    contributors: [Putro],
  },
  {
    date: new Date('2017-12-22'),
    changes: 'Updated to the new checklist format, and updated AlwaysBeCasting thresholds',
    contributors: [Putro],
  },
  {
    date: new Date('2017-12-04'),
    changes: 'Added  talents and traits. Moved them into a singular box to improve visibility.',
    contributors: [Putro],
  },
  {
    date: new Date('2017-12-03'),
    changes: 'Upgraded spec completeness to good, added t192p support, added t21 support and added a suggestion for Killer Cobra',
    contributors: [Putro],
  },
  {
    date: new Date('2017-11-01'),
    changes: 'Added Mark of the claw, Bestial Wrath modules, Dire Beast modules, Qapla module, Titan\'s Thunder module, Killer Cobra module.',
    contributors: [Putro],
  },
  {
    date: new Date('2017-10-29'),
    changes: 'Added base files for Beast Mastery.',
    contributors: [Putro],
  },
];
<|endoftext|>


<commit_before>import R from "ramda";

export default class Mutator{
    constructor(sourceTree, callback, path=[]){
        this._sourceTree = sourceTree;
        this._callback = callback;
        this.path = path;
    }

    in = (innerPath)=> new Mutator(this._sourceTree, this._callback, [...this.path, innerPath]);

    getValue = ()=>{
            var [innerPath, key] = R.splitAt(-1,this.path);
            return R.reduce((acc,x)=>acc[x], this.sourceTree, innerPath)[key];
        };

    updateValue = (newValue) =>{
            console.log(`updating value:${this.path} to ${newValue}`);
            var clonedTree = R.clone(this._sourceTree);
            var [innerPath, [key]] = R.splitAt(-1,this.path);
            R.reduce((acc,x)=>acc[x], clonedTree, innerPath)[key] = newValue;
            console.log(clonedTree);
            this._callback(clonedTree);
            return new Mutator(clonedTree, this._callback, this.path);
        }
     updateKey = (newKey) =>{
            console.log(`updating key:${this.path} to ${newKey}`);
            var [innerPath, [container, key]] = R.splitAt(-2,this.path);
            if (newKey === key) return this;
            var clonedTree = R.clone(this._sourceTree);
            var root = R.reduce((acc,x)=>acc[x], clonedTree, innerPath);
            root[container] = R.fromPairs(R.toPairs(root[container]).map(([k,v])=>[k === key ? newKey : k, v]));
            this._callback(clonedTree);
            return new Mutator(clonedTree, this._callback, [...innerPath, container, newKey]);
        }
}
<commit_msg>add delete mutation<commit_after>import R from "ramda";

export default class Mutator{
    constructor(sourceTree, callback, path=[]){
        this._sourceTree = sourceTree;
        this._callback = callback;
        this.path = path;
    }

    in = (innerPath)=> new Mutator(this._sourceTree, this._callback, [...this.path, innerPath]);

    getValue = ()=>{
            var [innerPath, key] = R.splitAt(-1,this.path);
            return R.reduce((acc,x)=>acc[x], this.sourceTree, innerPath)[key];
        };

    updateValue = newValue =>{
            console.log(`updating value:${this.path} to ${newValue}`);
            var clonedTree = R.clone(this._sourceTree);
            var [innerPath, [key]] = R.splitAt(-1,this.path);
            R.reduce((acc,x)=>acc[x], clonedTree, innerPath)[key] = newValue;
            console.log(clonedTree);
            this._callback(clonedTree);
            return new Mutator(clonedTree, this._callback, this.path);
        }
     updateKey = newKey =>{
            console.log(`updating key:${this.path} to ${newKey}`);
            var [innerPath, [container, key]] = R.splitAt(-2,this.path);
            if (newKey === key) return this;
            var clonedTree = R.clone(this._sourceTree);
            var root = R.reduce((acc,x)=>acc[x], clonedTree, innerPath);
            root[container] = R.fromPairs(R.toPairs(root[container]).map(([k,v])=>[k === key ? newKey : k, v]));
            this._callback(clonedTree);
            return new Mutator(clonedTree, this._callback, [...innerPath, container, newKey]);
        }
     delete =  () =>{
         console.log(`deleting key:${this.path}`);
         var [innerPath, [key]] = R.splitAt(-1,this.path);
         var clonedTree = R.clone(this._sourceTree);
         var container = R.reduce((acc,x)=>acc[x], clonedTree, innerPath);
         delete container[key];
         this._callback(clonedTree);
         return new Mutator(clonedTree, this._callback, innerPath);
        }
}
<|endoftext|>


<commit_before>'use strict';

require('should');
var anyfetchHydrater = require('anyfetch-hydrater');
var fs = require('fs');

var pdfocr = require('../lib/');
var HydrationError = anyfetchHydrater.HydrationError;

describe('Test PDFOCR results', function() {
  it('returns the correct informations for a simple pdf file (2 images)', function(done) {
      var document = {
          datas: {}
      };

      var changes = anyfetchHydrater.defaultChanges();
      pdfocr(__dirname + "/samples/test-2img.pdf", document, changes, function(err, changes) {
          if(err) {
            throw err;
          }
          var data = fs.readFileSync(__dirname + '/samples/dump-2img.txt');
          changes.metadata.should.eql(data.toString());
          done();
        });
    });

  it('returns the correct informations for a complicated pdf file (11 images)', function(done) {
      var document = {
          datas: {}
      };

      var changes = anyfetchHydrater.defaultChanges();
      pdfocr(__dirname + "/samples/test-11img.pdf", document, changes, function(err, changes) {
          if(err) {
            throw err;
          }
          var data = fs.readFileSync(__dirname + '/samples/dump-11img.txt');
          changes.metadata.should.eql(data.toString());
          done();
        });
    });

  it('should return an errored document', function(done) {
    var document = {
      data: {}
    };

    var changes = anyfetchHydrater.defaultChanges();
    pdfocr(__dirname + "/samples/bad.pdf", document, changes, function(err) {
      if(err instanceof HydrationError) {
        done();
      }
      else {
        done(new Error("invalid error"));
      }
    });
  });
});
<commit_msg>Indentation fix<commit_after>'use strict';

require('should');
var anyfetchHydrater = require('anyfetch-hydrater');
var fs = require('fs');

var pdfocr = require('../lib/');
var HydrationError = anyfetchHydrater.HydrationError;

describe('Test PDFOCR results', function() {
  it('returns the correct informations for a simple pdf file (2 images)', function(done) {
    var document = {
        datas: {}
    };

    var changes = anyfetchHydrater.defaultChanges();
    pdfocr(__dirname + "/samples/test-2img.pdf", document, changes, function(err, changes) {
      if(err) {
        throw err;
      }
      var data = fs.readFileSync(__dirname + '/samples/dump-2img.txt');
      changes.metadata.should.eql(data.toString());
      done();
    });
  });

  it('returns the correct informations for a complicated pdf file (11 images)', function(done) {
    var document = {
        datas: {}
    };

    var changes = anyfetchHydrater.defaultChanges();
    pdfocr(__dirname + "/samples/test-11img.pdf", document, changes, function(err, changes) {
        if(err) {
          throw err;
        }
        var data = fs.readFileSync(__dirname + '/samples/dump-11img.txt');
        changes.metadata.should.eql(data.toString());
        done();
      });
  });

  it('should return an errored document', function(done) {
    var document = {
      data: {}
    };

    var changes = anyfetchHydrater.defaultChanges();
    pdfocr(__dirname + "/samples/bad.pdf", document, changes, function(err) {
      if(err instanceof HydrationError) {
        done();
      }
      else {
        done(new Error("invalid error"));
      }
    });
  });
});
<|endoftext|>


<commit_before>/**
 * Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/view/payment/list': {
                'Amazon_PayV2/js/view/payment/list-mixin': true
            },
            'Magento_Tax/js/view/checkout/summary/grand-total': {
                'Amazon_PayV2/js/view/checkout/summary/grand-total-mixin': true
            }
        }
    },
    map: {
        '*': {
            amazonPayV2ProductAdd: 'Amazon_PayV2/js/amazon-product-add',
            amazonPayV2Button: 'Amazon_PayV2/js/amazon-button',
            amazonPayV2Config: 'Amazon_PayV2/js/model/amazonPayV2Config',
            amazonPayV2LoginButton: 'Amazon_PayV2/js/amazon-login-button',
            amazonPayV2Logout: 'Amazon_PayV2/js/amazon-logout'
        }
    },
    paths: {
        amazonPayV2CheckoutDE: 'https://static-eu.payments-amazon.com/checkout',
        amazonPayV2CheckoutUK: 'https://static-eu.payments-amazon.com/checkout',
        amazonPayV2CheckoutJP: 'https://static-fe.payments-amazon.com/checkout',
        amazonPayV2CheckoutUS: 'https://static-na.payments-amazon.com/checkout'
    }
};
<commit_msg>ASD-582 - disable CV1 mixin to prevent js errors<commit_after>/**
 * Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/view/payment/list': {
                'Amazon_PayV2/js/view/payment/list-mixin': true
            },
            'Magento_Tax/js/view/checkout/summary/grand-total': {
                'Amazon_PayV2/js/view/checkout/summary/grand-total-mixin': true,
                'Amazon_Payment/js/view/checkout/summary/grand-total-mixin': false
            }
        }
    },
    map: {
        '*': {
            amazonPayV2ProductAdd: 'Amazon_PayV2/js/amazon-product-add',
            amazonPayV2Button: 'Amazon_PayV2/js/amazon-button',
            amazonPayV2Config: 'Amazon_PayV2/js/model/amazonPayV2Config',
            amazonPayV2LoginButton: 'Amazon_PayV2/js/amazon-login-button',
            amazonPayV2Logout: 'Amazon_PayV2/js/amazon-logout'
        }
    },
    paths: {
        amazonPayV2CheckoutDE: 'https://static-eu.payments-amazon.com/checkout',
        amazonPayV2CheckoutUK: 'https://static-eu.payments-amazon.com/checkout',
        amazonPayV2CheckoutJP: 'https://static-fe.payments-amazon.com/checkout',
        amazonPayV2CheckoutUS: 'https://static-na.payments-amazon.com/checkout'
    }
};
<|endoftext|>


<commit_before>/**
 * Copyright 2014-2015, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
/*global window */
'use strict';

var debug = require('debug')('RouterMixin');
var navigateAction = require('../actions/navigate');
var History = require('./History');
var React = require('react');
var TYPE_CLICK = 'click';
var TYPE_PAGELOAD = 'pageload';
var TYPE_REPLACESTATE = 'replacestate';
var TYPE_POPSTATE = 'popstate';
var TYPE_DEFAULT = 'default'; // default value if navigation type is missing, for programmatic navigation
var RouterMixin;

require('setimmediate');

function routesEqual(route1, route2) {
    route1 = route1 || {};
    route2 = route2 || {};
    return (route1.url === route2.url);
}

function saveScrollPosition(e, history) {
    var historyState = (history.getState && history.getState()) || {};
    historyState.scroll = {x: window.scrollX, y: window.scrollY};
    debug('remember scroll position', historyState.scroll);
    history.replaceState(historyState);
}

RouterMixin = {
    contextTypes: {
        executeAction: React.PropTypes.func,
        makePath: React.PropTypes.func
    },
    childContextTypes: {
        makePath: React.PropTypes.func
    },
    getChildContext: function(){
        var context = {};
        Object.keys(RouterMixin.childContextTypes).forEach(function (key) {
            context[key] = (this.props.context && this.props.context[key]) || this.context[key];
        }, this);
        return context;
    },
    componentDidMount: function() {
        var self = this;
        var context;
        var urlFromHistory;
        var urlFromState;

        if (self.context && self.context.executeAction) {
            context = self.context;
        } else if (self.props.context && self.props.context.executeAction) {
            context = self.props.context;
        }

        self._history = ('function' === typeof self.props.historyCreator) ? self.props.historyCreator() : new History();
        self._enableScroll = (self.props.enableScroll !== false);

        if (self.props.checkRouteOnPageLoad) {
            // You probably want to enable checkRouteOnPageLoad, if you use a history implementation
            // that supports hash route:
            //   At page load, for browsers without pushState AND hash is present in the url,
            //   since hash fragment is not sent to the server side, we need to
            //   dispatch navigate action on browser side to load the actual page content
            //   for the route represented by the hash fragment.

            urlFromHistory = self._history.getUrl();
            urlFromState = self.state && self.state.route && self.state.route.url;

            if (context && (urlFromHistory !== urlFromState)) {
                // put it in setImmediate, because we need the base component to have
                // store listeners attached, before navigateAction is executed.
                debug('pageload navigate to actual route', urlFromHistory, urlFromState);
                setImmediate(function navigateToActualRoute() {
                    context.executeAction(navigateAction, {type: TYPE_PAGELOAD, url: urlFromHistory});
                });
            }
        }

        self._historyListener = function (e) {
            debug('history listener invoked', e, url, self.state.route.url);

            if (context) {
                var state = self.state || {};
                var url = self._history.getUrl();
                var currentUrl = state.route.url;
                var route = state.route || {};
                var onBeforeUnloadText = typeof window.onbeforeunload === 'function' ? window.onbeforeunload() : '';
                var confirmResult = onBeforeUnloadText ? window.confirm(onBeforeUnloadText) : true;
                var nav = route.navigate || {};
                var navParams = nav.params || {};
                var enableScroll = self._enableScroll && nav.preserveScrollPosition;
                var historyState = {
                    params: (nav.params || {}),
                    scroll: {
                        x: (enableScroll ? window.scrollX : 0),
                        y: (enableScroll ? window.scrollY : 0)
                    }
                };
                var pageTitle = navParams.pageTitle || null;

                if (!confirmResult && currentUrl) {
                    // Pushes the previous history state back on top to set the correct url
                    self._history.pushState(historyState, pageTitle, currentUrl);
                } else {
                    window.onbeforeunload = null;

                    if (url !== currentUrl) {
                        context.executeAction(navigateAction, {type: TYPE_POPSTATE, url: url, params: (e.state && e.state.params)});
                    }
                }
            }
        };
        self._history.on(self._historyListener);

        if (self._enableScroll) {
            var scrollTimer;
            self._scrollListener = function (e) {
                if (scrollTimer) {
                    window.clearTimeout(scrollTimer);
                }
                scrollTimer = window.setTimeout(saveScrollPosition.bind(self, e, self._history), 150);
            };
            window.addEventListener('scroll', self._scrollListener);
        }
    },
    componentWillUnmount: function() {
        this._history.off(this._historyListener);
        this._historyListener = null;

        if (this._enableScroll) {
            window.removeEventListener('scroll', this._scrollListener);
            this._scrollListener = null;
        }

        this._history = null;
    },
    componentDidUpdate: function (prevProps, prevState) {
        debug('component did update', prevState, this.state);

        var newState = this.state;
        if (routesEqual(prevState && prevState.route, newState && newState.route)) {
            return;
        }

        var nav = newState.route.navigate || {};
        var navType = nav.type || TYPE_DEFAULT;
        var historyState;
        var pageTitle;

        switch (navType) {
            case TYPE_CLICK:
            case TYPE_DEFAULT:
            case TYPE_REPLACESTATE:
                historyState = {params: (nav.params || {})};
                if(this._enableScroll) {
                    if (nav.preserveScrollPosition) {
                        historyState.scroll = {x: window.scrollX, y: window.scrollY};
                    } else {
                        window.scrollTo(0, 0);
                        historyState.scroll = {x: 0, y: 0};
                        debug('on click navigation, reset scroll position to (0, 0)');
                    }
                }
                pageTitle = nav.params && nav.params.pageTitle || null;
                if(navType == TYPE_REPLACESTATE) {
                    this._history.replaceState(historyState, pageTitle, newState.route.url);
                } else {
                    this._history.pushState(historyState, pageTitle, newState.route.url);
                }
                break;
            case TYPE_POPSTATE:
                if (this._enableScroll) {
                    historyState = (this._history.getState && this._history.getState()) || {};
                    var scroll = (historyState && historyState.scroll) || {};
                    debug('on popstate navigation, restore scroll position to ', scroll);
                    window.scrollTo(scroll.x || 0, scroll.y || 0);
                }
                break;
        }
    }
};

module.exports = RouterMixin;
<commit_msg>Remove the currentUrl condition per @lingyan<commit_after>/**
 * Copyright 2014-2015, Yahoo! Inc.
 * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
/*global window */
'use strict';

var debug = require('debug')('RouterMixin');
var navigateAction = require('../actions/navigate');
var History = require('./History');
var React = require('react');
var TYPE_CLICK = 'click';
var TYPE_PAGELOAD = 'pageload';
var TYPE_REPLACESTATE = 'replacestate';
var TYPE_POPSTATE = 'popstate';
var TYPE_DEFAULT = 'default'; // default value if navigation type is missing, for programmatic navigation
var RouterMixin;

require('setimmediate');

function routesEqual(route1, route2) {
    route1 = route1 || {};
    route2 = route2 || {};
    return (route1.url === route2.url);
}

function saveScrollPosition(e, history) {
    var historyState = (history.getState && history.getState()) || {};
    historyState.scroll = {x: window.scrollX, y: window.scrollY};
    debug('remember scroll position', historyState.scroll);
    history.replaceState(historyState);
}

RouterMixin = {
    contextTypes: {
        executeAction: React.PropTypes.func,
        makePath: React.PropTypes.func
    },
    childContextTypes: {
        makePath: React.PropTypes.func
    },
    getChildContext: function(){
        var context = {};
        Object.keys(RouterMixin.childContextTypes).forEach(function (key) {
            context[key] = (this.props.context && this.props.context[key]) || this.context[key];
        }, this);
        return context;
    },
    componentDidMount: function() {
        var self = this;
        var context;
        var urlFromHistory;
        var urlFromState;

        if (self.context && self.context.executeAction) {
            context = self.context;
        } else if (self.props.context && self.props.context.executeAction) {
            context = self.props.context;
        }

        self._history = ('function' === typeof self.props.historyCreator) ? self.props.historyCreator() : new History();
        self._enableScroll = (self.props.enableScroll !== false);

        if (self.props.checkRouteOnPageLoad) {
            // You probably want to enable checkRouteOnPageLoad, if you use a history implementation
            // that supports hash route:
            //   At page load, for browsers without pushState AND hash is present in the url,
            //   since hash fragment is not sent to the server side, we need to
            //   dispatch navigate action on browser side to load the actual page content
            //   for the route represented by the hash fragment.

            urlFromHistory = self._history.getUrl();
            urlFromState = self.state && self.state.route && self.state.route.url;

            if (context && (urlFromHistory !== urlFromState)) {
                // put it in setImmediate, because we need the base component to have
                // store listeners attached, before navigateAction is executed.
                debug('pageload navigate to actual route', urlFromHistory, urlFromState);
                setImmediate(function navigateToActualRoute() {
                    context.executeAction(navigateAction, {type: TYPE_PAGELOAD, url: urlFromHistory});
                });
            }
        }

        self._historyListener = function (e) {
            debug('history listener invoked', e, url, self.state.route.url);

            if (context) {
                var state = self.state || {};
                var url = self._history.getUrl();
                var currentUrl = state.route.url;
                var route = state.route || {};
                var onBeforeUnloadText = typeof window.onbeforeunload === 'function' ? window.onbeforeunload() : '';
                var confirmResult = onBeforeUnloadText ? window.confirm(onBeforeUnloadText) : true;
                var nav = route.navigate || {};
                var navParams = nav.params || {};
                var enableScroll = self._enableScroll && nav.preserveScrollPosition;
                var historyState = {
                    params: (nav.params || {}),
                    scroll: {
                        x: (enableScroll ? window.scrollX : 0),
                        y: (enableScroll ? window.scrollY : 0)
                    }
                };
                var pageTitle = navParams.pageTitle || null;

                if (!confirmResult) {
                    // Pushes the previous history state back on top to set the correct url
                    self._history.pushState(historyState, pageTitle, currentUrl);
                } else {
                    window.onbeforeunload = null;

                    if (url !== currentUrl) {
                        context.executeAction(navigateAction, {type: TYPE_POPSTATE, url: url, params: (e.state && e.state.params)});
                    }
                }
            }
        };
        self._history.on(self._historyListener);

        if (self._enableScroll) {
            var scrollTimer;
            self._scrollListener = function (e) {
                if (scrollTimer) {
                    window.clearTimeout(scrollTimer);
                }
                scrollTimer = window.setTimeout(saveScrollPosition.bind(self, e, self._history), 150);
            };
            window.addEventListener('scroll', self._scrollListener);
        }
    },
    componentWillUnmount: function() {
        this._history.off(this._historyListener);
        this._historyListener = null;

        if (this._enableScroll) {
            window.removeEventListener('scroll', this._scrollListener);
            this._scrollListener = null;
        }

        this._history = null;
    },
    componentDidUpdate: function (prevProps, prevState) {
        debug('component did update', prevState, this.state);

        var newState = this.state;
        if (routesEqual(prevState && prevState.route, newState && newState.route)) {
            return;
        }

        var nav = newState.route.navigate || {};
        var navType = nav.type || TYPE_DEFAULT;
        var historyState;
        var pageTitle;

        switch (navType) {
            case TYPE_CLICK:
            case TYPE_DEFAULT:
            case TYPE_REPLACESTATE:
                historyState = {params: (nav.params || {})};
                if(this._enableScroll) {
                    if (nav.preserveScrollPosition) {
                        historyState.scroll = {x: window.scrollX, y: window.scrollY};
                    } else {
                        window.scrollTo(0, 0);
                        historyState.scroll = {x: 0, y: 0};
                        debug('on click navigation, reset scroll position to (0, 0)');
                    }
                }
                pageTitle = nav.params && nav.params.pageTitle || null;
                if(navType == TYPE_REPLACESTATE) {
                    this._history.replaceState(historyState, pageTitle, newState.route.url);
                } else {
                    this._history.pushState(historyState, pageTitle, newState.route.url);
                }
                break;
            case TYPE_POPSTATE:
                if (this._enableScroll) {
                    historyState = (this._history.getState && this._history.getState()) || {};
                    var scroll = (historyState && historyState.scroll) || {};
                    debug('on popstate navigation, restore scroll position to ', scroll);
                    window.scrollTo(scroll.x || 0, scroll.y || 0);
                }
                break;
        }
    }
};

module.exports = RouterMixin;
<|endoftext|>


<commit_before>define(["jquery", "moment", "jquery.easing", "underscore", "scripts/helper/math", "backbone", "scripts/collections/population", "scripts/models/individual"],
	function($, moment, jquery_easing, _, math, Backbone, Population, Individual) {

	var Wheel = Backbone.View.extend({
		events: {
			"click": "spin"
		},

		populate: function() {
			this.render_info("Loading data...");
			var self = this;
			this.collection.fetch({reset: true, error: function() {
				self.render_info("Error: Failed to load data");
			}});
		},

		initialize: function(options) {
			options = typeof options !== 'undefined' ? options : {};
			this.random = typeof options.random !== 'undefined' ? options.random : Math.random;
			this.animation_duration = typeof options.animation_duration !== 'undefined' ? options.animation_duration : 10000; // ms
			this.color_brewer = typeof options.color_brewer !== 'undefined' ? options.color_brewer : function(number) { return _.map(new Array(number), _.constant("#000000")); };

			this.reset();
			this.collection.on("reset", this.reset, this);
		},

		reset: function(collection, options) {
			// TODO: wait for active animation to stop
			var fitness_values = this.collection.map(function(individual) {
				return individual.get("fitness");
			});
			var fitness_sum = _.reduce(fitness_values, Math.sum, 0);

			if (fitness_sum !== 0) {
				var label_values = this.collection.map(function(individual) {
					return individual.get("label");
				});

				var colors = this.color_brewer(this.collection.size());

				this.arc_widths = _.map(fitness_values, function(fitness) { return fitness / fitness_sum * Math.TWO_PI; });
				this.labels = label_values;
				this.colors = colors;
			} else {
				this.arc_widths = [];
				this.labels = [];
				this.colors = [];
			}

			this.render();
			this.$el.addClass("clickable");

			this.trigger("ready");
		},

		render: function(rotation) {
			rotation = typeof rotation !== 'undefined' ? rotation : 0;

			var canvas = this.$el.get(0);
			var acr_start = 0;
			var radius = 200;
			var wheel_center_x = 200;
			var wheel_center_y = 200;

			var ctx = canvas.getContext("2d");
			ctx.save();
			ctx.clearRect(0, 0, canvas.width, canvas.height);

			ctx.translate(wheel_center_x, wheel_center_y);
			ctx.rotate(rotation);
			ctx.translate(-wheel_center_x, -wheel_center_y);

			for (var i = 0; i < this.arc_widths.length; i++) {
				ctx.fillStyle = "#" + this.colors[i];
				ctx.beginPath();
				ctx.moveTo(wheel_center_x, wheel_center_y);
				var arc_end = acr_start + this.arc_widths[i];
				ctx.arc(wheel_center_x, wheel_center_y, radius, acr_start, arc_end, false);
				ctx.lineTo(wheel_center_x, wheel_center_y);
				ctx.fill();

				ctx.save();
				var adjacent = Math.cos(acr_start + (arc_end - acr_start) / 2) * (radius - 10);
				var opposite = Math.sin(acr_start + (arc_end - acr_start) / 2) * (radius - 10);
				ctx.translate(wheel_center_x + adjacent, wheel_center_y + opposite);
				ctx.rotate(acr_start + (arc_end - acr_start) / 2);

				ctx.fillStyle = "#ffffff";
				ctx.textAlign = "right";
				ctx.textBaseline = "middle";
				ctx.fillText(this.labels[i], 0, 0);

				ctx.restore();

				acr_start = arc_end;
			}
			ctx.restore();

			ctx.beginPath();
		    ctx.moveTo(wheel_center_x + radius - 5, wheel_center_y);
		    ctx.strokeStyle = '#ffffff';
		    ctx.lineWidth = 3;
		    ctx.lineTo(wheel_center_x + radius + 50, wheel_center_y);
		    ctx.stroke();
		},

		/**
		 * Spin the wheel
		 * @param {Function} after - A callback which gets called at the end of the animation
		 */
		render_spin: function(after) {
			after = typeof after !== 'undefined' ? after : _.noop();

			var self = this;

			var index = math.roulette_wheel_selection(self.arc_widths, self.random);
			var partial_exclusive = _.reduce(self.arc_widths.slice(0, index), Math.sum, 0);
			var partial_inclusive = partial_exclusive + self.arc_widths[index];
			var angle_selected = partial_exclusive + (partial_inclusive - partial_exclusive) / 2;
			var rotation_max = Math.TWO_PI * 10 + (Math.TWO_PI - angle_selected);

			var animation_start = moment();
			var animation_interval = setInterval(function() {
				var time = moment().diff(animation_start);
				var easing = $.easing.easeOutCirc(null, time, 0, rotation_max, self.animation_duration);
				var rotation = easing % Math.TWO_PI;
				self.render(rotation);
			}, 10);

			setTimeout(function() {
				clearInterval(animation_interval);
				after();
			}, self.animation_duration);
		},

		render_info: function(message) {
			var canvas = this.$el.get(0);
			var ctx = canvas.getContext("2d");
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			ctx.fillStyle = "#ffffff";
			ctx.textAlign = "center";
			ctx.fillText(message, canvas.width / 2, canvas.height / 2);
		},

		spin: function() {
			var el = this.$el;
			if (!el.hasClass("clickable")) {
				return;
			}
			el.removeClass("clickable");

			this.render_spin(function() {
				el.addClass("clickable");
			});
		}
	});

	return Wheel;
});<commit_msg>Improved wheel rendering performance<commit_after>define(["jquery", "moment", "jquery.easing", "underscore", "scripts/helper/math", "backbone", "scripts/collections/population", "scripts/models/individual"],
	function($, moment, jquery_easing, _, math, Backbone, Population, Individual) {

	var Wheel = Backbone.View.extend({
		events: {
			"click": "spin"
		},

		populate: function() {
			this.render_info("Loading data...");
			var self = this;
			this.collection.fetch({reset: true, error: function() {
				self.render_info("Error: Failed to load data");
			}});
		},

		initialize: function(options) {
			options = typeof options !== 'undefined' ? options : {};
			this.random = typeof options.random !== 'undefined' ? options.random : Math.random;
			this.animation_duration = typeof options.animation_duration !== 'undefined' ? options.animation_duration : 10000; // ms
			this.color_brewer = typeof options.color_brewer !== 'undefined' ? options.color_brewer : function(number) { return _.map(new Array(number), _.constant("#000000")); };
			this.fps = typeof options.fps !== 'undefined' ? options.fps : 60;

			this.reset();
			this.collection.on("reset", this.reset, this);
		},

		reset: function(collection, options) {
			// TODO: wait for active animation to stop
			var fitness_values = this.collection.map(function(individual) {
				return individual.get("fitness");
			});
			var fitness_sum = _.reduce(fitness_values, Math.sum, 0);

			if (fitness_sum !== 0) {
				var label_values = this.collection.map(function(individual) {
					return individual.get("label");
				});

				var colors = this.color_brewer(this.collection.size());

				this.arc_widths = _.map(fitness_values, function(fitness) { return fitness / fitness_sum * Math.TWO_PI; });
				this.labels = label_values;
				this.colors = colors;
			} else {
				this.arc_widths = [];
				this.labels = [];
				this.colors = [];
			}

			this.wheel_canvas = this.create_wheel_canvas();
			this.render();
			this.$el.addClass("clickable");

			this.trigger("ready");
		},

		create_wheel_canvas: function() {
			var radius = 200;
			var wheel_center_x = 200;
			var wheel_center_y = 200;

			var canvas = document.createElement('canvas');
			canvas.width = radius * 2;
			canvas.height = radius * 2;

			var ctx = canvas.getContext("2d");

			ctx.translate(wheel_center_x, wheel_center_y);
			ctx.translate(-wheel_center_x, -wheel_center_y);

			var acr_start = 0;

			for (var i = 0; i < this.arc_widths.length; i++) {
				ctx.fillStyle = "#" + this.colors[i];
				ctx.beginPath();
				ctx.moveTo(wheel_center_x, wheel_center_y);
				var arc_end = acr_start + this.arc_widths[i];
				ctx.arc(wheel_center_x, wheel_center_y, radius, acr_start, arc_end, false);
				ctx.lineTo(wheel_center_x, wheel_center_y);
				ctx.fill();

				ctx.save();
				var adjacent = Math.cos(acr_start + (arc_end - acr_start) / 2) * (radius - 10);
				var opposite = Math.sin(acr_start + (arc_end - acr_start) / 2) * (radius - 10);
				ctx.translate(wheel_center_x + adjacent, wheel_center_y + opposite);
				ctx.rotate(acr_start + (arc_end - acr_start) / 2);

				ctx.fillStyle = "#ffffff";
				ctx.textAlign = "right";
				ctx.textBaseline = "middle";
				ctx.fillText(this.labels[i], 0, 0);

				ctx.restore();

				acr_start = arc_end;
			}

		    return canvas;
		},

		render: function(rotation) {
			rotation = typeof rotation !== 'undefined' ? rotation : 0;

			var radius = 200;
			var wheel_center_x = 200;
			var wheel_center_y = 200;

			var canvas = this.$el.get(0);
			var ctx = canvas.getContext("2d");

			ctx.save();
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			ctx.translate(wheel_center_x, wheel_center_y);
			ctx.rotate(rotation);
			ctx.translate(-wheel_center_x, -wheel_center_y);
			ctx.drawImage(this.wheel_canvas, 0, 0);
			ctx.restore();

			ctx.beginPath();
		    ctx.moveTo(wheel_center_x + radius - 5, wheel_center_y);
		    ctx.strokeStyle = '#ffffff';
		    ctx.lineWidth = 3;
		    ctx.lineTo(wheel_center_x + radius + 50, wheel_center_y);
		    ctx.stroke();
		},

		/**
		 * Spin the wheel
		 * @param {Function} after - A callback which gets called at the end of the animation
		 */
		render_spin: function(after) {
			after = typeof after !== 'undefined' ? after : _.noop();

			var self = this;

			var index = math.roulette_wheel_selection(self.arc_widths, self.random);
			var partial_exclusive = _.reduce(self.arc_widths.slice(0, index), Math.sum, 0);
			var partial_inclusive = partial_exclusive + self.arc_widths[index];
			var angle_selected = partial_exclusive + (partial_inclusive - partial_exclusive) / 2;
			var full_rotations = 10;
			var rotation_max = Math.TWO_PI * full_rotations + (Math.TWO_PI - angle_selected);

			var animation_start = moment();
			var animation_interval = setInterval(function() {
				var time = moment().diff(animation_start);
				var easing = $.easing.easeOutCirc(null, time, 0, rotation_max, self.animation_duration);
				var rotation = easing % Math.TWO_PI;
				self.render(rotation);
			}, Math.min(1, Math.round(1000 / this.fps)));

			setTimeout(function() {
				clearInterval(animation_interval);
				after();
			}, self.animation_duration);
		},

		render_info: function(message) {
			var canvas = this.$el.get(0);
			var ctx = canvas.getContext("2d");
			ctx.clearRect(0, 0, canvas.width, canvas.height);
			ctx.fillStyle = "#ffffff";
			ctx.textAlign = "center";
			ctx.fillText(message, canvas.width / 2, canvas.height / 2);
		},

		spin: function() {
			var el = this.$el;
			if (!el.hasClass("clickable")) {
				return;
			}
			el.removeClass("clickable");

			this.render_spin(function() {
				el.addClass("clickable");
			});
		}
	});

	return Wheel;
});<|endoftext|>


<commit_before>'use strict';

const chai = require('chai');
const expect = chai.expect;

const domainPing = require('../domain-ping');

describe('Domain ping', () => {
    it('should return error for empty domain', (done) => {
        domainPing('')
            .then((res) => {
                done(Error('Expected to fail'));
            })
            .catch((res) => {
                expect(res).to.be.an('object');
                expect(res).to.have.property('success');
                expect(res.success).to.be.false;
                expect(res).to.have.property('error');
                expect(res.error).to.have.property('message');
                expect(res.error.message).to.be.a('string');
                expect(res.error.message).to.be.equal('Invalid domain name');
                done();
            })
            .catch(done);
    });
});
<commit_msg>Added tests for invalid and valid domains<commit_after>'use strict';

const chai = require('chai');
const expect = chai.expect;

const domainPing = require('../domain-ping');

describe('Testing domain-ping', () => {

    describe('Basic errors', () => {

        it('should return error for empty domain', (done) => {
            domainPing('')
                .then((res) => {
                    done(Error('Expected to fail'));
                })
                .catch((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.false;
                    expect(res).to.have.property('error');
                    expect(res.error).to.have.property('message');
                    expect(res.error.message).to.be.a('string');
                    expect(res.error.message).to.be.equal('Invalid domain name');
                    done();
                })
                .catch(done);
        });

        it('should return error for invalid domain', (done) => {
            domainPing('abc123')
                .then((res) => {
                    done(Error('Expected to fail'));
                })
                .catch((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.false;
                    expect(res).to.have.property('error');
                    expect(res.error).to.have.property('message');
                    expect(res.error.message).to.be.a('string');
                    expect(res.error.message).to.be.equal('Invalid domain name');
                    done();
                })
                .catch(done);
        });

    });

    describe('Valid domains', () => {

        it('should return correct data for github.com', (done) => {
            domainPing('github.com')
                .then((res) => {
                    expect(res).to.be.an('object');
                    expect(res).to.have.property('success');
                    expect(res.success).to.be.true;
                    expect(res).to.have.property('ip');
                    expect(res.ip).to.be.a('string');
                    expect(res).to.have.property('ping');
                    expect(res.ping).to.be.a('boolean');
                    expect(res).to.have.property('online');
                    expect(res.online).to.be.a('boolean');
                    expect(res).to.have.property('statusCode');
                    expect(res.statusCode).to.be.a('number');
                })
                .catch(done);
        });

    });

});
<|endoftext|>


<commit_before>#!/usr/bin/env node

var program      = require('commander')
  , _            = require('lodash')
  , changeCase   = require('change-case')
  , pkg          = require('../package.json')
  , smooth       = require('../lib')
  , customConfig = {}
  , cmd

// GLOBAL OPTIONS
// =============================================================================

program
  .version(pkg.version)
  .option('-c, --config <path>', 'set config file path. Defaults to ./' + smooth.defaultConfigFileName)
  .parse(process.argv)

try {

  if(_.isUndefined(program.config)) {
    program.config = smooth.defaultConfigFileName;
  }

  customConfig = require(process.cwd() + '/' + program.config);

} catch(exception) {}

// COMMANDS
// =============================================================================

// $ smooth theme [action] [name] --dest <destination>

program
  .command('theme [action] [name]')
  .description('Theme actions')
  .option('-d, --dest <destination>')
  .usage('[action]')
  .usage('[name]')
  .action(function(action, name, options) {

    var dest = _.isUndefined(options.dest)
      ? process.cwd() + '/themes/' + name
      : process.cwd() + '/' + options.dest

    smooth.theme[action](customConfig, name, dest);
  });

// $ smooth build [--dest <destination>] [--icon-*]

cmd = program.command('build');
cmd.description('Build package');
cmd.option('-d, --dest <destionation>');

smooth.iconSets.forEach(function(setName) {
  cmd.option('--' + smooth.cmdIncludeIconSetPrefix + '-' + setName)
});

cmd.action(function(options, icon) {

  var icons      = []
    , normalized = '';

  smooth.iconSets.forEach(function(setName) {

    normalized = changeCase.camelCase(smooth.cmdIncludeIconSetPrefix + ' ' + setName);

    if(true === options[normalized]) {
      icons.push({ bundle: setName })
    }

  });

  customConfig = _.merge(customConfig, {
    compress: true,
    path : {
      target: options.dest
    },
    icons: icons
  });

  smooth.build.build(customConfig)
});

// $ smooth watch

program
  .command('watch')
  .description('Develop and testing')
  .action(function() {
    smooth.build.watch(customConfig)
  });

// $ smooth help

program
  .command('help')
  .description('Displays verbose help')
  .action(function() {
    program.help();
  })

program.parse(process.argv);<commit_msg>Fix require paths<commit_after>#!/usr/bin/env node

var program      = require('commander')
  , _            = require('lodash')
  , changeCase   = require('change-case')
  , pkg          = require('./../package.json')
  , smooth       = require('./../lib')
  , customConfig = {}
  , cmd

// GLOBAL OPTIONS
// =============================================================================

program
  .version(pkg.version)
  .option('-c, --config <path>', 'set config file path. Defaults to ./' + smooth.defaultConfigFileName)
  .parse(process.argv)

try {

  if(_.isUndefined(program.config)) {
    program.config = smooth.defaultConfigFileName;
  }

  customConfig = require(process.cwd() + '/' + program.config);

} catch(exception) {}

// COMMANDS
// =============================================================================

// $ smooth theme [action] [name] --dest <destination>

program
  .command('theme [action] [name]')
  .description('Theme actions')
  .option('-d, --dest <destination>')
  .usage('[action]')
  .usage('[name]')
  .action(function(action, name, options) {

    var dest = _.isUndefined(options.dest)
      ? process.cwd() + '/themes/' + name
      : process.cwd() + '/' + options.dest

    smooth.theme[action](customConfig, name, dest);
  });

// $ smooth build [--dest <destination>] [--icon-*]

cmd = program.command('build');
cmd.description('Build package');
cmd.option('-d, --dest <destionation>');

smooth.iconSets.forEach(function(setName) {
  cmd.option('--' + smooth.cmdIncludeIconSetPrefix + '-' + setName)
});

cmd.action(function(options, icon) {

  var icons      = []
    , normalized = '';

  smooth.iconSets.forEach(function(setName) {

    normalized = changeCase.camelCase(smooth.cmdIncludeIconSetPrefix + ' ' + setName);

    if(true === options[normalized]) {
      icons.push({ bundle: setName })
    }

  });

  customConfig = _.merge(customConfig, {
    compress: true,
    path : {
      target: options.dest
    },
    icons: icons
  });

  smooth.build.build(customConfig)
});

// $ smooth watch

program
  .command('watch')
  .description('Develop and testing')
  .action(function() {
    smooth.build.watch(customConfig)
  });

// $ smooth help

program
  .command('help')
  .description('Displays verbose help')
  .action(function() {
    program.help();
  })

program.parse(process.argv);<|endoftext|>