import React, { Component } from 'react'
import { getDisplayName } from './utils'

export default function withSideEffect (reduceComponentsToState, handleStateChangeOnClient) {
  return function wrap (WrappedComponent) {
    let mountedInstances = []
    let state

    function emitChange (component) {
      state = reduceComponentsToState(mountedInstances)

      if (SideEffect.canUseDOM) {
        handleStateChangeOnClient.call(component, state)
      }
    }

    class SideEffect extends Component {
      // Try to use displayName of wrapped component
      static displayName = `SideEffect(${getDisplayName(WrappedComponent)})`

      // Expose canUseDOM so tests can monkeypatch it
      static canUseDOM = typeof window !== 'undefined'

      static peek () {
        return state
      }

      static rewind () {
        if (process.env.NODE_ENV !== 'production' && SideEffect.canUseDOM) {
          throw new Error('You may only call rewind() on the server. Call peek() to read the current state.')
        }

        const recordedState = state
        state = undefined
        mountedInstances = []
        return recordedState
      }

      componentWillMount () {
        mountedInstances.push(this)
        emitChange(this)
      }

      componentDidUpdate () {
        emitChange(this)
      }

      componentWillUnmount () {
        const index = mountedInstances.indexOf(this)
        if (index >= 0) {
          mountedInstances.splice(index, 1)
        }
        emitChange(this)
      }

      render () {
        return <WrappedComponent>{ this.props.children }</WrappedComponent>
      }
    }

    return SideEffect
  }
}

import React, { Component } from 'react'
import { getDisplayName } from './utils'

const canUseDOM = typeof window !== 'undefined'

export default function withSideEffect (reduceComponentsToState, handleStateChangeOnClient) {
  return function wrap (WrappedComponent) {
    let mountedInstances = []
    let state

    function emitChange (component) {
      state = reduceComponentsToState(mountedInstances)

      if (canUseDOM) {
        handleStateChangeOnClient.call(component, state)
      }
    }

    class SideEffect extends Component {
      // Try to use displayName of wrapped component
      static displayName = `SideEffect(${getDisplayName(WrappedComponent)})`

      static peek () {
        return state
      }

      static rewind () {
        if (process.env.NODE_ENV !== 'production' && canUseDOM) {
          throw new Error('You may only call rewind() on the server. Call peek() to read the current state.')
        }

        const recordedState = state
        state = undefined
        mountedInstances = []
        return recordedState
      }

      componentWillMount () {
        mountedInstances.push(this)
        emitChange(this)
      }

      componentDidUpdate () {
        emitChange(this)
      }

      componentWillUnmount () {
        const index = mountedInstances.indexOf(this)
        if (index >= 0) {
          mountedInstances.splice(index, 1)
        }
        emitChange(this)
      }

      render () {
        return <WrappedComponent>{ this.props.children }</WrappedComponent>
      }
    }

    return SideEffect
  }
}

Remove static var from side effect


#!/usr/bin/env node

var ted = require('../ted')
var sh = require('shelljs')
var path = require('path')
var exec = require('child_process').execFile

// Install an array of submodules with package.json
var installModules = function (paths, whatTheyAre) {
  paths.forEach(function (path, index) {
    installModule(path)
  })
}

// Installed a single module dependencie based on Path
var installModule = function (modulePath) {

  var cwd = path.dirname(modulePath)

  // What is going on log
  console.log(
    ted.say.happy('npm install'),
    ted.say.warn('->'),
    ted.say.happy(cwd)
  )

  var installing = exec('npm', ['install', '-d', '-s'], {
    cwd: cwd
  }, function (err, out, erout) {
    // Good output
    if (out) {
      console.log(ted.say.debug(out))
    }

    // Bad output
    if (err) {
      console.log(ted.say.error(err))
    }
  })

  // When everything is done, tell me what happened
  installing.on('close', function (exit, error) {
    if (exit === 0 && !error) {
      console.log(cwd, ted.say.happy('done'))
    }

    if (error) {
      console.log(ted.say.error(error))
    }
  })
}

module.exports = installModules

#!/usr/bin/env node

var ted = require('../ted')
var sh = require('shelljs')
var path = require('path')
var exec = require('child_process').execFile

// Install an array of submodules with package.json
var installModules = function (paths, whatTheyAre) {
  paths.forEach(function (path, index) {
    installModule(path)
  })
}

// Installed a single module dependencie based on Path
var installModule = function (modulePath) {

  var cwd = path.dirname(modulePath)
  //Hackish solution for windows users - npm isn't recognized as a executable, but npm.cmd is!
  var npm = (process.platform === "win32" ? "npm.cmd" : "npm")

  // What is going on log
  console.log(
    ted.say.happy('npm install'),
    ted.say.warn('->'),
    ted.say.happy(cwd)
  )

  var installing = exec(npm, ['install', '-d', '-s'], {
    cwd: cwd
  }, function (err, out, erout) {
    // Good output
    if (out) {
      console.log(ted.say.debug(out))
    }

    // Bad output
    if (err) {
      console.log(ted.say.error(err))
    }
  })

  // When everything is done, tell me what happened
  installing.on('close', function (exit, error) {
    if (exit === 0 && !error) {
      console.log(cwd, ted.say.happy('done'))
    }

    if (error) {
      console.log(ted.say.error(error))
    }
  })
}

module.exports = installModules

Fix install submodules on Windows


module.exports = (function(){

	// GET /users/:id/media?lat=<LAT>&lng=<LNG>&time=<TIME>
	return function* collection(id) {
    var Q = require('q');
    var TwitterManager = require('../media/twitter');
    var InstagramManager = require('../media/instagram');

    // Twitter Requests
    var twitterDef = Q.defer();
    var twitterGranuals = twitterDef.promise
                          .then(TwitterManager.search);

    // Instagram Requests
    var instagramDef = Q.defer();
    var instagramGranuals = instagramDef.promise
                            .then(InstagramManager.search);

    // Flickr Requests
    // var FlickrManager = require('../media/flickr');
    // var flickrGranuals = yield FlickrManager.search(this.request.url);

    // Creating a universal capsul object
    var capsul = {
      "user_id": id,
      "latitude": require('../../helpers').paramsForUrl(this.request.url).lat,
      "longitude": require('../../helpers').paramsForUrl(this.request.url).lng,
      "timestamp": require('../../helpers').paramsForUrl(this.request.url).time,
      "data": []
    }

    twitterDef.resolve(this.request.url)
    instagramDef.resolve(this.request.url)

    capsul.data.push(twitterGranuals);
    capsul.data.push(instagramGranuals);

		this.body = yield capsul;
	}
})();

module.exports = (function(){

	// GET /users/:id/media?lat=<LAT>&lng=<LNG>&time=<TIME>
	return function* collection(id) {
    var Q = require('q');
    var TwitterManager = require('../media/twitter');
    var InstagramManager = require('../media/instagram');

    // Twitter Requests
    var twitterDef = Q.defer();
    var twitterGranuals = twitterDef.promise
                          .then(TwitterManager.search)
    twitterDef.resolve(this.request.url)

    // Instagram Requests
    var instagramDef = Q.defer();
    var instagramGranuals = instagramDef.promise
                            .then(InstagramManager.search)
    instagramDef.resolve(this.request.url)

    // Flickr Requests
    // var FlickrManager = require('../media/flickr');
    // var flickrGranuals = yield FlickrManager.search(this.request.url);

    // Creating a universal capsul object
    var capsul = {
      user_id: id,
      latitude: require('../../helpers').paramsForUrl(this.request.url).lat,
      longitude: require('../../helpers').paramsForUrl(this.request.url).lng,
      timestamp: require('../../helpers').paramsForUrl(this.request.url).time,
      data: []
    }

    capsul.data.push(instagramGranuals);
    capsul.data.push(twitterGranuals);

    this.body = yield capsul;
  }
})();

Create two promises per social media request. Heroku deployment.


(function(window) {
  window.chai.should();
  window.expect = window.chai.expect;
  window.assert = window.chai.assert;
})(window);

(function(window) {
  window.should = window.chai.should();
  window.expect = window.chai.expect;
  window.assert = window.chai.assert;
})(window);

Add should global as well.


exports.BattleScripts = {
	init: function() {
		for (var i in this.data.Pokedex) {
			var tier = '';
			var adjustment = 0;
			// mega evolutions get the same stat boost as their base forme
			if (this.data.FormatsData[i]) tier = this.data.FormatsData[i].tier || this.data.FormatsData[toId(this.getTemplate(i).baseSpecies)].tier;
			switch (tier) {
			case 'BL':
			case 'UU':
				adjustment = 5;
				break;
			case 'BL2':
			case 'RU':
				adjustment = 10;
				break;
			case 'BL3':
			case 'NU':
			case 'NFE':
			case 'LC':
				adjustment = 15;
			}

			if (adjustment) {
				for (var j in this.data.Pokedex[i].baseStats) {
					this.modData('Pokedex', i).baseStats[j] = this.clampIntRange(this.data.Pokedex[i].baseStats[j] + adjustment, 1, 255);
				}
			}
		}
	}
};

exports.BattleScripts = {
	init: function() {
		for (var i in this.data.Pokedex) {
			var tier = '';
			var adjustment = 0;
			// mega evolutions get the same stat boost as their base forme
			if (this.data.FormatsData[i]) tier = this.data.FormatsData[i].tier || this.data.FormatsData[toId(this.getTemplate(i).baseSpecies)].tier;
			switch (tier) {
			case 'BL':
			case 'UU':
				adjustment = 5;
				break;
			case 'BL2':
			case 'RU':
				adjustment = 10;
				break;
			case 'BL3':
			case 'NU':
			case 'NFE':
			case 'LC Uber':
			case 'LC':
				adjustment = 15;
			}

			if (adjustment) {
				for (var j in this.data.Pokedex[i].baseStats) {
					this.modData('Pokedex', i).baseStats[j] = this.clampIntRange(this.data.Pokedex[i].baseStats[j] + adjustment, 1, 255);
				}
			}
		}
	}
};

Fix 'LC Uber' Pokemon being unchanged


/**
 * isAuthenticated
 * @description :: Policy that inject user in `req` via JSON Web Token
 */

var passport = require('passport');

module.exports = function (req, res, next) {
  passport.authenticate('jwt', function (error, user, info) {
    if (error) return res.serverError(error);
    if (!user) return res.unauthorized(null, info.code, info.message);

    req.user = user;
    next();
  })(req, res);
};

/**
 * isAuthenticated
 * @description :: Policy that inject user in `req` via JSON Web Token
 */

var passport = require('passport');

module.exports = function (req, res, next) {
  passport.authenticate('jwt', function (error, user, info) {
    if (error) return res.serverError(error);
    if (!user) return res.unauthorized(null, info && info.code, info && info.message);

    req.user = user;

    next();
  })(req, res);
};

Add checking for info is exists


/**
 * A rate-limiting Throttle model, by IP address
 *
 */
var mongoose = require('mongoose')
var ipRegex = require('ip-regex')
var Schema = mongoose.Schema
mongoose.Promise = global.Promise
/**
 * Register the Throttle model
 *
 * @access public
 * @param {object} defaults
 * @return {object} Mongoose Model
 */
module.exports = function createThrottle (defaults) {
  if (defaults.mongoose.uri) {
    mongoose.createConnection(defaults.mongoose.uri)
  }
  if (!mongoose.connection.readyState) {
    console.warn('MongoDB connection not set. Skipping. Fix this by specifying the mongoose:uri option.')
  }
  var Throttle = new Schema({
    createdAt: {
      type: Date,
      required: true,
      default: Date.now,
      expires: defaults.rateLimit.ttl
    },
    ip: {
      type: String,
      required: true,
      trim: true,
      match: ipRegex()
    },
    hits: {
      type: Number,
      default: 1,
      required: true,
      max: defaults.rateLimit.max,
      min: 0
    }
  })

  Throttle.index(
    { createdAt: 1 },
    { expireAfterSeconds: defaults.rateLimit.ttl }
  )

  return mongoose.model('Throttle', Throttle)
}

/**
 * A rate-limiting Throttle model, by IP address
 *
 */
var mongoose = require('mongoose')
var Schema = mongoose.Schema
mongoose.Promise = global.Promise
/**
 * Register the Throttle model
 *
 * @access public
 * @param {object} defaults
 * @return {object} Mongoose Model
 */
module.exports = function createThrottle (defaults) {
  if (defaults.mongoose.uri) {
    mongoose.createConnection(defaults.mongoose.uri)
  }
  if (!mongoose.connection.readyState) {
    console.warn('MongoDB connection not set. Skipping. Fix this by specifying the mongoose:uri option.')
  }
  var Throttle = new Schema({
    createdAt: {
      type: Date,
      required: true,
      default: Date.now,
      expires: defaults.rateLimit.ttl
    },
    ip: {
      type: String,
      required: true,
      trim: true
    },
    hits: {
      type: Number,
      default: 1,
      required: true,
      max: defaults.rateLimit.max,
      min: 0
    }
  })

  Throttle.index(
    { createdAt: 1 },
    { expireAfterSeconds: defaults.rateLimit.ttl }
  )

  return mongoose.model('Throttle', Throttle)
}

Remove Regex validation on Throttle


/**
 * Module dependencies
 */

var util          = require('util')
  , LocalStrategy = require('passport-local').Strategy
  , User          = require('../models/User')
  , mailer        = require('../boot/mailer')
  ;

/**
 * Verifier
 */

function verifier (req, email, password, done) {
  User.authenticate(email, password, function (err, user, info) {
    if (user) {
      // throw password value away so isn't included in URLs/logged
      delete req.connectParams.password;
      delete req.connectParams.email;
    }

    done(err, user, info);
  });
};

LocalStrategy.verifier = verifier;

/**
 * Initialize
 */

function initialize (provider, configuration) {
  if (configuration.verifyEmail && !mailer) {
    throw new Error("verifyEmail is true but no mailer defined");
  }
  return new LocalStrategy(provider, verifier);
}

LocalStrategy.initialize = initialize;

/**
 * Exports
 */

module.exports = LocalStrategy;

/**
 * Module dependencies
 */

var util          = require('util')
  , LocalStrategy = require('passport-local').Strategy
  , User          = require('../models/User')
  ;

/**
 * Verifier
 */

function verifier (req, email, password, done) {
  User.authenticate(email, password, function (err, user, info) {
    if (user) {
      // throw password value away so isn't included in URLs/logged
      delete req.connectParams.password;
      delete req.connectParams.email;
    }

    done(err, user, info);
  });
};

LocalStrategy.verifier = verifier;

/**
 * Initialize
 */

function initialize (provider, configuration) {
  return new LocalStrategy(provider, verifier);
}

LocalStrategy.initialize = initialize;

/**
 * Exports
 */

module.exports = LocalStrategy;

Remove dated configuration property check


'use strict';

angular.module('chatApp', [])

.controller('ChatCtrl', function($scope, $http) {

   $scope.chatChannel = "chatApp";
   $scope.messageLimit = 50;
   $scope.defaultUsername = "Guest";

   $scope.currentConnectionStatus = 0;
   $scope.errorMessage;
   $scope.loggedIn = false;

   PUBNUB.subscribe({
      channel: $scope.chatChannel,
      restore: false,

      callback: function() { },

      disconnect: function() {
         $scope.$apply(function(){
            $scope.currentConnectionStatus = 0;
         });
      },

      reconnect: function() {
         $scope.$apply(function(){
            $scope.currentConnectionStatus = 1;
         });
      },

      connect: function() {
         $scope.$apply(function(){
            $scope.currentConnectionStatus = 2;
         });
      }
   });

   $scope.attemptLogin = function() {
      $scope.errorMessage = "";

      if (!$scope.message.username) {
         $scope.errorMessage = "you must enter a username.";
         return;
      }

      if (!$scope.currentConnectionStatus) {
         $scope.errorMessage = "you're not connect to our chat room yet.";
         return;
      }

      $scope.loggedIn = true;
   };

});

'use strict';

angular.module('chatApp', [])

.controller('ChatCtrl', function($scope, $http) {

   $scope.chatChannel = "chatApp";
   $scope.messageLimit = 50;
   $scope.defaultUsername = "Guest";

   $scope.currentConnectionStatus = 0;
   $scope.errorMessage;
   $scope.loggedIn = false;

   PUBNUB.subscribe({
      channel: $scope.chatChannel,
      restore: false,

      callback: function() { },

      disconnect: function() {
         $scope.$apply(function(){
            $scope.currentConnectionStatus = 0;
         });
      },

      reconnect: function() {
         $scope.$apply(function(){
            $scope.currentConnectionStatus = 1;
         });
      },

      connect: function() {
         $scope.$apply(function(){
            $scope.currentConnectionStatus = 2;
         });
      }
   });

   $scope.attemptLogin = function() {
      $scope.errorMessage = "";

      if (!$scope.message.username) {
         $scope.errorMessage = "you must enter a username.";
         return;
      }

      if (!$scope.currentConnectionStatus) {
         $scope.errorMessage = "you're not connect to our chat room yet.";
         return;
      }

      $scope.loggedIn = true;
   };

   $scope.postMessage = function() {

   };

});

Add function to post a message to the channel


var traceur = Npm.require('traceur');
var os = Npm.require('os');

Plugin.registerSourceHandler("next.js", function (compileStep) {
  var oldPath = compileStep.inputPath;
  var newPath = oldPath.replace(/\.next\.js$/, '.now.js');

  var options = {
    filename: oldPath,
    sourceMap: true
  };
  var content = compileStep.read().toString('utf8');
  var output = traceur.compile(content, options);

  if (output.errors.length) {
    throw new Error(output.errors.join(os.EOL));
  }

  compileStep.addJavaScript({
    sourcePath: oldPath,
    path: newPath,
    data: output.js,
    sourceMap: output.sourceMap
  });
});

var traceur = Npm.require('traceur');

Plugin.registerSourceHandler("next.js", function (compileStep) {
  var oldPath = compileStep.inputPath;
  var newPath = oldPath.replace(/\.next\.js$/, '.now.js');

  var options = {
    filename: oldPath,
    sourceMap: true
  };
  var content = compileStep.read().toString('utf8');
  var output = traceur.compile(content, options);

  if (output.error) {
    throw new Error(output.error);
  }

  compileStep.addJavaScript({
    sourcePath: oldPath,
    path: newPath,
    data: output.js,
    sourceMap: output.sourceMap
  });
});

Use preferred error handling from Traceur docs


'use strict'

var proxy = {
  proxyType: 'autodetect'
}

if (process.env.http_proxy !== undefined && process.env.http_proxy !== null) {
  proxy = {
    proxyType: 'manual',
    httpProxy: process.env.http_proxy
  }
}

exports.config = {
  directConnect: true,

  allScriptsTimeout: 80000,

  specs: [
    'test/e2e/*.js'
  ],

  capabilities: {
    browserName: 'chrome',
    proxy: proxy
  },

  baseUrl: 'http://localhost:3000',

  framework: 'jasmine2',

  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 90000
  },

  onPrepare: function () {
    var jasmineReporters = require('jasmine-reporters')
    jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
      consolidateAll: true,
      savePath: 'build/reports/e2e_results'
    }))

    // Get cookie consent popup out of the way
    browser.get('/#')
    browser.manage().addCookie({ name: 'cookieconsent_status', value: 'dismiss' })
  }
}

if (process.env.TRAVIS_BUILD_NUMBER) {
  exports.config.capabilities.chromeOptions = {
    args: ['--headless', '--disable-gpu', '--window-size=800,600']
  }
}

'use strict'

var proxy = {
  proxyType: 'autodetect'
}

if (process.env.HTTP_PROXY !== undefined && process.env.HTTP_PROXY !== null) {
  proxy = {
    proxyType: 'manual',
    httpProxy: process.env.HTTP_PROXY
  }
}

exports.config = {
  directConnect: true,

  allScriptsTimeout: 80000,

  specs: [
    'test/e2e/*.js'
  ],

  capabilities: {
    browserName: 'chrome',
    proxy: proxy
  },

  baseUrl: 'http://localhost:3000',

  framework: 'jasmine2',

  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 90000
  },

  onPrepare: function () {
    var jasmineReporters = require('jasmine-reporters')
    jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({
      consolidateAll: true,
      savePath: 'build/reports/e2e_results'
    }))

    // Get cookie consent popup out of the way
    browser.get('/#')
    browser.manage().addCookie({ name: 'cookieconsent_status', value: 'dismiss' })
  }
}

if (process.env.TRAVIS_BUILD_NUMBER) {
  exports.config.capabilities.chromeOptions = {
    args: ['--headless', '--disable-gpu', '--window-size=800,600']
  }
}

Use all-caps env var name


//Running BB Router and immediately extending itself
window.TodoApp = new (Backbone.Router.extend({
	routes: { '' : 'index'}, //when base route ('') run the index function/controller
	initialize: function() { //init the following
		this.todoItems = new TodoItems();
		this.todosView = new TodosView({collection: this.todoItems});
		this.todosView.render();// start everything off with a first Render

		//using JQ outside of app render view. Btn is in index so we have to handle somehow.
		$('.btn-clear').click(function(e) {
			window.TodoApp.todosView.filterCompleted()
		});
	},

	index: function() {
		//Add fixture data
		var fixtures = [
		{ val: "Apples", completed: true},
		{ val: "Bananna", completed: false},
		{ val: "Pear", completed: true},
		{ val: "Peach", completed: false}
		];

		$('#app').html(this.todosView.el);
		this.todoItems.reset(fixtures); //Reset will just fill out with updated model
	},

	start: function() { // To load all BB first
		Backbone.history.start(); //then track html history. Html push states.
	}
}));

//Running BB Router and immediately extending itself
window.TodoApp = new (Backbone.Router.extend({
	routes: { '' : 'index'}, //when base route ('') run the index function/controller
	initialize: function() { //init the following
		this.todoItems = new TodoItems();
		this.todosView = new TodosView({collection: this.todoItems});
		this.todosView.render();// start everything off with a first Render

		//using JQ outside of app render view. Btn is in index so we have to handle somehow.
		$('.btn-clear').click(function(e) {
			window.TodoApp.todosView.filterCompleted()
		});

		$('.btn-success').click(function() {
			window.TodoApp.todoItems.add({val:$("#newTodo").val(), completed: false}); //Add new value in todo on click
			$("#newTodo").val(''); //clear it out
		});
	},

	index: function() {
		//Add fixture data
		var fixtures = [
		{ val: "Apples", completed: true},
		{ val: "Bananna", completed: false},
		{ val: "Pear", completed: true},
		{ val: "Peach", completed: false}
		];

		$('#app').html(this.todosView.el);
		this.todoItems.reset(fixtures); //Reset will just fill out with updated model
	},

	start: function() { // To load all BB first
		Backbone.history.start(); //then track html history. Html push states.
	}
}));

Add item and clear input now functional


/*global WebWorker:false */
(function () {
    'use strict';

    var worker = null,
        $output = null;

    $output = $('#worker-output');

    /**
    worker = new Worker('/js/example-worker.js');

    worker.addEventListener('message', function (event) {
        $output.text(event.data);
        return;
    })

    //worker.postMessage();
    /**/

    worker = new WebWorker('/js/example-worker.js');
    window.worker = worker;

    worker.on('message', function (event) {
        $output.text(event.data);
        return;
    });

    worker.on(WebWorker.Event.WORKER_LOADING, function () {
        console.log('worker loading');
        return;
    });

    worker.on(WebWorker.Event.WORKER_LOADED, function () {
        console.log('has loaded');
        worker.start();
        return;
    });

    worker.on(WebWorker.Event.WORKER_STARTING, function () {
        console.log('worker starting');
        return;
    });

    worker.on(WebWorker.Event.WORKER_STARTED, function () {
        console.log('worker started');
        return;
    });

    worker.load();

    return;
})();

/*global WebWorker:false */
(function () {
    'use strict';

    var worker = null,
        $output = null;

    $output = $('#worker-output');

    /**
    worker = new Worker('/js/example-worker.js');

    worker.addEventListener('message', function (event) {
        $output.text(event.data);
        return;
    })

    //worker.postMessage();
    /**/

    worker = new WebWorker('/js/example-worker.js');
    window.worker = worker;

    worker.on('message', function (event) {
        $output.text(event.data);
        return;
    });

    worker.on(WebWorker.Event.WORKER_LOADING, function () {
        console.log('worker loading');
        return;
    });

    worker.on(WebWorker.Event.WORKER_LOADED, function () {
        console.log('worker loaded');
        worker.start();
        return;
    });

    worker.on(WebWorker.Event.WORKER_STARTING, function () {
        console.log('worker starting');
        return;
    });

    worker.on(WebWorker.Event.WORKER_STARTED, function () {
        console.log('worker started');
        worker.terminate();
        return;
    });

    worker.on(WebWorker.Event.WORKER_TERMINATING, function () {
        console.log('worker terminating');
        return;
    });

    worker.on(WebWorker.Event.WORKER_TERMINATED, function () {
        console.log('worker terminated');
        return;
    });

    worker.load();

    return;
})();

Add some event listeners on example page to track progress of worker.


'use strict'
const grunt = require('gruntfile-api')
const fs = require('fs')
const path = require('path')
const spawn = require('child_process').spawn

global.processes = {}

exports.getTasks = function () {
  return new Promise(function (resolve, reject) {
    const gruntfile = fs.readFileSync(path.join(window.localStorage.getItem('current'), 'Gruntfile.js'))
    grunt.init(gruntfile)
    let tasks = JSON.parse(grunt.getJsonTasks())

    let taskList = []

    for (let task in tasks) {
      taskList.push(task)
    }

    return resolve(taskList)
  })
}

exports.runTask = function (task, cb) {
  // Stop task if running
  if (global.processes[task]) {
    global.processes[task].kill()
    return
  }

  let dir = window.localStorage.getItem('current')
  var command = spawn('grunt', [task], {cwd: dir})

  // Save
  global.processes[task] = command

  cb()

  // Remove when done
  command.on('close', function () {
    delete global.processes[task]
    cb()
  })
}

'use strict'
const grunt = require('gruntfile-api')
const fs = require('fs')
const path = require('path')
const spawn = require('child_process').spawn

global.processes = {}

exports.getTasks = function () {
  return new Promise(function (resolve, reject) {
    fs.readFile(path.join(window.localStorage.getItem('current'), 'Gruntfile.js'), function (err, gruntfile) {
      if (err) return resolve([])

      grunt.init(gruntfile)
      let tasks = JSON.parse(grunt.getJsonTasks())

      let taskList = []

      for (let task in tasks) {
        taskList.push(task)
      }

      return resolve(taskList)
    })
  })
}

exports.runTask = function (task, cb) {
  // Stop task if running
  if (global.processes[task]) {
    global.processes[task].kill()
    return
  }

  let dir = window.localStorage.getItem('current')
  var command = spawn('grunt', [task], {cwd: dir})

  // Save
  global.processes[task] = command

  cb()

  // Remove when done
  command.on('close', function () {
    delete global.processes[task]
    cb()
  })
}

Use callback instead of sync


/**
 * Module dependencies
 */

var defaults = require('./helpers');
var template = require('./template');

module.exports = createInstance;

/**
 * Create an instance of JSONt
 *
 * @return {JSONt}
 */

function createInstance() {
  function JSONt(tmpl, options){ return JSONt.compile(tmpl, options); };
  JSONt.helpers = {};
  JSONt.helpers.__proto__ = defaults;

  JSONt.use = use;
  JSONt.plugin = plugin;
  JSONt.compile = compile;

  return JSONt;
};

/**
 * Use a helpers
 *
 * @param {String} name
 * @param {Function} fun
 * @return {JSONt} for chaining
 */

function use(name, fun) {
  this.helpers[name] = fun;
  return this;
};

/**
 * Register a collection of helpers
 */

function plugin(fn) {
  fn(this);
  return this;
};

/**
 * Compile a template with the default helpers
 *
 * @param {String|Object} tmpl
 * @param {Object} options
 * @return {Template}
 */

function compile(tmpl, options) {
  return template(tmpl, options || {}, this.helpers);
};

/**
 * Module dependencies
 */

var defaults = require('./helpers');
var template = require('./template');

module.exports = createInstance;

/**
 * Create an instance of JSONt
 *
 * @return {JSONt}
 */

function createInstance() {
  function JSONt(tmpl, options){ return JSONt.compile(tmpl, options); };
  JSONt.helpers = {};
  JSONt.helpers.__proto__ = defaults;

  JSONt.use = use;
  JSONt.plugin = plugin;
  JSONt.compile = compile;
  JSONt.render = render;
  JSONt.renderFile = renderFile;

  return JSONt;
};

/**
 * Use a helpers
 *
 * @param {String} name
 * @param {Function} fun
 * @return {JSONt} for chaining
 */

function use(name, fun) {
  this.helpers[name] = fun;
  return this;
};

/**
 * Register a collection of helpers
 */

function plugin(fn) {
  fn(this);
  return this;
};

/**
 * Compile a template with the default helpers
 *
 * @param {String|Object} tmpl
 * @param {Object} options
 * @return {Template}
 */

function compile(tmpl, options) {
  return template(tmpl, options || {}, this.helpers);
};

/**
 * Compile and render a template
 *
 * @param {String|Object} tmpl
 * @param {Object} options
 * @param {Function} fn
 */

function render(tmpl, options, fn) {
  this.compile(tmpl, options)(options, fn);
};

/**
 * Compile and render a template
 *
 * @param {String} filename
 * @param {Object} options
 * @param {Function} fn
 */

function renderFile(file, options, fn) {
  this.compile(require(file), options)(options, fn);
};

Add render and renderFile methods


const fs = require("fs");
const glob = require("glob");

const ext = "{js,js.map,d.ts,d.ts.map}";

glob.sync("{def,lib}/**/*." + ext)
  .concat(glob.sync("test{*,/!(data)/**}/*." + ext))
  .concat(glob.sync("{fork,main}." + ext))
  .forEach(filename => {
    if (fs.existsSync(filename)) {
      fs.unlinkSync(filename);
    }
  });

const fs = require("fs");
const glob = require("glob");

const ext = "{js,js.map,d.ts,d.ts.map}";

glob.sync("{def,lib}/**/*." + ext)
  .concat(glob.sync("test{*,/!(data)/**}/*." + ext))
  .concat(glob.sync("{fork,main,types}." + ext))
  .forEach(filename => {
    if (fs.existsSync(filename)) {
      fs.unlinkSync(filename);
    }
  });

Clean emitted files for types.ts


var Cookielaw = {
    ACCEPTED: '1',
    REJECTED: '0',

    createCookie: function (name, value, days) {
        var date = new Date(),
            expires = '';
        if (days) {
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toGMTString();
        } else {
            expires = "";
        }
        document.cookie = name + "=" + value + expires + "; path=/";
    },

    createCookielawCookie: function (cookieValue) {
        cookieValue = cookieValue || this.ACCEPTED;
        this.createCookie('cookielaw_accepted', cookieValue, 10 * 365);

        if (typeof (window.jQuery) === 'function') {
            jQuery('#CookielawBanner').slideUp();
        } else {
            document.getElementById('CookielawBanner').style.display = 'none';
        }
    },

    accept: function () {
        this.createCookielawCookie(this.ACCEPTED);
    },

    reject: function () {
          this.createCookielawCookie(this.REJECTED);
    }
};

var Cookielaw = {
    ACCEPTED: '1',
    REJECTED: '0',

    createCookie: function (name, value, days, secure=true) {
        var date = new Date(),
            expires = '';
        if (days) {
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toGMTString();
        } else {
            expires = "";
        }

        var secureString = "";
        if (window.isSecureContext && secure) {
            secureString = "; Secure";
        }

        document.cookie = name + "=" + value + expires + "; path=/" + secureString;
    },

    createCookielawCookie: function (cookieValue, secure=true) {
        cookieValue = cookieValue || this.ACCEPTED;
        this.createCookie('cookielaw_accepted', cookieValue, 10 * 365, secure);

        if (typeof (window.jQuery) === 'function') {
            jQuery('#CookielawBanner').slideUp();
        } else {
            document.getElementById('CookielawBanner').style.display = 'none';
        }
    },

    accept: function () {
        this.createCookielawCookie(this.ACCEPTED);
    },

    reject: function () {
          this.createCookielawCookie(this.REJECTED);
    }
};

Add option to use "Secure" attribute on cookies


var activeIcons = {
  path: {
    '19': 'icons/active-19.png',
    '38': 'icons/active-38.png'
  }
};

var inactiveIcons = {
  path: {
    '19': 'icons/inactive-19.png',
    '38': 'icons/inactive-38.png'
  }
};

var active = false;

function toggleIcon() {
  chrome.browserAction.setIcon(active ? inactiveIcons : activeIcons);
  active = !active;
}

chrome.browserAction.onClicked.addListener(toggleIcon);

var manifest = chrome.runtime.getManifest();

var inactiveIcons = manifest.browser_action.default_icon;
var activeIcons = {
  '19': 'icons/active-19.png',
  '38': 'icons/active-38.png'
};

var inactiveTitle = manifest.browser_action.default_title;
var activeTitle = 'Stop showing line lengths';

var active = false;

function toggleActiveState() {
  chrome.browserAction.setIcon({
    path: active ? inactiveIcons : activeIcons
  });

  chrome.browserAction.setTitle({
    title: active ? inactiveTitle : activeTitle
  });

  active = !active;
}

chrome.browserAction.onClicked.addListener(toggleActiveState);

Reduce redundancy be inspecting manifest


import React, {Component} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import ApartmentUpdates from '../apartments/ApartmentUpdates';
import TenantUpdates from '../tenants/TenantUpdates';
import Apartment from '../apartments/Apartment';
import Tenant from '../tenants/Tenant';

const Home = () => (
  <div className="Updates">
    <MuiThemeProvider>
      <ApartmentUpdates />
    </MuiThemeProvider>
    <MuiThemeProvider>
      <TenantUpdates />
    </MuiThemeProvider>
    <Apartment />
    <Tenant />
  </div>
)

export default Home;

import React, {Component} from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import ApartmentUpdates from '../apartments/ApartmentUpdates';
import TenantUpdates from '../tenants/TenantUpdates';
import Apartment from '../apartments/Apartment';
import Tenant from '../tenants/Tenant';

const Home = () => (
  <div className="Updates">
    <MuiThemeProvider>
      <ApartmentUpdates />
    </MuiThemeProvider>
    <MuiThemeProvider>
      <TenantUpdates />
    </MuiThemeProvider>
  </div>
)

export default Home;

Fix bug on home page


var el = require('./el');
var outputEl = document.querySelector('#el-js');

function assertDomContent(elTree, expected) {
    el(outputEl, elTree);
    var actual = outputEl.innerHTML.replace(/\s+/g, ' ').trim();
    if (actual === expected) return;
    throw new Error('Unexpected element output; to debug this, break on errors and inspect the DOM');
}

function resetOutputEl() {
    outputEl.innerHTML = '';
}

describe('el.js', function() {

    afterEach(resetOutputEl);

    it('produces the simplest possible element', function() {
        assertDomContent(
            el('div', 'Hello World'),
            '
Hello World
'
        );
    });

});

var el = require('./el');
var outputEl = document.querySelector('#el-js');

function assertDomContent(elTree, expected) {
    el(outputEl, elTree);
    var actual = outputEl.innerHTML.replace(/\s+/g, ' ').trim();
    if (actual === expected) return;
    throw new Error('\n\nExpected: ' + expected + '\n  Actual: ' + actual + '\n\n');
}

function resetOutputEl() {
    outputEl.innerHTML = '';
}

describe('el.js', function() {

    afterEach(resetOutputEl);

    it('produces the simplest possible element', function() {
        assertDomContent(
            el('div', 'Hello World'),
            '
Hello World
'
        );
    });

    it('produces more complex elements', function() {
        assertDomContent(
            el('p', { class: 'cool' },
                el('<!', 'this is a comment'),
                el('a', 'Click here', {
                    href: '#some-location'
                }),
                el('', 'Text after link')
            ),
            '
Click hereText after link
'
        );
    });

});

Add a more involved test case.


jQuery(function() {
    jQuery('[data-star-rating]').each(function(index, element) {
        element = $(element);

        var data = element.data('star-rating'),
            globalOptions = window.StarRatingsOptions ? window.StarRatingsOptions : {},
            options = jQuery.extend(data.options, globalOptions, {
                callback: function(currentRating, element) {
                    $.post(data.uri, { id: data.id, rating: currentRating })
                     .done(function() {
                        console.log('success');
                     })
                     .fail(function() {
                        console.log('fail');
                     });
                }
            });

        if (options.readOnly) {
            element.addClass('disabled');
        }
        element.starRating(options);
    });
});

jQuery(function() {
    jQuery('[data-star-rating]').each(function(index, element) {
        element = $(element);

        var data = element.data('star-rating'),
            globalOptions = window.StarRatingsOptions ? window.StarRatingsOptions : {},
            options = jQuery.extend(data.options, globalOptions, {
                callback: function(currentRating, element) {
                    $.post(data.uri, { id: data.id, rating: currentRating })
                     .done(function() {
                        console.log('success');
                     })
                     .fail(function() {
                        console.log('fail');
                     });
                }
            });

        if (element.data('voted')) {
            options.readOnly = true;
        }

        if (options.readOnly) {
            element.addClass('disabled');
        }

        element.starRating(options);
    });
});

Disable voting if already voted


'use strict';

var paths = require('./_paths');
var gulp = require('gulp');
var filter = require('gulp-filter');
var sass = require('gulp-ruby-sass');
var browserSync = require('browser-sync');
var reload = browserSync.reload;

gulp.task('ie-css', ['clean-iecss'], function() {
  return gulp
    .src(paths.ie_styles)
    .pipe(sass({
      lineNumbers: true,
      style: 'compact'
    }))
    .on('error', function (err) { console.log(err.message); })
    .pipe(gulp.dest(paths.dest + 'ie-stylesheets/'))
    .pipe(filter('**/*.css'))  // don't react to .map files when reloading browser
    .pipe(reload({ stream:true }));
});

'use strict';

var paths = require('./_paths');
var gulp = require('gulp');
var filter = require('gulp-filter');
var sass = require('gulp-ruby-sass');
var browserSync = require('browser-sync');
var reload = browserSync.reload;

gulp.task('ie-css', ['clean-iecss'], function() {
  return gulp
    .src(paths.ie_styles)
    .pipe(sass({
      lineNumbers: true,
      style: 'compact',
      loadPath: 'node_modules/govuk_frontend_toolkit/' // add node module toolkit path
    }))
    .on('error', function (err) { console.log(err.message); })
    .pipe(gulp.dest(paths.dest + 'ie-stylesheets/'))
    .pipe(filter('**/*.css'))  // don't react to .map files when reloading browser
    .pipe(reload({ stream:true }));
});

Fix Guild IE CSS loadPath


/*
 * grunt-contrib-rename (unofficial)
 * https://github.com/jasonlam604/grunt-contrib-rename
 *
 * Copyright (c) 2013 Jason Lam
 * Licensed under the MIT license.
 * https://github.com/jasonlam604/grunt-contrib-rename/blob/master/LICENSE-MIT
 */
'use strict';

var fs = require('fs');

module.exports = function(grunt) {

  grunt.registerMultiTask('rename', 'Rename file', function() {

    var options = this.options({
      force: false
    });

    grunt.verbose.writeflags(options, 'Options');

    this.files.forEach(function(filePair) {

      filePair.src.forEach(function(src) {

				if (!grunt.file.exists(String(filePair.src))) {
          grunt.log.writeln('Cannot rename non-existent file.');
				} else {

					if (fs.statSync(String(filePair.src)).isDirectory()) {
						grunt.log.writeln('Renaming Directory ' + filePair.src + ' -> ' + filePair.dest);
					} else {
						grunt.log.writeln('Renaming File ' + filePair.src + ' -> ' + filePair.dest);
					}

					fs.renameSync(String(filePair.src), String(filePair.dest), function (err) {
						if (err) {
							grunt.log.error();
							grunt.verbose.error();
							grunt.fail.warn('Rename operation failed.');
						}
					});
				}
      });
    });
  });
};

/*
 * grunt-rename-util
 * https://github.com/jasonlam604/grunt-rename-util
 *
 * Copyright (c) 2013 Jason Lam
 * Licensed under the MIT license.
 * https://github.com/jasonlam604/grunt-rename-util/blob/master/LICENSE-MIT
 */
'use strict';

var fs = require('fs');

module.exports = function(grunt) {

  grunt.registerMultiTask('rename', 'Rename file', function() {

    var options = this.options({
      force: false
    });

    grunt.verbose.writeflags(options, 'Options');

    this.files.forEach(function(filePair) {

      filePair.src.forEach(function(src) {

				if (!grunt.file.exists(String(filePair.src))) {
          grunt.log.writeln('Cannot rename non-existent file.');
				} else {

					if (fs.statSync(String(filePair.src)).isDirectory()) {
						grunt.log.writeln('Renaming Directory ' + filePair.src + ' -> ' + filePair.dest);
					} else {
						grunt.log.writeln('Renaming File ' + filePair.src + ' -> ' + filePair.dest);
					}

					fs.renameSync(String(filePair.src), String(filePair.dest), function (err) {
						if (err) {
							grunt.log.error();
							grunt.verbose.error();
							grunt.fail.warn('Rename operation failed.');
						}
					});
				}
      });
    });
  });
};

Update comment to reflect new package name


Ext.define('Slate.sorter.Code', {
    extend: 'Ext.util.Sorter',

    config: {
        numberRe: /^\d+$/,
        numberDelim: '.',

        sorterFn: function(a, b) {
            var codeA = a.get('Code').toLowerCase(),
                codeB = b.get('Code').toLowerCase(),
                numberRe = this._numberRe, // eslint-disable-line no-underscore-dangle
                numberDelim = this._numberDelim, // eslint-disable-line no-underscore-dangle
                dotIndexA, dotIndexB,
                numberA, numberB;

            if (codeA == codeB) {
                return 0;
            }

            dotIndexA = codeA.lastIndexOf(numberDelim);
            dotIndexB = codeB.lastIndexOf(numberDelim);

            if (
                dotIndexA == -1
                || dotIndexB == -1
                || codeA.substr(0, dotIndexA) != codeB.substr(0, dotIndexB)
                || (numberA = codeA.substr(dotIndexA + 1)) == ''
                || (numberB = codeB.substr(dotIndexB + 1)) == ''
                || !numberRe.test(numberA)
                || !numberRe.test(numberB)
            ) {
                return codeA < codeB ? -1 : 1;
            }

            return numberA - numberB;
        }
    },

});

Ext.define('Slate.sorter.Code', {
    extend: 'Ext.util.Sorter',

    config: {
        numberRe: /^\d+$/,
        numberDelim: '.',

        codeFn: function(item) {
            return item.get('Code');
        },

        sorterFn: function(a, b) {
            var me = this,
                codeFn = me._codeFn, // eslint-disable-line no-underscore-dangle
                numberRe = me._numberRe, // eslint-disable-line no-underscore-dangle
                numberDelim = me._numberDelim, // eslint-disable-line no-underscore-dangle
                codeA = codeFn(a).toLowerCase(),
                codeB = codeFn(b).toLowerCase(),
                dotIndexA, dotIndexB,
                numberA, numberB;

            if (codeA == codeB) {
                return 0;
            }

            dotIndexA = codeA.lastIndexOf(numberDelim);
            dotIndexB = codeB.lastIndexOf(numberDelim);

            if (
                dotIndexA == -1
                || dotIndexB == -1
                || codeA.substr(0, dotIndexA) != codeB.substr(0, dotIndexB)
                || (numberA = codeA.substr(dotIndexA + 1)) == ''
                || (numberB = codeB.substr(dotIndexB + 1)) == ''
                || !numberRe.test(numberA)
                || !numberRe.test(numberB)
            ) {
                return codeA < codeB ? -1 : 1;
            }

            return numberA - numberB;
        }
    },

    constructor: function(config) {
        this.initConfig(config);
    }
});

Add support for configurable function to extract code from item for sorting


import store from 'store';
import uuid from 'node-uuid';
import later from 'later';

function initDeviceID() {
	let deviceID = store.get('device_id');
	if (!deviceID) {
		store.set('device_id', uuid.v4());
	}
}

function setupWorker() {
	// var sched = later.parse.cron('1/1 * * * *');
	var sched = later.parse.cron('0,30 * * * *');
	later.setInterval(runWorker, sched);
}

function runWorker() {
	var deviceID = store.get('device_id');
	let worker = new Worker('./js/worker.js');

	worker.onmessage = (e) => {
		// TODO: update update records
		const { unreadChapters, comicID } = e.data;
		// console.log(unreadChapters.length);

		switch(window.PLATFORM) {
		case 'electron':
			// TODO: create native notification
			// var { ipcRenderer } = require('electron');

			break;
		case 'chrome_extension':
			// TODO create chrome notification

			break;
		default:
			throw 'Unsupported Platform';
		}
	};

	// start worker
	worker.postMessage({deviceID});
}

export function initializeApp({callback}) {
	initDeviceID();
	setupWorker();

	callback();
}

import store from 'store';
import uuid from 'node-uuid';
import later from 'later';

let worker;

function initDeviceID() {
	let deviceID = store.get('device_id');
	if (!deviceID) {
		store.set('device_id', uuid.v4());
	}
}

function setupWorker() {
	// var sched = later.parse.cron('1/1 * * * *');
	var sched = later.parse.cron('0,30 * * * *');
	later.setInterval(runWorker, sched);
}

function runWorker() {
	var deviceID = store.get('device_id');

	if (typeof worker !== 'undefined') { worker.terminate(); }
	worker = new Worker('./js/worker.js');

	worker.onmessage = (e) => {
		// TODO: update update records
		const { unreadChapters, comicID } = e.data;
		// console.log(unreadChapters.length);

		switch(window.PLATFORM) {
		case 'electron':
			// TODO: create native notification
			// var { ipcRenderer } = require('electron');

			break;
		case 'chrome_extension':
			// TODO create chrome notification

			break;
		default:
			throw 'Unsupported Platform';
		}
	};

	// start worker
	worker.postMessage({deviceID});
}

export function initializeApp({callback}) {
	initDeviceID();
	setupWorker();

	callback();
}

Terminate worker before every task start


var nano = require('nano')('http://localhost:5984');
var db = nano.db.use('todo');

var todo = {};

todo.getAll = function() {
  var results = [];
  db.view('todos', 'all_todos', function(err, body) {
    for (var row of body.rows) {
      results.push(row.value);
    }
    return results;
  });
};

module.exports = todo;

var nano = require('nano')('http://localhost:5984');
var db = nano.db.use('todo');

var todo = {};

todo.getAll = function(callback) {
  var results = [];
  db.view('todos', 'all_todos', function(err, body) {
    for (var row of body.rows) {
      results.push(row.value);
    }
    callback(err, results);
  });
};

module.exports = todo;

Change getAll to use a callback in place of return


if (process.platform != 'win32') {
  var commandExists = require('command-exists');
  var colors = require('colors');
  var depedencies = [
    'automake',
    'autoconf',
    'pkg-config',
    'wget',
    'libtool',
    'python2'
  ];

  depedencies.forEach(function(dep) {
    commandExists(dep, function(err, commandExists) {
      if(!commandExists) {
        console.error(`Missing dependency: ${dep.bold} Could not be found on your system.`.red);
      }
    });
  });
}

if (process.platform != 'win32') {
  var commandExists = require('command-exists');
  var colors = require('colors');
  var depedencies = [
    'make',
    'gcc',
    'automake',
    'autoconf',
    'pkg-config',
    'wget',
    'libtool',
    'python2'
  ];

  depedencies.forEach(function(dep) {
    commandExists(dep, function(err, commandExists) {
      if(!commandExists) {
        console.error(`Missing dependency: ${dep.bold} Could not be found on your system.`.red);
      }
    });
  });
}

Add make and gcc to dependencies


var assert = require('assert');
var equal = assert.equal;
var path = require('path');
var basePath = process.cwd();

module.exports = function() {
  var Events = require(path.resolve(basePath + '/lib/base/events'));

  describe('Events', function() {
    var events;
    var handlersRun;

    beforeEach(function() {
      events = new Events();
      handlersRun = [];
      events.on('A', eventHandler('A'));
      events.on('B', eventHandler('B'));
    });

    function eventHandler(event) {
      return function() {
        handlersRun.push(event);
      }
    }

    describe('#off()', function() {
      it('should deregister multiple, space-separated events', function() {
        events.off('A B');
        events.trigger('A');
        events.trigger('B');
        equal(handlersRun.length, 0);
      });
    });
  });
};

var path = require('path');

module.exports = function() {
  var Events = require(path.resolve(process.cwd(), 'lib/base/events'));

  describe('Events', function() {
    var events;

    beforeEach(function() {
      events = new Events();
    });

    describe('#off()', function() {
      it('should deregister multiple space-separated events', function() {
        function eventHandler() {
          throw new Error('Expected event handler to have not been called');
        }

        events.on('A', eventHandler);
        events.on('B', eventHandler);
        events.off('A B');
        events.trigger('A');

        expect(events._eventsCount).to.equal(0);
      });
    });

    describe('#trigger()', function() {
      it('should pass additional arguments to the listener', function() {
        events.on('event', function(name, arg1, arg2) {
          expect(name).to.equal('event');
          expect(arg1).to.equal(1);
          expect(arg2).to.equal(2);
        })
        events.trigger('event', 1, 2);
      });
    });
  });
};

Refactor and add new Events unit tests


/* jshint node:true */
/**
 * PUSH Notification server
 * (c) Telefonica Digital, 2012 - All rights reserved
 * License: GNU Affero V3 (see LICENSE file)
 * Fernando Rodríguez Sela <frsela@tid.es>
 * Guillermo Lopez Leal <gll@tid.es>
 */

'use strict';

function ConnectorWebSocket(data, connection) {
    this.data = data;
    this.connection = connection;
}

ConnectorWebSocket.prototype = {
    getType: function() {
        return 'WS';
    },

    getServer: function() {
        return process.serverId;
    },

    getConnection: function() {
        return this.connection;
    },

    getInterface: function() {
        return null;
    },

    getMobileNetwork: function() {
        return null;
    },

    getProtocol: function() {
        return 'ws';
    },

    canBeWakeup: function() {
        return false;
    },

    resetAutoclose: function() {
        // nothing to do on this connector
    },

    notify: function(msgList) {
        this.connection.sendUTF(JSON.stringify(msgList));
    }
};

module.exports = ConnectorWebSocket;

/* jshint node:true */
/**
 * PUSH Notification server
 * (c) Telefonica Digital, 2012 - All rights reserved
 * License: GNU Affero V3 (see LICENSE file)
 * Fernando Rodríguez Sela <frsela@tid.es>
 * Guillermo Lopez Leal <gll@tid.es>
 */

'use strict';

function ConnectorWebSocket(data, connection) {
    this.data = data;
    this.connection = connection;
}

ConnectorWebSocket.prototype = {
    getType: function() {
        return 'WS';
    },

    getServer: function() {
        return process.serverId;
    },

    getConnection: function() {
        return this.connection;
    },

    getInterface: function() {
        return null;
    },

    getMobileNetwork: function() {
        return this.data.mobilenetwork || null;
    },

    getProtocol: function() {
        return 'ws';
    },

    canBeWakeup: function() {
        return false;
    },

    resetAutoclose: function() {
        // nothing to do on this connector
    },

    notify: function(msgList) {
        this.connection.sendUTF(JSON.stringify(msgList));
    }
};

module.exports = ConnectorWebSocket;

Save mcc-mnc data to WS connector, so monitor is able to print logs for Deployment KPIs


module.exports = function(gulp, speck) {
  return gulp.task('js:vendor', function() {
    var uglify = require('gulp-uglify'),
      gulpif = require('gulp-if'),
      insert = require('gulp-insert'),
      concat = require('gulp-concat');

    return gulp.src(speck.config.vendorJS)
      .pipe(gulpif(speck.build.env.optimise, uglify()))
      .pipe(concat('vendor.js'))
      .pipe(gulpif(speck.build.env.optimise, insert.prepend(
        '/*\n' +
        '* ' + speck.config.name + ', ' + speck.config.version + ' (' + speck.config.currentRevision + ')\n' +
        '* ' + 'vendor.js built ' + new Date().toISOString().substring(0, 10) + '\n' +
        '* Don\'t edit this file directly.\n' +
        '*/\n'
      )))
      .pipe(gulp.dest(speck.assets.build.js));
  });
};

module.exports = function(gulp, speck) {
  return gulp.task('js:vendor', function() {
    var uglify = require('gulp-uglify'),
      gulpif = require('gulp-if'),
      insert = require('gulp-insert'),
      size = require('gulp-size'),
      concat = require('gulp-concat');

    return gulp.src(speck.config.vendorJS)
      .pipe(gulpif(speck.build.env.optimise, uglify()))
      .pipe(concat('vendor.js'))
      .pipe(gulpif(speck.build.env.optimise, insert.prepend(
        '/*\n' +
        '* ' + speck.config.name + ', ' + speck.config.version + ' (' + speck.config.currentRevision + ')\n' +
        '* ' + 'vendor.js built ' + new Date().toISOString().substring(0, 10) + '\n' +
        '* Don\'t edit this file directly.\n' +
        '*/\n'
      )))
      .pipe(size({gzip: true, title: 'vendor.js'}))
      .pipe(gulp.dest(speck.assets.build.js));
  });
};

Add size loging for vendor.js file


global.simulatency = {}

global.simulatency.timeout = 0

inject(Meteor.server.stream_server.server._events, 'connection', function (args, done) {
  inject(args[0], 'write', function (args, done) {
    setTimeout(done, global.simulatency.timeout)
  })
  done()
})

function httpDelay (args, done) {
  global.simulatency.timeout =
    getCookie(args[0].headers.cookie, 'lag')[1]
    || global.simulatency.timeout
  setTimeout(done, global.simulatency.timeout)
}

inject(WebApp.httpServer._events, 'upgrade', httpDelay)
inject(WebApp.httpServer._events, 'request', httpDelay)

global.simulatency = {}

global.simulatency.timeout = 0

inject(Meteor.server.stream_server.server._events, 'connection', function (args, done) {
  inject(args[0], 'write', function (args, done) {
    setTimeout(done, global.simulatency.timeout)
  })
  inject(args[0], 'data', function (args, done) {
    setTimeout(done, global.simulatency.timeout)
  })
  done()
})

function httpDelay (args, done) {
  global.simulatency.timeout =
    getCookie(args[0].headers.cookie, 'lag')[1]
    || global.simulatency.timeout
  setTimeout(done, global.simulatency.timeout)
}

inject(WebApp.httpServer._events, 'upgrade', httpDelay)
inject(WebApp.httpServer._events, 'request', httpDelay)

Add a lag to incomming messages


/* Set up the environment before loading the rest of the files into Zotero */
var $rdf = {
	Util: {
		ArrayIndexOf: function (arr, item, i) {
			//supported in all browsers except IE<9
			return arr.indexOf(item, i);
		},
		RDFArrayRemove: function (a, x) { //removes all statements equal to x from a
			for (var i = 0; i < a.length; i++) {
				//TODO: This used to be the following, which didnt always work..why
				//if(a[i] == x)
				if (a[i].subject.sameTerm(x.subject) && a[i].predicate.sameTerm(x.predicate) && a[i].object.sameTerm(x.object) && a[i].why.sameTerm(x.why)) {
					a.splice(i, 1);
					return;
				}
			}
			throw "RDFArrayRemove: Array did not contain " + x;
		}
	},
	log: {
		debug: Zotero.debug,
		warn: Zotero.debug
	}
};

if(Zotero.RDF) {
	Zotero.RDF.AJAW = $rdf;
} else {
	Zotero.RDF = {AJAW:$rdf};
}

var tabulator = {log: $rdf.log};
var alert = $rdf.log.warn;

Fix indentation and parse error


/*doc
 ---
 title: Copy To Clipboard
 name: copy_to_clipboard_react
 categories:
 - react_components_copy-to-clipboard
 - react_all
 ---

 npm install pui-react-copy-to-clipboard --save

 Require the subcomponents:

 ```
var CopyToClipboard = require('pui-react-copy-to-clipboard').CopyToClipboard;
var CopyToClipboardButton = require('pui-react-copy-to-clipboard').CopyToClipboardButton;
 ```

 ```react_example_table
 <CopyToClipboard text="I got copied by a button"><button>Click Me To Copy</button></CopyToClipboard>

 <CopyToClipboardButton text="I got copied by a good looking button"/>
 ```

 The CopyToClipboard Components require the following property:

 Property           |  Type         | Description
 -------------      | --------------| --------------------------------------------------------------------------
 `text`       | String        | Text that is copied when the user clicks

Below is a common example combining a readonly input and a copy button:

```
var Input = require('pui-react-inputs').Input
```

 ```react_example_table
<div className="copy-input">
   <Input label="shareable link" value="bar.com/1234.jpg" readOnly />
   <CopyToClipboardButton text="bar.com/1234.jpg"/>
</div>

 ```

 */

/*doc
 ---
 title: Copy To Clipboard
 name: copy_to_clipboard_react
 categories:
 - react_components_copy-to-clipboard
 - react_all
 ---

 npm install pui-react-copy-to-clipboard --save

 Require the subcomponents:

 ```
var CopyToClipboard = require('pui-react-copy-to-clipboard').CopyToClipboard;
var CopyToClipboardButton = require('pui-react-copy-to-clipboard').CopyToClipboardButton;
 ```

 ```react_example_table
 <CopyToClipboard text="I got copied by a button"><button>Click Me To Copy</button></CopyToClipboard>

 <CopyToClipboardButton text="I got copied by a good looking button"/>
 ```

 The CopyToClipboard Components require the following property:

 Property           |  Type         | Description
 -------------      | --------------| --------------------------------------------------------------------------
 `text`       | String        | Text that is copied when the user clicks

Below is a common example combining a readonly input and a copy button.
Note that there is custom css on the styleguide to get the positioning right.

```
var Input = require('pui-react-inputs').Input
```

 ```react_example_table
<div className="copy-input">
   <Input label="shareable link" value="bar.com/1234.jpg" readOnly />
   <CopyToClipboardButton text="bar.com/1234.jpg"/>
</div>

 ```

 */

Add a note to the copy to clipboard example on styleguide


test('script_proxy_dynamic.php', function (assert, document) {
    var h1s = document.getElementsByTagName('h1');
    assert.equal(h1s.length, 1, "There should be one <H1> element on the page");
    assert.equal(h1s[0].textContent, 'Hello, World!', "The <H1> should say 'Hello, World!'");

    var scripts = document.getElementsByTagName('script');
    assert.ok(scripts.length >= 1, "There should be one or more <SCRIPT> elements on the page");
    assert.ok(/script_proxy\.js/.test(scripts[0].src), "The first <SCRIPT> src should contain the name of the script");
    assert.ok(!/phast\.php/.test(scripts[0].src), "The first <SCRIPT> src should not contain phast.php");
});

test('script_proxy_dynamic.php', function (assert, document) {
    wait(assert, function () {
        return document.getElementsByTagName('h1').length > 0;
    }, function () {
        var h1s = document.getElementsByTagName('h1');
        assert.equal(h1s.length, 1, "There should be one <H1> element on the page");
        assert.equal(h1s[0].textContent, 'Hello, World!', "The <H1> should say 'Hello, World!'");

        var scripts = document.getElementsByTagName('script');
        assert.ok(scripts.length >= 1, "There should be one or more <SCRIPT> elements on the page");
        assert.ok(/script_proxy\.js/.test(scripts[0].src), "The first <SCRIPT> src should contain the name of the script");
        assert.ok(!/phast\.php/.test(scripts[0].src), "The first <SCRIPT> src should not contain phast.php");
    });
});

Fix timing issue with browser test


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

import Jed from "jed";
import locales from "./locales";

export function init(lang) {
    if (!lang) {
        lang = window.navigator.languages && window.navigator.languages.length ?
            window.navigator.languages[0] :
            (window.navigator.language || window.navigator.userLanguage || "en");
    }

    var langShort = lang.split(/[-_]/)[0];

    var localeData = locales[lang] || locales[langShort] || {};

    console.log(`User languages: ${lang}, ${langShort}. Using ${locales[lang] ? lang : locales[langShort] ? langShort : "en (default)"}`);

    return new Jed(localeData);
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

import Jed from "jed";
import locales from "./locales";

// Convert a language tag to a dash-separated lowercase string
function normalize(tag) {
    return tag.replace(/_/g, "-").toLowerCase();
}

export function init(lang) {
    if (!lang) {
        lang = window.navigator.languages && window.navigator.languages.length ?
            window.navigator.languages[0] :
            (window.navigator.language || window.navigator.userLanguage || "en");
    }

    // Normalize the given language tag and extract the language code alone
    lang = normalize(lang);
    var langShort = lang.split("-")[0];

    // Find the user language in the available locales
    var allLanguages = Object.keys(locales).map(normalize);
    var langIndex = allLanguages.indexOf(lang);
    if (langIndex < 0) {
        langIndex = allLanguages.indexOf(langShort);
    }

    var localeData = langIndex >= 0 ? locales[Object.keys(locales)[langIndex]] : {};

    return new Jed(localeData);
}

Fix for variations in language tags


import _ from 'lodash';

class ChainStart {
  constructor(name) {
    this.name = name;
    this.type = 'CHAIN_START';
  }
}

class Step {
  constructor(name) {
    this.name = name;
    this.type = 'STEP';
  }
}

class Arguments {
  constructor(params = []) {
    this.params = params;
    this.type = 'ARGUMENTS'
  }
}

export class Chain {
  constructor() {
    this.members = [];
  }

  startWith(identifierName) {
    this.members.push(new ChainStart(identifierName));
    return this;
  }

  addStep(name) {
    this.members.push(new Step(name));
    return this;
  }

  addArguments(...args) {
    this.members.push(new Arguments(args));
    return this;
  }

  __repr__(...args) {
    return this.members;
  }
}

export function createChain() {
  return new Chain();
}

export const STRATEGY = {
  init(name) {
    return createChain(name);
  },

  addStep(chain, name) {
    chain.addStep(name)
  },

  addArguments(chain, ...args) {
    chain.addArguments(...args)
  }
}

import _ from 'lodash';

class ChainStart {
  constructor(name) {
    this.name = name;
    this.type = 'CHAIN_START';
  }
}

class Step {
  constructor(name) {
    this.name = name;
    this.type = 'STEP';
  }
}

class Arguments {
  constructor(params = []) {
    this.params = params;
    this.type = 'ARGUMENTS'
  }
}

export class Chain {
  constructor() {
    this.members = [];
  }

  startWith(identifierName) {
    this.members.push(new ChainStart(identifierName));
    return this;
  }

  addStep(name) {
    this.members.push(new Step(name));
    return this;
  }

  addArguments(...args) {
    this.members.push(new Arguments(args));
    return this;
  }

  __repr__(...args) {
    return this.members;
  }
}

// TODO: missing name argument here?
export function createChain() {
  return new Chain();
}

Clean up old STRATEGY object


import React from 'react'
import { render } from 'react-dom'
import './stylesheets/ui.scss'
import { SkiDayList } from './components/SkiDayList'

window.React = React

render(
	<SkiDayList days="plenty of days"/>,
	document.getElementById('react-container')
)

import React from 'react'
import { render } from 'react-dom'
import './stylesheets/ui.scss'
import { SkiDayList } from './components/SkiDayList'

window.React = React

render(
	<SkiDayList days={[]}/>,
	document.getElementById('react-container')
)

Check for error message when an empty array is passed.


import configureUrlQuery from './configureUrlQuery';

import * as Serialize from './serialize';
import {
  replaceInUrlQuery,
  replaceUrlQuery,
  pushInUrlQuery,
  pushUrlQuery,
} from './updateUrlQuery';
import UrlQueryParamTypes from './UrlQueryParamTypes';
import UrlUpdateTypes from './UrlUpdateTypes';

/** React */
import addUrlProps from './react/addUrlProps';
import RouterToUrlQuery from './react/RouterToUrlQuery';

/** Redux */
import {
  replaceInUrlQueryFromAction,
  replaceUrlQueryFromAction,
  pushInUrlQueryFromAction,
  pushUrlQueryFromAction,
} from './redux/updateUrlQueryFromAction';
import urlAction, {
  urlReplaceAction,
  urlPushAction,
  urlReplaceInAction,
  urlPushInAction,
} from './redux/urlAction';
import urlQueryMiddleware from './redux/urlQueryMiddleware';
import urlQueryReducer from './redux/urlQueryReducer';

/** Utils */
import subquery from './utils/subquery';
import subqueryOmit from './utils/subqueryOmit';

// for convenience export these two directly
const decode = Serialize.decode;
const encode = Serialize.encode;

export {
  configureUrlQuery,

  Serialize,
  decode,
  encode,
  pushInUrlQuery,
  pushUrlQuery,
  replaceInUrlQuery,
  replaceUrlQuery,
  UrlQueryParamTypes,
  UrlUpdateTypes,

  addUrlProps,
  RouterToUrlQuery,

  urlAction,
  urlReplaceInAction,
  urlReplaceAction,
  urlPushInAction,
  urlPushAction,
  pushInUrlQueryFromAction,
  pushUrlQueryFromAction,
  replaceInUrlQueryFromAction,
  replaceUrlQueryFromAction,
  urlQueryMiddleware,
  urlQueryReducer,

  subquery,
  subqueryOmit,
};

export configureUrlQuery from './configureUrlQuery';

export * as Serialize, { encode, decode } from './serialize';
export {
  replaceInUrlQuery,
  replaceUrlQuery,
  pushInUrlQuery,
  pushUrlQuery,
} from './updateUrlQuery';
export UrlQueryParamTypes from './UrlQueryParamTypes';
export UrlUpdateTypes from './UrlUpdateTypes';

/** React */
export addUrlProps from './react/addUrlProps';
export RouterToUrlQuery from './react/RouterToUrlQuery';

/** Redux */
export {
  replaceInUrlQueryFromAction,
  replaceUrlQueryFromAction,
  pushInUrlQueryFromAction,
  pushUrlQueryFromAction,
} from './redux/updateUrlQueryFromAction';
export urlAction, {
  urlReplaceAction,
  urlPushAction,
  urlReplaceInAction,
  urlPushInAction,
} from './redux/urlAction';
export urlQueryMiddleware from './redux/urlQueryMiddleware';
export urlQueryReducer from './redux/urlQueryReducer';

/** Utils */
export subquery from './utils/subquery';
export subqueryOmit from './utils/subqueryOmit';

Switch to using export statements


var scene = require('./Utils/Scene').create();
var camera = require('./Utils/Camera').create();
var renderer = require('./Utils/Renderer').create();

var terrain = require('./Terrain').create(scene, camera, renderer);
var sounds = require('./Sounds');
var controls = require('./Controls').create(camera);
var UI = require('./UI');

var render = function() {
    requestAnimationFrame(render);

    renderer.render(scene, camera);
};

render();

var scene = require('./Utils/Scene').create();
var camera = require('./Utils/Camera').create();
var renderer = require('./Utils/Renderer').create();

var terrain = require('./Terrain').create(scene, camera, renderer);
var sounds = require('./Sounds');
var controls = require('./Controls').create(camera);
var UI = require('./UI');

var render = function() {
    requestAnimationFrame(render);

    controls.update();
    renderer.render(scene, camera);
};

render();

Update controls behavior on update


module.exports = function (config) {
  'use strict'

  config.set({
    frameworks: ['jspm', 'mocha', 'chai', 'sinon'],

    jspm: {
      config: 'src/jspm.conf.js',
      loadFiles: [
        'node_modules/es6-shim/es6-shim.js',
        'test/**/*.spec.js'
      ],
      serveFiles: [
        'node_modules/es6-shim/es6-shim.js',
        'src/**/*.js',
        'test/stubs.js'
      ],
      meta: {
        'src/*': { format: 'register' }
      }
    },
    proxies: {
      '/node_modules/': '/base/node_modules/',
      '/jspm_packages/': '/base/src/jspm_packages/',
      '/src/': '/base/src/',
      '/test/': '/base/test/'
    },

    autoWatchBatchDelay: 10000,
    singleRun: true,
    browsers: ['PhantomJS'],

    preprocessors: {
      'src/!(jspm.conf).js': ['coverage', 'sourcemap'],
      'src/app/**/*.js': ['coverage', 'sourcemap']
    },
    reporters: ['progress', 'coverage', 'appveyor'],
    coverageReporter: {
      dir: '../reports',
      includeAllSources: true,
      reporters: [
        {
          type: 'json',
          subdir: '.',
          file: 'coverage-js.json'
        }
      ]
    }
  })
}

module.exports = function (config) {
  'use strict'

  config.set({
    frameworks: ['jspm', 'mocha', 'chai', 'sinon'],

    jspm: {
      config: 'src/jspm.conf.js',
      loadFiles: [
        'node_modules/es6-shim/es6-shim.js',
        'test/**/*.spec.js'
      ],
      serveFiles: [
        'node_modules/es6-shim/es6-shim.js',
        'src/**/*.js',
        'test/stubs.js'
      ],
      meta: {
        'src/*': { format: 'register' }
      }
    },
    proxies: {
      '/node_modules/': '/base/node_modules/',
      '/jspm_packages/': '/base/src/jspm_packages/',
      '/src/': '/base/src/',
      '/test/': '/base/test/'
    },

    autoWatchBatchDelay: 10000,
    singleRun: true,
    browsers: ['PhantomJS'],
    captureTimeout: 2000,
    browserDisconnectTimeout: 2000,
    browserDisconnectTolerance: 3,
    browserNoActivityTimeout: 10000,

    preprocessors: {
      'src/!(jspm.conf).js': ['coverage', 'sourcemap'],
      'src/app/**/*.js': ['coverage', 'sourcemap']
    },
    reporters: ['progress', 'coverage', 'appveyor'],
    coverageReporter: {
      dir: '../reports',
      includeAllSources: true,
      reporters: [
        {
          type: 'json',
          subdir: '.',
          file: 'coverage-js.json'
        }
      ]
    }
  })
}

Increase some timeouts. Hopefully that resolves some "Disconnected" issues on AppVeyor.


var Clairvoyance = require('./clairvoyance');

module.exports = new CLI();

function CLI() {}

CLI.prototype.start = function(options) {
  this.parser = new Clairvoyance(options);
  this.parser.run(function(result) {
    console.log(result);
  });
}

var Clairvoyance = require('./clairvoyance'),
    fs = require('fs');

module.exports = new CLI();

function CLI() {}

CLI.prototype.start = function(options) {
  this.parser = new Clairvoyance(options);
  this.parser.run(function(result) {
    fs.mkdir('coverage', function(err) {
      var data = JSON.stringify(result);
      fs.writeFile('coverage/css-coverage.json', data);
    });
  });
}

Use a json file instead of console


'use strict';

var hasArgument = require('./hasArgument');
var isCommand = require('./isCommand');
var replaceArgument = require('./replaceArgument');
var replaceCommand = require('./replaceCommand');
var spawnMeteor = require('./spawnMeteor');
var spawnTestPackagesMeteor = require('./spawnTestPackagesMeteor');

module.exports = function run(options) {
  var meteorArguments = options.args;
  var meteorEnvironment = options.env;

  meteorArguments = replaceCommand(meteorArguments, 'test-package', 'test-packages');

  if (isCommand(meteorArguments, 'test-app')) {
    meteorArguments = replaceCommand(meteorArguments, 'test-app', 'run');
    meteorArguments = replaceArgument(meteorArguments, '--ci', '--test');

    spawnMeteor({
      args: meteorArguments,
      env: meteorEnvironment
    });
  } else if (isCommand(meteorArguments, 'test-packages')) {
    if (!hasArgument(meteorArguments, '--release')) {
      meteorArguments.push('--release', 'velocity:METEOR@1.1.0.2_3');
    }

    if (!hasArgument(meteorArguments, '--driver-package')) {
      meteorArguments.push('--driver-package', 'velocity:html-reporter');
    }

    meteorArguments = replaceArgument(meteorArguments, '--ci', '--velocity');

    spawnTestPackagesMeteor({
      args: meteorArguments,
      env: meteorEnvironment
    });
  } else {
    console.error('Velocity does not support this command.');
    console.log('Supported commands:');
    console.log('  * velocity test-package my-package');
  }
}

'use strict';

var hasArgument = require('./hasArgument');
var isCommand = require('./isCommand');
var replaceArgument = require('./replaceArgument');
var replaceCommand = require('./replaceCommand');
var spawnMeteor = require('./spawnMeteor');
var spawnTestPackagesMeteor = require('./spawnTestPackagesMeteor');

module.exports = function run(options) {
  var meteorArguments = options.args;
  var meteorEnvironment = options.env;

  meteorArguments = replaceCommand(meteorArguments, 'test-package', 'test-packages');

  if (isCommand(meteorArguments, 'test-app')) {
    meteorArguments = replaceCommand(meteorArguments, 'test-app', 'run');
    meteorArguments = replaceArgument(meteorArguments, '--ci', '--test');

    spawnMeteor({
      args: meteorArguments,
      env: meteorEnvironment
    });
  } else if (isCommand(meteorArguments, 'test-packages')) {
    if (!hasArgument(meteorArguments, '--release')) {
      meteorArguments.push('--release', 'velocity:METEOR@1.1.0.2_3');
    }

    if (!hasArgument(meteorArguments, '--driver-package')) {
      meteorArguments.push('--driver-package', 'velocity:html-reporter');
    }

    meteorArguments = replaceArgument(meteorArguments, '--ci', '--velocity');

    spawnTestPackagesMeteor({
      args: meteorArguments,
      env: meteorEnvironment
    });
  } else {
    console.error('Velocity does not support this command.');
    console.log('Supported commands:');
    console.log('  * velocity test-app');
    console.log('  * velocity test-package my-package');
  }
}

Add test-app command to help output


'use strict';

var React = require('react-native');
var Constants = require('./constants');

var { AsyncStorage } = React;

var SessionManager = {
  login: function(user) {
    return new Promise(function(resolve, reject) {
      var url = Constants.APP_SERVER_HOST + '/users/register';
      fetch(url, {
        method: 'post',
        body: JSON.stringify({user: user}),
        headers: new Headers({
          'Content-Type': 'application/json'
        })
      })
      .then(response => {
        if(response.ok) {
          return response.json();
        } else {
          reject(new Error(JSON.parse(response._bodyText).error)); //FixIt - Shoudn't be using the quasi private method
        }
      })
      .then(response => {
        var user = {
          name: response.user.name,
          email: response.user.email,
          avatar: response.user.gravatar_url
        }
        console.log(user);
        AsyncStorage
        .setItem(Constants.CURRENT_USER_STORAGE_KEY, JSON.stringify(user))
        .then((value) => resolve(value));
      })
      .catch(error => reject(error));
    });
  },

  getCurrentUser: function() {
    return new Promise(function(resolve, reject) {
      AsyncStorage.getItem(Constants.CURRENT_USER_STORAGE_KEY)
      .then((value) => {
        if(value === null) reject(Error('No user logged in!'));
        resolve(JSON.parse(value));
      });
    });
  }
};

module.exports = SessionManager;

'use strict';

var React = require('react-native');
var Constants = require('./constants');

var { AsyncStorage } = React;

const CURRENT_USER_STORAGE_KEY = 'currentUser';

var SessionManager = {
  login: function(user) {
    return new Promise(function(resolve, reject) {
      var url = Constants.APP_SERVER_HOST + '/users/register';
      fetch(url, {
        method: 'post',
        body: JSON.stringify({user: user}),
        headers: new Headers({
          'Content-Type': 'application/json'
        })
      })
      .then(response => {
        if(response.ok) {
          return response.json();
        } else {
          reject(new Error(JSON.parse(response._bodyText).error)); //FixIt - Shoudn't be using the quasi private method
        }
      })
      .then(response => {
        var user = {
          name: response.user.name,
          email: response.user.email,
          avatar: response.user.gravatar_url
        }
        console.log(user);
        AsyncStorage
        .setItem(CURRENT_USER_STORAGE_KEY, JSON.stringify(user))
        .then((value) => resolve(value));
      })
      .catch(error => reject(error));
    });
  },

  getCurrentUser: function() {
    return new Promise(function(resolve, reject) {
      AsyncStorage.getItem(CURRENT_USER_STORAGE_KEY)
      .then((value) => {
        if(value === null) reject(Error('No user logged in!'));
        resolve(JSON.parse(value));
      });
    });
  }
};

module.exports = SessionManager;

Move the CURRENT_USER_STORAGE_KEY constant to SessionManager


/**
 * Created by Alvaro on 18/03/2017.
 */

// use a cacheName for cache versioning
var CACHE_NAME = 'static-v1';
var urlsToCache = [
  './',
  './js/main.js'
];

// during the install phase you usually want to cache static assets
self.addEventListener('install', function(event) {
  // once the SW is installed, go ahead and fetch the resources to make this work offline
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      console.log('Opening cache for ' + CACHE_NAME);
      return cache.addAll(urlsToCache).then(function() {
        self.skipWaiting();
      });
    })
  );
});

// when the browser fetches a url
self.addEventListener('fetch', function(event) {
  // either respond with the cached object or go ahead and fetch the actual url
  event.respondWith(
    caches.match(event.request).then(function(response) {
      if (response) {
        // retrieve from cache
        return response;
      }
      // fetch as normal
      return fetch(event.request);
    })
  );
});

/**
 * Created by Alvaro on 18/03/2017.
 */

// use a cacheName for cache versioning
var CACHE_NAME = 'static-v0.0.1';
var urlsToCache = [
  './',
  './js/main.js',
  './js/build/ObjectivesList.js',
  './js/vendor/react.min.js',
  './js/vendor/react-com.min.js'
];

// during the install phase you usually want to cache static assets
self.addEventListener('install', function(event) {
  // once the SW is installed, go ahead and fetch the resources to make this work offline
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      console.log('Opening cache for ' + CACHE_NAME);
      return cache.addAll(urlsToCache).then(function() {
        self.skipWaiting();
      });
    })
  );
});

// when the browser fetches a url
self.addEventListener('fetch', function(event) {
  // either respond with the cached object or go ahead and fetch the actual url
  event.respondWith(
    caches.match(event.request).then(function(response) {
      if (response) {
        // retrieve from cache
        return response;
      }
      // fetch as normal
      return fetch(event.request);
    })
  );
});

Add more objects to cache in the service worker.


include('helma/webapp/response');
include('helma/jsdoc');
require('core/array');

var log = require('helma/logging').getLogger(module.id);

exports.index = function index(req, module) {
    var repo = new ScriptRepository(require.paths.peek());
    if (module && module != "/") {
        var res = repo.getScriptResource(module);
        var jsdoc = parseResource(res);
        // log.info("export()ed symbols: " + exported);
        return new SkinnedResponse(getResource('./skins/module.html'), {
            title: "Module " + res.moduleName,
            jsdoc: jsdoc
        });
    } else {
        var modules = repo.getScriptResources(true).sort(function(a, b) {
            return a.relativePath > b.relativePath ? 1 : -1;
        });
        return new SkinnedResponse(getResource('./skins/index.html'), {
            title: "API Documentation",
            modules: modules
        });
    }
}

include('helma/webapp/response');
include('helma/jsdoc');
require('core/array');

var log = require('helma/logging').getLogger(module.id);

exports.index = function index(req, module) {
    var repo = new ScriptRepository(require.paths.peek());
    if (module && module != "/") {
        var res = repo.getScriptResource(module);
        var jsdoc = parseResource(res);
        // log.info("export()ed symbols: " + exported);
        return new SkinnedResponse(getResource('./skins/module.html'), {
            title: "Module " + res.moduleName,
            jsdoc: jsdoc
        });
    } else {
        var modules = repo.getScriptResources(true).filter(function(r) {
            return r.relativePath.indexOf('test') != 0;
        }).sort(function(a, b) {
            return a.relativePath > b.relativePath ? 1 : -1;
        });
        return new SkinnedResponse(getResource('./skins/index.html'), {
            title: "API Documentation",
            modules: modules
        });
    }
}

Exclude test suite from jsdoc


var express = require('express');

var routes  = require('./routes');
var http    = require('http');
var path    = require('path');

var app     = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hjs');

app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/rt', routes.index);
app.get('/rt/:stopid', routes.rt);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

var express = require('express');

var routes  = require('./routes');
var http    = require('http');
var path    = require('path');

var app     = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hjs');

// app.use(express.favicon('public/images/fav.ico'));
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/rt', routes.index);
app.get('/rt/:stopid', routes.rt);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Comment out favicon. No icon added to project


#!/usr/bin/env node
var replace = require('stream-replace')
var path = require('path')

var name = require(path.join(process.cwd(), 'package.json')).name

process.stdin
  .pipe(
    replace(
      new RegExp('require\\(\s*[\'"]' + name + '[\'"]\s*\\)', 'g'),
      'require(\'./\')'))
  .pipe(process.stdout)

#!/usr/bin/env node
var replace = require('stream-replace')
var path = require('path')

var name = require(path.join(process.cwd(), 'package.json')).name

process.stdin
  .pipe(
    replace(
      new RegExp('require\\(\s*[\'"]' + name, 'g'),
      'require(\'./'))
  .pipe(process.stdout)

Replace only the first part of the require call


let base = require('./ts')

module.exports = {
  ...base,
  plugins: [...base.plugins],
  rules: {
    ...base.rules,
    'unicorn/prefer-module': 'error',
    'import/extensions': [
      'error',
      'always',
      {
        ignorePackages: true
      }
    ]
  },
  overrides: [
    ...base.overrides,
    {
      files: '*.cjs',
      rules: {
        'unicorn/prefer-module': 'off'
      }
    }
  ]
}

let base = require('./ts')

module.exports = {
  ...base,
  plugins: [...base.plugins],
  rules: {
    ...base.rules,
    // 'unicorn/prefer-node-protocol': 'error',
    'unicorn/prefer-module': 'error',
    'import/extensions': [
      'error',
      'always',
      {
        ignorePackages: true
      }
    ]
  },
  overrides: [
    ...base.overrides,
    {
      files: '*.cjs',
      rules: {
        'unicorn/prefer-module': 'off'
      }
    }
  ]
}

Add note to enable unicorn/prefer-node-protocol on ESLint 8 release


import React from 'react';
import { addStoryInGroup, LOW_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { Store, State } from '@sambego/storybook-state';
import { RadioGroup, RadioButton } from '../../index';

const values = ['Option one', 'Option two', 'Option three'];

const store = new Store({
  value: 'Option one',
});
const updateState = (value) => {
  store.set({ value });
};

export default {
  component: RadioButton,
  title: addStoryInGroup(LOW_LEVEL_BLOCKS, 'Form elements/Radio'),

  parameters: {
    design: {
      type: 'figma',
      url: 'https://www.figma.com/file/LHH25GN90ljQaBEUNMsdJn/Desktop-components?node-id=6454%3A22776',
    },
  },
};

export const DefaultStory = (args) => <RadioButton {...args} />;

DefaultStory.args = {
  checked: true,
  label: 'I am the label',
  value: 'the_value',
};

export const Group = () => (
  <State store={store}>
    <RadioGroup name="stringValue" onChange={updateState}>
      {values.map((value, key) => (
        <RadioButton key={key} marginVertical={3} label={value} value={value} />
      ))}
    </RadioGroup>
  </State>
);

import React, { useState } from 'react';
import { addStoryInGroup, LOW_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { RadioGroup, RadioButton } from '../../index';

const values = ['Option one', 'Option two', 'Option three'];

export default {
  component: RadioButton,
  title: addStoryInGroup(LOW_LEVEL_BLOCKS, 'Form elements/Radio'),

  parameters: {
    design: {
      type: 'figma',
      url: 'https://www.figma.com/file/LHH25GN90ljQaBEUNMsdJn/Desktop-components?node-id=6454%3A22776',
    },
  },
};

export const DefaultStory = (args) => <RadioButton {...args} />;

DefaultStory.args = {
  checked: true,
  label: 'I am the label',
  value: 'the_value',
};

export const Group = () => {
  const [value, setValue] = useState('Option one');

  const handleChange = (value) => {
    setValue(value);
  };

  return (
    <RadioGroup name="stringValue" onChange={handleChange} value={value}>
      {values.map((value, key) => (
        <RadioButton key={key} marginVertical={3} label={value} value={value} />
      ))}
    </RadioGroup>
  );
};

Replace storybook-state usage from radio story with useState


/*global exports */

(function () {
    "use strict";

    var popularNamesPerState = function(req, res) {
        var body = JSON.stringify([
            {name: 'Smith', geo: {lat: -37.716, lon: 144.891}},
            {name: 'Jones', geo: {lat: -41.218, lon: 1446.188}},
            {name: 'Nyuan', geo: {lat: -25.173, lon: 144.891}},
            {name: 'Potter', geo: {lat: -33.390, lon: 149.547}},
            {name: 'Brown', geo: {lat: -28.101, lon: 115.192}}
        ]);
        res.setHeader('Content-Type', 'application/json');
        res.setHeader('Content-Length', body.length);
        res.end(body);
    };

    exports.list = function(req, res) {
        popularNamesPerState(req, res);
    };
})();

/*global exports */

(function () {
    "use strict";

    var popularNamesPerState = function(req, res) {
        var body = JSON.stringify([
            {name: 'Smith', geo: {lat: -36.64, lon: 144.11}},
            {name: 'Smith', geo: {lat: -31.0, lon: 146.4}},
            {name: 'Smith', geo: {lat: -24.9, lon: 145.0}},
            {name: 'Smith', geo: {lat: -14.5, lon: 131.7}},
            {name: 'Smith', geo: {lat: -27.9, lon: 134.3}},
            {name: 'Smith', geo: {lat: -42.39, lon: 146.73}},
            {name: 'Smith', geo: {lat: -28.1, lon: 119.3}}
        ]);
        res.setHeader('Content-Type', 'application/json');
        res.setHeader('Content-Length', body.length);
        res.end(body);
    };

    exports.list = function(req, res) {
        popularNamesPerState(req, res);
    };
})();

Add all of the states


'use strict'

import { StyleSheet } from 'react-native'
import { Fonts, Colors, Metrics } from '../../Themes/'

export default StyleSheet.create({
  button: {
    height: 45,
    borderRadius: 5,
    marginHorizontal: Metrics.section,
    marginVertical: Metrics.baseMargin,
    backgroundColor: Colors.fire,
    justifyContent: 'center'
  },
  buttonText: {
    color: Colors.snow,
    textAlign: 'center',
    fontWeight: 'bold',
    fontSize: Fonts.size.medium,
    paddingVertical: Metrics.baseMargin
  }
})

'use strict'

import { StyleSheet } from 'react-native'
import { Fonts, Colors, Metrics } from '../../Themes/'

export default StyleSheet.create({
  button: {
    height: 45,
    borderRadius: 5,
    marginHorizontal: Metrics.section,
    marginVertical: Metrics.baseMargin,
    backgroundColor: Colors.fire,
    justifyContent: 'center'
  },
  buttonText: {
    color: Colors.snow,
    textAlign: 'center',
    fontWeight: 'bold',
    fontSize: Fonts.size.medium,
    marginVertical: Metrics.baseMargin
  }
})

Fix text vert margin on RoundedButton


const child = require('child_process');

const forkCount =  parseInt(process.argv[2]);
const forkCalls =  parseInt(process.argv[3])
const intervalTime = parseInt(process.argv[4]);
const targetURI = 'www.google.com';

for (var i = 0 ; i < forkCount; i++){
	process.stdout.write(`Process: ${i} at work`);
	let childProcess = child.fork('get-request.js',[targetURI,forkCalls ,intervalTime]);

	childProcess.on('close', (code) => {`child process exited with code ${code}`})
}

const child = require('child_process');

const forkCount =  parseInt(process.argv[2]);
const forkCalls =  parseInt(process.argv[3])
const intervalTime = parseInt(process.argv[4]);
const targetURI = 'tr.fuelx.com';

for (var i = 0 ; i < forkCount; i++){
	process.stdout.write(`Process: ${i} at work`);
	let childProcess = child.fork('get-request.js',[targetURI,forkCalls ,intervalTime]);

	childProcess.on('close', (code) => {`child process exited with code ${code}`})
}

Add tr.fuelx.com as the request uri


'use strict';

/**
 *  client
 **/

/**
 *  client.common
 **/

/*global $, _, nodeca, window, document*/

/**
 *  client.common.init()
 *
 *  Assigns all necessary event listeners and handlers.
 *
 *
 *  ##### Example
 *
 *      nodeca.client.common.init();
 **/
module.exports = function () {
  nodeca.io.init();

  $(function () {
    // Bootstrap.Collapse calls e.preventDefault() only when there's no
    // data-target attribute. We don't want URL to be changed, so we are
    // forcing prevention of default behavior.
    $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) {
      e.preventDefault();
    });

    // Bootstrap.Dropdown don't calls e.preventDefault() by default
    $('body').on('click.dropdown.data-api', '[data-toggle=dropdown]', function ( e ) {
      e.preventDefault();
    });

    //
    // Observe quicksearch focus to tweak icon style
    //

    $('.navbar-search .search-query')
      .focus(function (){ $(this).next('div').addClass('focused'); })
      .blur(function (){ $(this).next('div').removeClass('focused'); });
  });

  // history intercepts clicks on all `a` elements,
  // so we initialize it as later as possible to have
  // "lowest" priority of handlers
  nodeca.client.common.init.history();
};

'use strict';

/**
 *  client
 **/

/**
 *  client.common
 **/

/*global $, _, nodeca, window, document*/

/**
 *  client.common.init()
 *
 *  Assigns all necessary event listeners and handlers.
 *
 *
 *  ##### Example
 *
 *      nodeca.client.common.init();
 **/
module.exports = function () {
  nodeca.io.init();

  $(function () {
    // Bootstrap.Collapse calls e.preventDefault() only when there's no
    // data-target attribute. We don't want URL to be changed, so we are
    // forcing prevention of default behavior.
    $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) {
      e.preventDefault();
    });

    //
    // Observe quicksearch focus to tweak icon style
    //

    $('.navbar-search .search-query')
      .focus(function (){ $(this).next('div').addClass('focused'); })
      .blur(function (){ $(this).next('div').removeClass('focused'); });
  });

  // history intercepts clicks on all `a` elements,
  // so we initialize it as later as possible to have
  // "lowest" priority of handlers
  nodeca.client.common.init.history();
};

Revert "Prevent link processing for dropdowns"


hqDefine("reports/js/base", function() {
    $(function() {
        hqImport("reports/js/filters/main").init();

        var initial_page_data = hqImport("hqwebapp/js/initial_page_data").get;
        var defaultConfig = initial_page_data('default_config') || {};
        if (initial_page_data('has_datespan')) {
            defaultConfig.date_range = 'last7';
        } else {
            defaultConfig.date_range = null;
        }
        defaultConfig.has_ucr_datespan = false;
        defaultConfig.datespan_filters = [];
        defaultConfig.datespan_slug = null;

        var $savedReports = $("#savedReports");
        if ($savedReports.length) {
            $savedReports.reportConfigEditor({
                filterForm: $("#reportFilters"),
                items: initial_page_data('report_configs'),
                defaultItem: defaultConfig,
                saveUrl: hqImport("hqwebapp/js/initial_page_data").reverse("add_report_config"),
            });
        }

        $('#email-enabled').tooltip({
            placement: 'right',
            html: true,
            title: gettext("You can email a saved version<br />of this report."),
        });

        $(document).on('click', '.export-action-download', function() {
            var $modalBody = $("#export-download-status .modal-body");
            $modalBody.text("Fetching...");
            $("#export-download-status .modal-header h3 span").text($(this).data("formname"));
            $.getJSON($(this).data('dlocation'), function(d) {
                $modalBody.empty().load(d.download_url);
            });
        });
    });
});

hqDefine("reports/js/base", function() {
    $(function() {
        hqImport("reports/js/filters/main").init();

        var initial_page_data = hqImport("hqwebapp/js/initial_page_data").get;
        var defaultConfig = initial_page_data('default_config') || {};
        if (initial_page_data('has_datespan')) {
            defaultConfig.date_range = 'last7';
        } else {
            defaultConfig.date_range = null;
        }
        defaultConfig.has_ucr_datespan = false;
        defaultConfig.datespan_filters = [];
        defaultConfig.datespan_slug = null;

        var $savedReports = $("#savedReports");
        if ($savedReports.length) {
            $savedReports.reportConfigEditor({
                filterForm: $("#reportFilters"),
                items: initial_page_data('report_configs'),
                defaultItem: defaultConfig,
                saveUrl: hqImport("hqwebapp/js/initial_page_data").reverse("add_report_config"),
            });
        }

        $('#email-enabled').tooltip({
            placement: 'right',
            html: true,
            title: gettext("You can email a saved version<br />of this report."),
        });
    });
});

Remove .export-action-download now that reports.download_export.js is gone