var validateEditForm = function (formObject) {
	var error = false;

	// check name
	if (formObject.name.length < 1) {
		toastr.error("You did not enter a valid name");
		error = true;
	}

	// check username
	if (formObject.username.length < 1) {
		toastr.error("You did not enter a valid username");
		error = true;
	}

	return !error;
};

var validateCreateForm = function (formObject) {
	var error = false;

	// check name
	if (formObject.name.length < 1) {
		toastr.error("You did not enter a valid name");
		error = true;
	}

	// check username
	if (formObject.username.length < 1) {
		toastr.error("You did not enter a valid username");
		error = true;
	}

	// check password length
	if (formObject.password.length < 4) {
		toastr.error("Your password must be at least 4 characters long");
		error = true;
	}

	// check password match
	if (formObject.password != formObject.password_check) {
		toastr.error("Your passwords did not match");
		error = true;
	}

	return !error;
};

var validateEditForm = function (formObject) {
	var error = false;

	// check name
	if (formObject.name.trim().length < 1) {
		toastr.error("You did not enter a valid name");
		error = true;
	}

	// check username
	if (formObject.username.trim().length < 1) {
		toastr.error("You did not enter a valid username");
		error = true;
	}

	return !error;
};

var validateCreateForm = function (formObject) {
	var error = false;

	// check name
	if (formObject.name.trim().length < 1) {
		toastr.error("You did not enter a valid name");
		error = true;
	}

	// check username
	if (formObject.username.trim().length < 1) {
		toastr.error("You did not enter a valid username");
		error = true;
	}

	// check password length
	if (formObject.password.trim().length < 4) {
		toastr.error("Your password must be at least 4 characters long");
		error = true;
	}

	// check password match
	if (formObject.password != formObject.password_check) {
		toastr.error("Your passwords did not match");
		error = true;
	}

	return !error;
};

Trim added to Mark's Form Validations.


$(document).ready(function () {
    $('#add-another-email').click(function () {
        var emailList = $('#email-fields-list');
        var newWidget = emailList.attr('data-prototype');
        newWidget = newWidget.replace(/__name__/g, emailCount);
        emailCount++;
        var newDiv = $('

').html(newWidget);
        newDiv.appendTo($('#email-fields-list'));
        return false;
    });

    $(document).on('click', '.removeRow', function (event) {
        var name = $(this).attr('data-related');
        $('*[data-content="' + name + '"]').remove();
        return false;
    });
});

$(function () {
    var cList  = $('#email-fields-list'),
        cCount = cList.children().length;

    $('#add-another-email').on('click', function () {
        widget = cList.attr('data-prototype').replace(/__name__/g, cCount++);

        $('

').html(widget).appendTo(cList);

        return false;
    });

    $(document).on('click', '.removeRow', function (event) {
        name = $(this).attr('data-related');

        $('*[data-content="' + name + '"]').remove();

        return false;
    });
});

Apply layout to user edit account form. JS collection minor refactoring.


/*
 * debugger.io: An interactive web scripting sandbox
 *
 * observers.js: mutation observers
 */

define([
  'config', 'utils', 'jquery', 'underscore',
  'bus'
],
function(
  config, utils, $, _, bus
) {
  'use strict';

  var observers = utils.module('observers');

  var listeners = {};
  var last_lid = 0;

  var MutationObserver =
    window.MutationObserver || window.WebKitMutationObserver;

  bus.init(function(av) {
    utils.log('init observers module');

    var observer = new MutationObserver(function(mutations) {
      // pass each mutation
      _.each(mutations, function(mutation) {
        // to each listener function
        _.each(listeners, function(listener) {
          if (_.isFunction(listener)) { listener(mutation); }
        });
      });
    });

    observer.observe(document, { childList: true, subtree: true });
  });

  /**
   * Register a new observer listener function
   *
   * @param {Function} callback - listener function, passed MutationRecord
   * @return {Integer} unique listener id
   */
  observers.register_listener = function(callback) {
    var lid = last_lid++;

    utils.log('registering observer listener', lid);
    listeners[lid] = callback;

    return lid;
  };

  /**
   * Unregister an observer listener by id
   *
   * @param {Integer} lid - listener id
   */
  observers.unregister_listener = function(lid) {
    if (_.has(listeners, lid)) {
      utils.log('unregistering observer listener', lid);
      delete listeners[lid];
    }
  };

  return observers;
});

/*
 * debugger.io: An interactive web scripting sandbox
 *
 * observers.js: mutation observers
 */

define([
  'config', 'utils', 'jquery', 'underscore',
  'bus'
],
function(
  config, utils, $, _, bus
) {
  'use strict';

  var observers = utils.module('observers');

  var listeners = {};
  var last_lid = 0;

  var MutationObserver =
    window.MutationObserver || window.WebKitMutationObserver;

  bus.init(function(av) {
    utils.log('init observers module');

    var observer = new MutationObserver(function(mutations) {
      // pass each mutation
      _.each(mutations, function(mutation) {
        // to each listener function
        _.each(listeners, function(listener) { listener(mutation); });
      });
    });

    observer.observe(document, { childList: true, subtree: true });
  });

  /**
   * Register a new observer listener function
   *
   * @param {Function} callback - listener function, passed MutationRecord
   * @return {Integer} unique listener id
   */
  observers.register_listener = function(callback) {
    if (!_.isFunction(callback) { return; });

    var lid = last_lid++;

    utils.log('registering observer listener', lid);
    listeners[lid] = callback;

    return lid;
  };

  /**
   * Unregister an observer listener by id
   *
   * @param {Integer} lid - listener id
   */
  observers.unregister_listener = function(lid) {
    if (_.has(listeners, lid)) {
      utils.log('unregistering observer listener', lid);
      delete listeners[lid];
    }
  };

  return observers;
});

Check observer callback functionhood at registration


module.exports = grammar({
  name: 'ruby',

  extras: $ => [
    $.comment,
    $._line_break,
    /[ \t\r]/
  ],

  rules: {
    program: $ => repeat(seq($._statement, optional($._terminator))),

  	_statement: $ => choice($._expression),
  	_expression: $ => choice($._argument),

  	_argument: $ => choice($._primary),

  	_primary: $ => choice($._variable),

  	_variable: $ => choice($.identifier , 'nil', 'self'),

  	identifier: $ => seq(repeat(choice('@', '$')), /[a-zA-Z_][a-zA-Z0-9_]*/),

  	comment: $ => token(seq('#', /.*/)),

    _line_break: $ => '\n',
  	_terminator: $ => choice($._line_break, ';'),
  }
});

module.exports = grammar({
  name: 'ruby',

  extras: $ => [
    $.comment,
    $._line_break,
    /[ \t\r]/
  ],

  rules: {
    program: $ => $._compound_statement,

    _compound_statement: $ => repeat(seq($._statement, optional($._terminator))),

  	_statement: $ => choice($._expression),
  	_expression: $ => choice($._argument),

  	_argument: $ => choice($._primary),

  	_primary: $ => choice($._variable),

  	_variable: $ => choice($.identifier , 'nil', 'self'),

  	identifier: $ => seq(repeat(choice('@', '$')), /[a-zA-Z_][a-zA-Z0-9_]*/),

  	comment: $ => token(seq('#', /.*/)),

    _line_break: $ => '\n',
  	_terminator: $ => choice($._line_break, ';'),
  }
});

Add compound statements back in.


import * as config from './config'
import * as dict from '../get/dict'
import * as input from '../parse/interface/register'
import * as output from '../get/registrar'

const getConfig = config.get
const registers = {
  config,
  dict,
  input,
  output
}

const indices = {}

export const add = (ref, plugins) => {
  let mainIndex = indices[ref] = {}

  if ('config' in plugins) {
    registers.config.add(ref, plugins.config)
    delete plugins.config
  }

  for (let type in plugins) {
    let typeIndex = mainIndex[type] = {}
    let typePlugins = plugins[type]

    for (let name in typePlugins) {
      let typePlugin = typePlugins[name]

      typeIndex[name] = true
      registers[type].add(name, typePlugin)
    }
  }
}

export const remove = (ref) => {
  let mainIndex = indices[ref]

  for (let type in mainIndex) {
    let typeIndex = mainIndex[type]

    for (let name in typeIndex) {
      registers[type].remove(name)
    }
  }

  delete indices[ref]
}

export const has = (ref) => ref in indices
export const list = () => Object.keys(indices)

export {getConfig as config}
export {registers}

import * as config from './config'
import * as dict from '../get/dict'
import * as input from '../parse/interface/register'
import * as output from '../get/registrar'

const getConfig = config.get
const registers = {
  config,
  dict,
  input,
  output
}

const indices = {}

export const add = (ref, plugins = {}) => {
  let mainIndex = indices[ref] = {}

  if ('config' in plugins) {
    registers.config.add(ref, plugins.config)
    delete plugins.config
  }

  for (let type in plugins) {
    let typeIndex = mainIndex[type] = {}
    let typePlugins = plugins[type]

    for (let name in typePlugins) {
      let typePlugin = typePlugins[name]

      typeIndex[name] = true
      registers[type].add(name, typePlugin)
    }
  }
}

export const remove = (ref) => {
  let mainIndex = indices[ref]

  for (let type in mainIndex) {
    let typeIndex = mainIndex[type]

    for (let name in typeIndex) {
      registers[type].remove(name)
    }
  }

  delete indices[ref]
}

export const has = (ref) => ref in indices
export const list = () => Object.keys(indices)

export {getConfig as config, registers}

Allow empty plugin add method call


import uniqolor from "uniqolor";
import { v5 as uuidv5 } from "uuid";

/**
 * Create a unique color for any IRIDA thing (project, sample, etc.)
 * @param id identifier for the thing
 * @param label label for the thing
 * @returns {{background: *, text: *}}
 */
export function generateColourForItem({ id, label }) {
  const formatted = uuidv5(`${id}`, "1b671a64-40d5-491e-99b0-da01ff1f3341");
  const name = `${formatted}-${label}`;

  let background = uniqolor(name, {
    lightness: 95,
    saturation: 95,
  });

  let colour = uniqolor(name, {
    lightness: 40,
    saturation: 95,
  });
  return { background: background.color, text: colour.color };
}

import uniqolor from "uniqolor";
import { v5 as uuidv5 } from "uuid";

/**
 * Create a unique color for any IRIDA thing (project, sample, etc.)
 * @param id identifier for the thing
 * @param label label for the thing
 * @returns {{background: *, text: *}}
 */
export function generateColourForItem({ id, label }) {
  const formatted = uuidv5(`${id}`, "1b671a64-40d5-491e-99b0-da01ff1f3341");
  const name = `${formatted}-${label}`;

  let background = uniqolor(name, {
    lightness: 90,
    saturation: 80,
  });

  let colour = uniqolor(name, {
    lightness: 20,
  });
  return { background: background.color, text: colour.color };
}

Clean up unique project colours


export const loanProfileTypesMap = {
  FIXED: 'Fixed',
  ROLLING: 'Rolling',
  INDEFINITE: 'Indefinite',
};

export const intervalPeriodsMap = {
  MINUTE: 'minutes',
  HOUR: 'hours',
  DAY: 'days',
  WEEK: 'weeks',
  MONTH: 'months',
};

export const intervalIdsMap = {
  'Minutes': 'minutes',
  'Hours': 'hours',
  'Days': 'days',
  'Weeks': 'weeks',
  'Months': 'months',
};

export const renewFromMap = {
  CURRENT_DUE_DATE: '1',
  SYSTEM_DATE: '2',
};

export default {};

export const loanProfileTypesMap = {
  FIXED: 'Fixed',
  ROLLING: 'Rolling',
  INDEFINITE: 'Indefinite',
};

export const intervalPeriodsMap = {
  MINUTE: 'minutes',
  HOUR: 'hours',
  DAY: 'days',
  WEEK: 'weeks',
  MONTH: 'months',
};

export const intervalIdsMap = {
  'Minutes': 'minutes',
  'Hours': 'hours',
  'Days': 'days',
  'Weeks': 'weeks',
  'Months': 'months',
};

export const renewFromMap = {
  CURRENT_DUE_DATE: 'CURRENT_DUE_DATE',
  SYSTEM_DATE: 'SYSTEM_DATE',
};

export default {};

Replace int values with string representations for renewFromOptions


var fs = require('fs');
var path = require('path');
var Q = require('q');
var child_process = require('child_process');

// Returns a promise.
module.exports = exports = function runInChrome(platform) {
  chromeAppRunRoot = null;
  if (fs.existsSync('www/manifest.json')) {
    chromeAppRunRoot = 'www';
  } else if (fs.existsSync('manifest.json')) {
    chromeAppRunRoot = '.';
  } else {
    return Q.reject('Not a chrome app.');
  }
  return Q.fcall(function() {
    var chrome = 'Google Chrome' + (platform === 'canary' ? ' Canary' : '');
    var chromeArgs = ['--load-and-launch-app=' + path.resolve(chromeAppRunRoot)]; // '--disable-web-security'
    if (platform === 'canary') {
      chromeArgs.push('--user-data-dir=/tmp/cca_chrome_data_dir');
    }
    child_process.spawn('open', ['-n', '-a', chrome, '--args'].concat(chromeArgs));
    return;
  });
};

var fs = require('fs');
var path = require('path');
var Q = require('q');
var child_process = require('child_process');

// Returns a promise.
module.exports = exports = function runInChrome(platform) {
  chromeAppRunRoot = null;
  if (fs.existsSync('www/manifest.json')) {
    chromeAppRunRoot = 'www';
  } else if (fs.existsSync('manifest.json')) {
    chromeAppRunRoot = '.';
  } else {
    return Q.reject('Not a chrome app.');
  }

  var chromeArgs = ['--load-and-launch-app=' + path.resolve(chromeAppRunRoot)]; // '--disable-web-security'
  if (platform === 'canary') {
    chromeArgs.push('--user-data-dir=/tmp/cca_chrome_data_dir');
  }

  return Q.fcall(function() {
    if (process.platform === 'win32') {
      var chrome = 'Chrome' + (platform === 'canary' ? ' Canary' : '');
      child_process.spawn('cmd', ['/s', '/c', 'start', chrome].concat(chromeArgs));
    } else if (process.platform === 'darwin') {
      var chrome = 'Google Chrome' + (platform === 'canary' ? ' Canary' : '');
      child_process.spawn('open', ['-n', '-a', chrome, '--args'].concat(chromeArgs));
    }
  });
};

Add cca run chrome support for windows (maybe?)


/* -*- mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */

// http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#json
jQuery.microdata.json = function(selector, format) {
  function getObject($item) {
    var result = {};
    if ($item.itemType())
      result.type = $item.itemType();
    if ($item.itemId())
      result.id = $item.itemId();
    result.properties = {};
    $item.properties().each(function() {
      var $elem = jQuery(this);
      var value;
      if ($elem.itemScope())
        value = getObject($elem);
      else
        value = $elem.itemValue();
      $elem.itemProp().each(function() {
        if (!result.properties[this])
          result.properties[this] = [];
        result.properties[this].push(value);
      });
    });
    return result;
  }

  var result = {};
  result.items = [];
  var $items = selector ? jQuery(selector) : jQuery(document).items();
  $items.each(function(i, item) {
    var $item = jQuery(item);
    if ($item.itemScope())
      result.items.push(getObject($item));
  });
  return format ? format(result) : JSON.stringify(result, undefined, 2);
};

/* -*- mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */

// http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#json
jQuery.microdata.json = function(selector, format) {
  // Implemented as per spec, with the addition that a stack of
  // elements is maintained to break itemref loops. The branch is cut
  // just before the item would be included a second time.

  var $ = jQuery;

  function getObject(item, stack) {
    var $item = $(item);
    var result = {};
    if ($item.itemType())
      result.type = $item.itemType();
    if ($item.itemId())
      result.id = $item.itemId();
    result.properties = {};
    $item.properties().each(function(i, elem) {
      var $elem = $(elem);
      var value;
      if ($elem.itemScope()) {
        if (stack.indexOf(elem) != -1)
          return;
        stack.push(elem);
        value = getObject(elem, stack);
        stack.pop();
      } else {
        value = $elem.itemValue();
      }
      $elem.itemProp().each(function(i, prop) {
        if (!result.properties[prop])
          result.properties[prop] = [];
        result.properties[prop].push(value);
      });
    });
    return result;
  }

  var result = {};
  result.items = [];
  var $items = selector ? $(selector) : $(document).items();
  $items.each(function(i, item) {
    var $item = $(item);
    if ($item.itemScope())
      result.items.push(getObject(item, []));
  });
  return format ? format(result) : JSON.stringify(result, undefined, 2);
};

Handle itemref loops in JSON conversion


/* jshint node:true */
/* jshint -W034 */
/* jshint -W097 */

'use strict';

let exec = require('child_process').exec;
let path = require('path');

let cmdLine = path.join('.', 'node_modules', '.bin', 'webpack');
cmdLine += ' --progress --colors';

let env = !!process.argv[2] ? process.argv[2].toLowerCase() : 'production';

if (process.platform === 'win32') {
  cmdLine = 'set NODE_ENV=' + env + '&& ' + cmdLine;
} else {
  cmdLine = 'NODE_ENV=' + env + ' ' + cmdLine;
}

let command = exec(cmdLine, error => process.exit(error === null ? 0 : error.code));

command.stdout.on('data', data => process.stdout.write(data));
command.stderr.on('data', data => process.stderr.write(data));
command.on('error', err => process.stderr.write(err));

/* jshint node:true */
/* jshint -W034 */
/* jshint -W097 */

'use strict';

let exec = require('child_process').exec;
let path = require('path');

let cmdLine = path.join('.', 'node_modules', '.bin', 'webpack');
cmdLine += ' -p --bail --progress --colors';

let env = !!process.argv[2] ? process.argv[2].toLowerCase() : 'production';

if (process.platform === 'win32') {
  cmdLine = 'set NODE_ENV=' + env + '&& ' + cmdLine;
} else {
  cmdLine = 'NODE_ENV=' + env + ' ' + cmdLine;
}

let command = exec(cmdLine, error => process.exit(error === null ? 0 : error.code));

command.stdout.on('data', data => process.stdout.write(data));
command.stderr.on('data', data => process.stderr.write(data));
command.on('error', err => process.stderr.write(err));

Build for production with options -p and --bail


'use strict'

var page  = require('page');
var React = require('react');
var moment = require('moment');

var WeekView = React.createFactory(require('./views/week.jsx'));

page('/', function(ctx) {
	var year = moment().year();
	var week = moment().week();

	return page.show('/matches/' + year + '/' + week + '');
});

page('/matches/:year/:week', function(ctx) {
	var weekView = WeekView({
		'year': parseInt(ctx.params.year),
		'week': parseInt(ctx.params.week),
	});
	return React.render(weekView, document.getElementById('view'));
});

moment.locale('fi') && page.start();

'use strict'

var page   = require('page');
var React  = require('react');
var moment = require('moment');

var WeekView = React.createFactory(require('./views/week.jsx'));

page('/', function(ctx) {
	var year = moment().locale('fi').year();
	var week = moment().locale('fi').week();

	return page.show('/matches/' + year + '/' + week + '');
});

page('/matches/:year/:week', function(ctx) {
	var weekView = WeekView({
		'year': parseInt(ctx.params.year),
		'week': parseInt(ctx.params.week),
	});
	return React.render(weekView, document.getElementById('view'));
});

// 'moment' requires the locale-module when you set a locale through
// 'moment.locale', hence 'browserify' does not pick it up.
require('moment/locale/fi') && page.start();

Fix client 'moment.locale' not being set to 'fi'.


// The contents of this file will be executed before any of
// your view controllers are ever executed, including the index.
// You have access to all functionality on the `Alloy` namespace.
//
// This is a great place to do any initialization for your app
// or create any global variables/functions that you'd like to
// make available throughout your app. You can easily make things
// accessible globally by attaching them to the `Alloy.Globals`
// object. For example:
//
// Alloy.Globals.someGlobalFunction = function(){};

Alloy.Globals.pageStack = {
  navigationWindow: null,
  pages: [],
  open: function(page) {

    Alloy.Globals.pageStack.pages.push(page);

    if (OS_IOS) {

      if (Alloy.Globals.pageStack.navigationWindow === null) {

        Alloy.Globals.pageStack.navigationWindow = Ti.UI.iOS.createNavigationWindow({
          window: page
        });

        Alloy.Globals.pageStack.navigationWindow.open();
      } else {
        Alloy.Globals.pageStack.navigationWindow.openWindow(page);
      }
    } else if (OS_MOBILEWEB) {

      Alloy.Globals.pageStack.navigationWindow.open(page);
    } else {
      page.open();
    }
  },
  close: function(page) {

    if (OS_IOS) {
      Alloy.Globals.pageStack.navigationWindow.closeWindow(page);
    } else if (OS_MOBILEWEB) {
      Alloy.Globals.pageStack.navigationWindow.close(page);
    } else {
      page.close();
    }
    Alloy.Globals.pageStack.pages = _.without(Alloy.Globals.pageStack.pages, page);
  },
  back: function() {
    var page = _.last(Alloy.Globals.pageStack.pages);
    Alloy.Globals.pageStack.close(page);

    if (Alloy.Globals.pageStack.pages.length == 0 && OS_IOS) {
      Alloy.Globals.pageStack.navigationWindow.close();
      Alloy.Globals.pageStack.navigationWindow = null;
    }
  },
  home: function() {

    while (Alloy.Globals.pageStack.pages.length >= 2) {
      Alloy.Globals.pageStack.back();
    }
  }
};

Add my page stack to manage navigation on both iOS and Android


import React, { Component } from 'react'

export class SignIn extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (

          What up

    )
  }
}

import React, { Component } from 'react';
import { FacebookLogin } from 'react-facebook-login-component';

export class SignIn extends Component {
  constructor(props, context) {
    super(props, context);
  }

  responseFacebook (response, a) {
    console.log(response, a);
    //anything else you want to do(save to localStorage)...
  }

  render() {
    return (
      <div>
        <FacebookLogin socialId="375795929474685"
                       language="en_US"
                       scope="public_profile,email"
                       responseHandler={this.responseFacebook}
                       xfbml={true}
                       version="v2.5"
                       class="facebook-login"
                       buttonText="Login With Facebook"/>
      </div>
    )
  }
}

Add facebook login button (functional.. ish)


'use strict';

(function() {
  angular.module('ncsaas')
    .service('customersService', ['baseServiceClass', '$q', 'agreementsService', customersService]);

  function customersService(baseServiceClass, $q, agreementsService) {
    var ServiceClass = baseServiceClass.extend({
      filterByCustomer: false,

      init:function() {
        this._super();
        this.endpoint = '/customers/';
      },
      getPersonalOrFirstCustomer: function(username) {
        var deferred = $q.defer();
        /*jshint camelcase: false */
        this.getList().then(
          function(customers) {
            for(var i = 0; i < customers.length; i++) {
              if (customers[i].name === username) {
                deferred.resolve(customers[i]);
              }
            }
            if (customers.length !== 0) {
              deferred.resolve(customers[0]);
            } else {
              deferred.resolve(undefined);
            }
          }
        );

        return deferred.promise;
      },
      getCurrentPlan: function(customer) {
        var deferred = $q.defer(),
          error = true;
        agreementsService.getList({customer: customer.uuid}).then(function(response) {
          if (response) {
            for (var i = 0; i < response.length; i++) {
              if (response[i].customer == customer.url && response[i].state == 'active') {
                deferred.resolve(response[i]);
                error = false;
                break;
              }
            }
          }
          if (error) {
            deferred.reject();
          }
        });

        return deferred.promise;
      }
    });
    return new ServiceClass();
  }

})();

'use strict';

(function() {
  angular.module('ncsaas')
    .service('customersService', ['baseServiceClass', '$q', 'agreementsService', customersService]);

  function customersService(baseServiceClass, $q, agreementsService) {
    var ServiceClass = baseServiceClass.extend({
      filterByCustomer: false,

      init:function() {
        this._super();
        this.endpoint = '/customers/';
      },
      getPersonalOrFirstCustomer: function(username) {
        var deferred = $q.defer();
        /*jshint camelcase: false */
        this.getList().then(
          function(customers) {
            for(var i = 0; i < customers.length; i++) {
              if (customers[i].name === username) {
                deferred.resolve(customers[i]);
              }
            }
            if (customers.length !== 0) {
              deferred.resolve(customers[0]);
            } else {
              deferred.resolve(undefined);
            }
          }
        );

        return deferred.promise;
      },
      getCurrentPlan: function(customer) {
        var deferred = $q.defer();
        agreementsService.getList({customer: customer.uuid, state: 'active'}).then(function(response) {
          if (response.length > 0) {
            deferred.resolve(response[0]);
          } else {
            deferred.reject();
          }
        });

        return deferred.promise;
      }
    });
    return new ServiceClass();
  }

})();

Add filter by state (SAAS-635)


import React, { Component } from 'react';
import { TouchableOpacity, Image, View } from 'react-native';
// import styles from './styles/ClickableImageStyle';
import PropTypes from 'prop-types';
import { StackNagivator } from 'react-navigation';
import FadeInView from '../animations/FadeInView';

export default class ClickableImage extends Component {
  static propTypes = {
    onPress: PropTypes.func,
    text: PropTypes.object,
    children: PropTypes.object,
    navigation: PropTypes.object,
    imageStyle: Image.propTypes.style,
    touchStyle: View.propTypes.style
  }

  render() {
    return (
      <View>
        <FadeInView>
          <TouchableOpacity style={this.props.touchStyle} activeOpacity={.85} onPress={this.props.onPress}>
              <Image style={this.props.imageStyle} source={this.props.text} />
          </TouchableOpacity>
        </FadeInView>
      </View>
    )
  }
}

import React, { Component } from 'react';
import { TouchableOpacity, Image, View } from 'react-native';
// import styles from './styles/ClickableImageStyle';
import PropTypes from 'prop-types';
import { StackNagivator } from 'react-navigation';
import FadeInView from '../animations/FadeInView';

export default class ClickableImage extends Component {
  static propTypes = {
    onPress: PropTypes.func,
    onLongPress: PropTypes.func,
    text: PropTypes.object,
    children: PropTypes.object,
    navigation: PropTypes.object,
    imageStyle: Image.propTypes.style,
    touchStyle: View.propTypes.style
  }

  _onLongPress() {

  }

  render() {
    return (
      <View>
        <FadeInView>
          <TouchableOpacity style={this.props.touchStyle} activeOpacity={.85} onPress={this.props.onPress} onLongPress={this.props.onLongPress}>
              <Image style={this.props.imageStyle} source={this.props.text} />
          </TouchableOpacity>
        </FadeInView>
      </View>
    )
  }
}

Add variables and function call for longtouch


import Day from './day';

const Calendar = ({ events }) => {

  let id = 0;
  let lastEpochDays = 0;
  let daysEvents = [];
  let allDays = [];

  for (let event of events) {

    event.start_time = new Date(event.start_time);
    let epochDays = Math.floor(event.start_time.getTime() / 8.64e7);

    if (lastEpochDays == 0) {
      daysEvents.push(event);
      lastEpochDays = epochDays;
    }
    else if (epochDays > lastEpochDays) {
      allDays.push(<Day events={daysEvents} key={id}/>);
      daysEvents = [ event ];
      lastEpochDays = epochDays;
    }
    else if (epochDays == lastEpochDays) {
      daysEvents.push(event);
    }
    id++;
  }
  allDays.push(<Day events={daysEvents} key={id}/>);

  return (

Calendar

      {allDays}

  );
};

export default Calendar;

import Day from './day';

const Calendar = ({ events }) => {

  let id = 0;
  let lastEpochDays = 0;
  let daysEvents = [];
  let allDays = [];

  const MS_IN_DAY = 1000*60*60*24;

  for (let event of events) {

    event.start_time = new Date(event.start_time);
    let epochDays = Math.floor(event.start_time.getTime() / MS_IN_DAY);

    if (lastEpochDays == 0) {
      daysEvents.push(event);
      lastEpochDays = epochDays;
    }
    else if (epochDays > lastEpochDays) {
      allDays.push(<Day events={daysEvents} key={id}/>);
      daysEvents = [ event ];
      lastEpochDays = epochDays;
    }
    else if (epochDays == lastEpochDays) {
      daysEvents.push(event);
    }
    id++;
  }
  allDays.push(<Day events={daysEvents} key={id}/>);

  return (

Calendar

      {allDays}

  );
};

export default Calendar;

Make epochDays more legible by visualizing what it's divided by


var EventEmitter = require('events').EventEmitter;

var hardware = module.exports = new EventEmitter();
//var e = nobleEmitter.connect(peripheralUuid, serviceUuid, characteristicUuid);

var e = (process.env.DEVICE) ?
          require('./serialport')(process.env.DEVICE) :
          require('./stdin-mock');

var commands = {
  0: function () {
    emitScore('a');
  },
  1: function () {
    emitScore('b');
  },
  2: function () {
    emitUndo('a');
  },
  3: function () {
    emitUndo('b');
  },
  4: function () {
    hardware.emit('restart');
  }
};

e.on('connected', function () {
  console.log('ble connected');
  hardware.emit('connected');
});

e.on('data', function (data) {
  console.log('data: ', data);
  if(commands[data])
    commands[data]();
  else
    console.log('unknown command "' + data + '"');
});

e.on('disconnected', function () {
  console.log('ble disconnected');
  hardware.emit('disconnected');
});

function emitScore (side) {
  hardware.emit('score', { side: side });
}

function emitUndo (side) {
  hardware.emit('undo', { side: side });
}

var EventEmitter = require('events').EventEmitter;

var hardware = module.exports = new EventEmitter();
//var e = nobleEmitter.connect(peripheralUuid, serviceUuid, characteristicUuid);

var e = (process.env.DEVICE) ?
          require('./serialport')(process.env.DEVICE) :
          require('./stdin-mock');

var commands = {
  0: function () {
    emitScore('a'); // single click
  },
  1: function () {
    emitScore('b'); // single click
  },
  2: function () {
    emitUndo('a'); // double click
  },
  3: function () {
    emitUndo('b'); // double click
  },
  4: function () {
    hardware.emit('restart'); // long press
  },
  5: function () {
    hardware.emit('restart'); // long press
  }
};

e.on('connected', function () {
  console.log('ble connected');
  hardware.emit('connected');
});

e.on('data', function (data) {
  console.log('data: ', data);
  if(commands[data])
    commands[data]();
  else
    console.log('unknown command "' + data + '"');
});

e.on('disconnected', function () {
  console.log('ble disconnected');
  hardware.emit('disconnected');
});

function emitScore (side) {
  hardware.emit('score', { side: side });
}

function emitUndo (side) {
  hardware.emit('undo', { side: side });
}

Add command to allow reset with longpress on both a and b.


import angular from 'angular';
import uiRouter from 'angular-ui-router';
import gameComponent from './game.component';

const gameModule = angular
  .module('game', [uiRouter])
  .config(($stateProvider) => {
    "ngInject";

    $stateProvider.state('game', {
      url: '/game',
      component: 'game'
    });
  })
  .component('game', gameComponent)
  .name;

export default gameModule;

import angular from 'angular';
import uiRouter from 'angular-ui-router';
import gameComponent from './game.component';

const gameModule = angular
  .module('game', [uiRouter])
  .config(($stateProvider) => {
    "ngInject";

    $stateProvider.state('game', {
      url: '/game',
      component: 'game',
      resolve: {
        gameServiceInit: GameService => GameService.initialize()
      },
    });
  })
  .component('game', gameComponent)
  .name;

export default gameModule;

Initialize GameService upon displaying GameComponent


import React from 'react';
import ServerThumbnail from './ServerThumbnail';

const ServerListing = ({
  servers
}) => (

    {servers.length === 0 ? (

        No servers are currently available.

    ) : servers.map((server) => (
      <ServerThumbnail
        key={server.url}
        server={server}
        media={server.booth && server.booth.media}
      />
    ))}

);

export default ServerListing;

Add a margin around server thumbnails for mobile screens.


export const UPDATE_ENTITY = 'UPDATE_ENTITY';

export function updateEntity(href, token, entity) {
	return {
		type: UPDATE_ENTITY,
		href,
		token,
		entity,
		updatedAt: Date.now()
	};
}

export const UPDATE_ENTITY = 'UPDATE_ENTITY';

export function updateEntity(href, token, entity) {
	const selfLink = entity && entity.links && entity.links.find(link => link.rel.includes('self'));
	if (selfLink) {
		href = selfLink.href;
	}
	return {
		type: UPDATE_ENTITY,
		href,
		token,
		entity,
		updatedAt: Date.now()
	};
}

Fix redux update not using self link


/* 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/. */

(function (aGlobal) {
  var Cc = Components.classes;
  var Ci = Components.interfaces;
  var process = Cc["@mozilla.org/process/util;1"]
                  .createInstance(Ci.nsIProcess);
  var file = Cc["@mozilla.org/file/local;1"]
               .createInstance(Ci.nsILocalFile);
  const kIEPath = "C:\\Program Files\\Internet Explorer\\iexplore.exe";
  const kChromePath = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe";
  var SwitchLinkExternalHandler = {
    startIE: function startIE() {
      this.startExternalProcess(kIEPath);
    },
    startChrome: function startChrome() {
      this.startExternalProcess(kChromePath);
    },
    startExternalProcess: function startExternalProcess(aPath) {
      file.initWithPath(aPath);
      process.init(file);
      var args = ["https://dev.mozilla.jp"];
      process.run(false, args, args.length);
    },
    run: function run() {
    },
  };

  aGlobal.SwitchLinkExternalHandler = SwitchLinkExternalHandler;
})(this);

/* 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/. */

(function (aGlobal) {
  var Cc = Components.classes;
  var Ci = Components.interfaces;
  const kIEPath = "C:\\Program Files\\Internet Explorer\\iexplore.exe";
  const kChromePath = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe";
  var SwitchLinkExternalHandler = {
    startIE: function startIE() {
      this.startExternalProcess(kIEPath);
    },
    startChrome: function startChrome() {
      this.startExternalProcess(kChromePath);
    },
    startExternalProcess: function startExternalProcess(aPath) {
      var process = Cc["@mozilla.org/process/util;1"]
                      .createInstance(Ci.nsIProcess);
      var file = Cc["@mozilla.org/file/local;1"]
                   .createInstance(Ci.nsILocalFile);
      file.initWithPath(aPath);
      process.init(file);
      var args = ["https://dev.mozilla.jp"];
      process.run(false, args, args.length);
    },
    run: function run() {
    },
  };

  aGlobal.SwitchLinkExternalHandler = SwitchLinkExternalHandler;
})(this);

Create file and process instance every time


var Timers = require('timers')
var SenecaModule = require('seneca')

global.setImmediate = global.setImmediate || Timers.setImmediate

var SenecaExport = function(options, more_options) {
  options = options || {}
  options.legacy = options.legacy || {}
  options.legacy.transport = false

  var seneca = SenecaModule(options, more_options)

  seneca.use(function browser() {
    this.add('role:transport,hook:client,type:browser', hook_client_browser)

    var tu = this.export('transport/utils')

    function hook_client_browser(msg, reply) {
      var seneca = this

      reply({
        send: function(msg, reply, meta) {
          fetch('/seneca', {
	    method: 'post',
	    body: tu.stringifyJSON(tu.externalize_msg(seneca, msg, meta))

          }).then(function(response) {
            if(response.ok) {
	      return response.json()
            }
            else {
              // FIX: handle transport errors
              return null
            }
          }).then(function(json) {
            // FIX: seneca.reply broken in browser
            var rep = tu.internalize_reply(seneca, json)
            reply(rep.err, rep.out)
          })
        }
      })
    }
  })

  return seneca
}

SenecaExport.prototype = SenecaModule.prototype

module.exports = SenecaExport

var Timers = require('timers')
var SenecaModule = require('seneca')

global.setImmediate = global.setImmediate || Timers.setImmediate

var SenecaExport = function(options, more_options) {
  options = options || {}
  options.legacy = options.legacy || {}
  options.legacy.transport = false

  var seneca = SenecaModule(options, more_options)

  seneca.use(function browser() {
    this.add('role:transport,hook:client,type:browser', hook_client_browser)

    var tu = this.export('transport/utils')

    function hook_client_browser(msg, reply) {
      var seneca = this

      reply({
        send: function(msg, reply, meta) {
          fetch('/seneca', {
      credentials: 'same-origin',
	    method: 'post',
	    body: tu.stringifyJSON(tu.externalize_msg(seneca, msg, meta))

          }).then(function(response) {
            if(response.ok) {
	      return response.json()
            }
            else {
              // FIX: handle transport errors
              return null
            }
          }).then(function(json) {
            // FIX: seneca.reply broken in browser
            var rep = tu.internalize_reply(seneca, json)
            reply(rep.err, rep.out)
          })
        }
      })
    }
  })

  return seneca
}

SenecaExport.prototype = SenecaModule.prototype

module.exports = SenecaExport

Add credentials option in order to send headers


// We don't want everyone to be synchronized, so add a random amount to the interval
const NEW_VERSION_INTERVAL = (10 + Math.random() * 5) * 60 * 1000;
// const NEW_VERSION_INTERVAL = 5000; // For debuggins
module.exports = [
  '$interval', '$http', '$rootScope', 'storedConfiguration',
  ($interval, $http, $rootScope, storedConfiguration) => {
    let ignoreNewVersion = false;

    $rootScope.ignoreNewVersion = () => {
      ignoreNewVersion = true;
      $rootScope.newVersionAvailable = false;
      $interval.cancel(versionCheck);
    };

    $rootScope.loadNewVersion = () => {
      const {configuration} = $rootScope;
      storedConfiguration.saveConfiguration(configuration);
      window.location.search = 'cacheBust';
    };

    let versionCheck = $interval(checkForNewVersion, NEW_VERSION_INTERVAL);

    function checkForNewVersion() {
      $http
        .get('currentVersion?' + new Date().getTime())
          .success(version => {
            // clVersion is generate at build time and injected into index.html
            // it is a global value!
            $rootScope.newVersionAvailable = !ignoreNewVersion && clVersion != version;
          })
          .error(error => {

          });
    }
  }
];

// We don't want everyone to be synchronized, so add a random amount to the interval
const NEW_VERSION_INTERVAL = (10 + Math.random() * 5) * 60 * 1000;
// const NEW_VERSION_INTERVAL = 5000; // For debuggins
module.exports = [
  '$interval', '$http', '$rootScope', 'storedConfiguration',
  ($interval, $http, $rootScope, storedConfiguration) => {
    let ignoreNewVersion = false;

    $rootScope.ignoreNewVersion = () => {
      ignoreNewVersion = true;
      $rootScope.newVersionAvailable = false;
      $interval.cancel(versionCheck);
    };

    $rootScope.loadNewVersion = () => {
      const {configuration} = $rootScope;
      storedConfiguration.saveConfiguration(configuration);
      window.location.search = 'cacheBust';
    };

    let versionCheck = $interval(checkForNewVersion, NEW_VERSION_INTERVAL);

    checkForNewVersion();

    function checkForNewVersion() {
      $http
        .get('currentVersion?' + new Date().getTime())
          .success(version => {
            // clVersion is generate at build time and injected into index.html
            // it is a global value!
            $rootScope.newVersionAvailable = !ignoreNewVersion && clVersion != version;
          })
          .error(error => {

          });
    }
  }
];

Check for new version on load.


Dashboard.SmoothHelper = Ember.Mixin.create({
  _smoothHelperInterval: 10,

  _smoothHelperProperties: function() {
    return {};
  }.property(),

  setSmooth: function(property, value, duration) {
    var p = this.get('_smoothHelperProperties');
    var d = typeof duration !== 'undefined' ? duration : 1000;
    if (p[property] && p[property].intervalCall) {
      clearInterval(p[property].intervalCall);
    }
    p[property] = {
      initialValue: this.get(property),
      targetValue: value,
      currentX: 0,
      step: Math.PI / (d / this.get('_smoothHelperInterval') * 2)
    };
    var that = this;
    p[property].intervalCall = setInterval(function() {
      if (p[property].currentX >= Math.PI / 2)
        clearInterval(p[property].intervalCall);
      else {
        that.set(property, p[property].initialValue + (p[property].targetValue - p[property].initialValue) * Math.sin(p[property].currentX));
        p[property].currentX = p[property].currentX + p[property].step;
      }
    }, this.get('_smoothHelperInterval'));
  }
});

Dashboard.SmoothHelper = Ember.Mixin.create({
  _smoothHelperInterval: 10,
  _smoothHelperDuration: 1000,

  _smoothHelperProperties: function() {
    return {};
  }.property(),

  setSmooth: function(property, value, duration) {
    var p = this.get('_smoothHelperProperties');
    var d = typeof duration !== 'undefined' ? duration : this.get('_smoothHelperDuration');
    if (p[property] && p[property].intervalCall) {
      clearInterval(p[property].intervalCall);
    }
    p[property] = {
      initialValue: this.get(property),
      targetValue: value,
      currentX: 0,
      step: Math.PI / (d / this.get('_smoothHelperInterval') * 2)
    };
    var that = this;
    p[property].intervalCall = setInterval(function() {
      if (p[property].currentX >= Math.PI / 2)
        clearInterval(p[property].intervalCall);
      else {
        that.set(property, p[property].initialValue + (p[property].targetValue - p[property].initialValue) * Math.sin(p[property].currentX));
        p[property].currentX = p[property].currentX + p[property].step;
      }
    }, this.get('_smoothHelperInterval'));
  }
});

Make the default duration of the transition in the 'setSmooth' method of SmoothHelper a property.


(function(context) {
    context.global = context;

    var logger = context.logger || Interop.import('logger');

    if(!context.console){
        context.console = {
            log: function(){
                for(var i = 0; i < arguments.length; i ++){
                    logger.info(arguments[i])
                }
            },
            info: function(){
                for(var i = 0; i < arguments.length; i ++){
                    logger.info(arguments[i])
                }
            },
            debug: function(){
                for(var i = 0; i < arguments.length; i ++){
                    logger.debug(arguments[i])
                }
            },
            trace: function(){
                for(var i = 0; i < arguments.length; i ++){
                    logger.debug(arguments[i])
                }
            },
            warn: function(){
                for(var i = 0; i < arguments.length; i ++){
                    logger.warning(arguments[i])
                }
            },
            error: function(){
                for(var i = 0; i < arguments.length; i ++){
                    logger.error(arguments[i])
                }
            }
        };
    }

})(this);

(function (context) {
    context.global = context;

    var logger = context.logger || Interop.import('logger');

    context.console = context.console || {};

    context.console.debug = function () {
        for (var i = 0; i < arguments.length; i++) {
            logger.debug(arguments[i])
        }
    };

    context.console.info = function () {
        for (var i = 0; i < arguments.length; i++) {
            logger.info(arguments[i])
        }
    };

    context.console.warn = function () {
        for (var i = 0; i < arguments.length; i++) {
            logger.warning(arguments[i])
        }
    };

    context.console.error = function () {
        for (var i = 0; i < arguments.length; i++) {
            logger.error(arguments[i])
        }
    };

    context.console.log = context.console.info;

    context.console.trace = context.console.debug;

})(this);

Fix console polyfill for Graal


// Game Controller
// A checkers board has 64 squares like a chess board but it only uses 32 squares all the same color
// Each player has 12 pieces and pieces that make until the last squares on the enemy side is promoted
// Promoted piece also moves on diagonals with no limit of squares if it is free
// The moves are on diagonal one square at a time
// To capture, the piece capturing jumps the enemy piece advancing two squares on diagonal
// The game end when one of the player lost all pieces or doesn't have moves to make
function Game() {
  /* Board.initial generate all elements used to be pieces and empty squares on the
  board and return an array of the board initial position*/
  this.board = Board.initial();
}

Game.prototype.flattenBoard = function(param){
  var flatBoard = this.board.reduce(
    function(a,b) {
      return a.concat(b);
    },[]
  );
  return flatBoard;
}

// Game Controller
// A checkers board has 64 squares like a chess board but it only uses 32 squares all the same color
// Each player has 12 pieces and pieces that make until the last squares on the enemy side is promoted
// Promoted piece also moves on diagonals with no limit of squares if it is free
// The moves are on diagonal one square at a time
// To capture, the piece capturing jumps the enemy piece advancing two squares on diagonal
// The game end when one of the player lost all pieces or doesn't have moves to make
function Game() {
  /* Board.initial generate all elements used to be pieces and empty squares on the
  board and return an array of the board initial position*/
  this.board = Board.initial();

}

// flattenBoard ask the Board to flatten the current game board
Game.prototype.flattenBoard = function(){
  return Board.flatten(this.board);
}

Game.prototype.selectPiece = function(element){
  console.log(element);
}

Add call to Board flatten function and create selectPiece prototype function


(function() {
    "use strict";

    head.js("js/vendor/json2.js")
        .js("js/vendor/jsonlint.js")
        .js("js/vendor/lucene/lucene-query-parser.js")
        .js("http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js","js/vendor/angular.min.js", function() {
            head.js("js/vendor/angular-sanitize.min.js","js/vendor/ng-bootstrap/ui-bootstrap-custom-tpls-0.1.0-SNAPSHOT.min.js","js/vendor/select2/select2.min.js","js/vendor/select2/angular-ui.min.js", "js/app.js", function() {
                head.js("js/controllers/DropdownCtrl.js")
                    .js("js/controllers/AnalyzerCtrl.js")
                    .js("js/controllers/TokenizerCtrl.js")
                    .js("js/controllers/NavbarCtrl.js")
                    .js("js/controllers/AdhocCtrl.js")
                    .js("js/controllers/QueryInput.js")
                    .js("js/controllers/QueryOutput.js")
                    .js("js/directives/modal.js")
                    .js("js/directives/rawerror.js")
                    .js("js/filters/jsonlint.js");
            });
        });

}).call(this);

(function() {
    "use strict";

    head.js("js/vendor/json2.js")
        .js("js/vendor/jsonlint.js")
        .js("http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js","js/vendor/angular.min.js", function() {
            head.js("js/vendor/angular-sanitize.min.js","js/vendor/ng-bootstrap/ui-bootstrap-custom-tpls-0.1.0-SNAPSHOT.min.js","js/vendor/select2/select2.min.js","js/vendor/select2/angular-ui.min.js", "js/app.js", function() {
                head.js("js/controllers/DropdownCtrl.js")
                    .js("js/controllers/AnalyzerCtrl.js")
                    .js("js/controllers/TokenizerCtrl.js")
                    .js("js/controllers/NavbarCtrl.js")
                    .js("js/controllers/AdhocCtrl.js")
                    .js("js/controllers/QueryInput.js")
                    .js("js/controllers/QueryOutput.js")
                    .js("js/directives/modal.js")
                    .js("js/directives/rawerror.js")
                    .js("js/filters/jsonlint.js");
            });
        });

}).call(this);

Remove lucene query parser lib


import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    increment (state) {
      state.counter++
    }
  }
})

export default store

import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    increment (state) {
      state.counter++
    }
  }
})

export default store

Fix example for vuex store


var express = require('express');

module.exports = function() {
  this.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  this.set('db-uri', 'mongodb://localhost/rezerve');
  this.set('view options', {
    pretty: true
  });
}

var express = require('express');
var utils = require('connect').utils;

module.exports = function() {
  this.set('db-uri', 'mongodb://localhost/rezerve');
  this.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

  // Set pretty prints
  this.express.locals.pretty = true;
};

Add pretty print for Jade HTML output


/*
 *  Copyright 2015 Adobe Systems Incorporated
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
new hobs.TestSuite("${artifactName} Tests", {path:"/apps/${appsFolderName}l/tests/SampleTests.js", register: true})

    .addTestCase(new hobs.TestCase("Hello World component on english page")
        .navigateTo("/content/${contentFolderName}/en.html")
        .asserts.location("/content/${contentFolderName}/en.html", true)
        .asserts.visible(".helloworld", true)
    )

    .addTestCase(new hobs.TestCase("Hello World component on french page")
        .navigateTo("/content/${contentFolderName}/fr.html")
        .asserts.location("/content/${contentFolderName}/fr.html", true)
        .asserts.visible(".helloworld", true)
    );

/*
 *  Copyright 2015 Adobe Systems Incorporated
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
new hobs.TestSuite("${artifactName} Tests", {path:"/apps/${appsFolderName}/tests/SampleTests.js", register: true})

    .addTestCase(new hobs.TestCase("Hello World component on english page")
        .navigateTo("/content/${contentFolderName}/en.html")
        .asserts.location("/content/${contentFolderName}/en.html", true)
        .asserts.visible(".helloworld", true)
    )

    .addTestCase(new hobs.TestCase("Hello World component on french page")
        .navigateTo("/content/${contentFolderName}/fr.html")
        .asserts.location("/content/${contentFolderName}/fr.html", true)
        .asserts.visible(".helloworld", true)
    );

Remove typo from sample JS test path


#! /usr/bin/env node

var cuecard = require('./cuecard.js'),
	path = require('path'),
	cuecardFile = require(path.join(process.cwd(), 'cuecard.js'));

if (cuecardFile.config) {
	cuecard.create(cuecardFile.config);
}

#! /usr/bin/env node

var path = require('path'),
	cuecardFile = require(path.join(process.cwd(), 'cuecard.js'));

Remove global Cuecard dep to run slideshow


"use strict";
var errorHandler = module.exports = { };

var helper = require("./helper.js");
var router = require("../router.js");

errorHandler.register = function() {
  router.bindErrorHandler(function(request, res, error) {

    var errorDetails = error.stack.split("\n");
    console.log(JSON.stringify({
      request: request,
      error: errorDetails.shift(),
      stack: errorDetails
    }));

    return helper.handleError(request, res, {
      status: "500",
      code: "EUNKNOWN",
      title: "An unknown error has occured. Sorry?",
      detail: "??"
    });
  });
};

"use strict";
var errorHandler = module.exports = { };

var helper = require("./helper.js");
var router = require("../router.js");

errorHandler.register = function() {
  router.bindErrorHandler(function(request, res, error) {

    var errorDetails = error.stack.split("\n");
    console.error(JSON.stringify({
      request: request,
      error: errorDetails.shift(),
      stack: errorDetails
    }));

    return helper.handleError(request, res, {
      status: "500",
      code: "EUNKNOWN",
      title: "An unknown error has occured. Sorry?",
      detail: "??"
    });
  });
};

Send error log to stdout


/*
 * Author: CM
 */

(function($) {

  /**
   * @param {Object} [lazyLoadOptions] options to lazy-load-images plugin.
   * @returns {jQuery}
   */
  $.fn.lazyImageSetup = function(lazyLoadOptions) {
    return this.each(function() {
      var options = _.defaults(lazyLoadOptions || {}, {
        threshold: 600,
        failure_limit: 10
      });
      var $this = $(this);
      if ($this.hasClass('scrollable')) {
        options.container = $this;
      }
      $this.find('img.lazy').lazyload(options);
    });
  };

})(window.jQuery);

/*
 * Author: CM
 */

(function($) {

  /**
   * @param {Object} [lazyLoadOptions] options to lazy-load-images plugin.
   * @returns {jQuery}
   */
  $.fn.lazyImageSetup = function(lazyLoadOptions) {
    return this.each(function() {
      var options = _.defaults(lazyLoadOptions || {}, {
        threshold: 600,
        failure_limit: 10
      });
      var $this = $(this);
      if ($this.closest('.scrollable').length) {
        options.container = $this.closest('.scrollable');
      }
      $this.find('img.lazy').lazyload(options);
    });
  };

})(window.jQuery);

Improve container detection for images lazy load


var express = require('express');
var path = require('path');
var expressValidator = require('express-validator');

var home = require('./routes/home.js');
var api = require('./routes/api.js');

var port = process.env.PORT || 5000;

var app = express();

app.configure(function () {
	app.set('views', __dirname + '/views');
	app.set('view engine', 'jade');
	app.use(express.logger());
	app.use(express.urlencoded());
	app.use(express.json());
	app.use(express.methodOverride());
	app.use(express.static(path.join(__dirname, 'public')));
	app.use(express.static(path.join(__dirname, 'controllers')));
    app.use(express.static(path.join(__dirname, 'services')));
	app.use(expressValidator());
});

app.get('/', home.index);
app.put('/api/subscriptions', api.createSubscription);
app.get('/api/subscriptions', api.readSubscriptions);
app.del('/api/subscriptions', api.deleteSubscription);
app.get('/api/updates', api.readUpdates);

app.listen(port);

var express = require('express');
var path = require('path');
var expressValidator = require('express-validator');

var home = require('./routes/home.js');
var api = require('./routes/api.js');

var port = process.env.PORT || 5000;

var app = express();

app.configure(function () {
	app.set('views', __dirname + '/views');
	app.set('view engine', 'jade');
	app.use(express.logger());
	app.use(express.urlencoded());
	app.use(express.json());
	app.use(express.methodOverride());
	app.use(express.static(path.join(__dirname, 'public')));
	app.use(express.static(path.join(__dirname, 'controllers')));
    app.use(express.static(path.join(__dirname, 'services')));
	app.use(expressValidator());
});

app.all('/', function(req, res, next) {
	res.header("Access-Control-Allow-Origin", "*");
	res.header("Access-Control-Allow-Headers", "X-Requested-With");
	next();
});
app.get('/', home.index);
app.put('/api/subscriptions', api.createSubscription);
app.get('/api/subscriptions', api.readSubscriptions);
app.del('/api/subscriptions', api.deleteSubscription);
app.get('/api/updates', api.readUpdates);

app.listen(port);

Add Access-Control-Allow-Origin for CORS from gh-pages


#!/usr/bin/env node

'use strict'

const browserify = require('browserify')
const str = require('string-to-stream')
const babel = require('babel-core')
const babelify = require('babelify')
const envify = require('envify')

const pkg = require('../package.json')

browserify(str(js(pkg)), { basedir: process.cwd(), detectGlobals: false, browserField: false })
  .transform(babelify.configure({
    presets: [require('babel-preset-react')],
    plugins: [require('babel-plugin-transform-es2015-modules-commonjs')]
  }))
  .transform(envify)
  .transform(require('brfs'))
  .bundle((err, content) => {
    if (err) throw err
    const opts = { presets: [require('babel-preset-babili')], comments: false }
    const res = babel.transform(content, opts)
    if (err) throw err
    console.log(res.code)
  })

function js ({ name }) {
  return (
`import createServerRenderer from '${name}/server_renderer.js'
import dataToState from '${name}/lib/data_to_state'
import components from './components'
const { html, data } = __params
const renderer = createServerRenderer({
  components,
  state: dataToState(data)
})
const $ = renderer(html)
$('body').attr('data-penguin-built', 'true')
__params.output = $.html()
`
  )
}

#!/usr/bin/env node

'use strict'

const browserify = require('browserify')
const str = require('string-to-stream')
const babel = require('babel-core')
const babelify = require('babelify')
const envify = require('envify')

const pkg = require('../package.json')

browserify(str(js(pkg)), { basedir: process.cwd() })
  .transform(babelify.configure({
    presets: [require('babel-preset-react')],
    plugins: [require('babel-plugin-transform-es2015-modules-commonjs')]
  }))
  .transform(envify)
  .transform(require('brfs'))
  .bundle((err, content) => {
    if (err) throw err
    const opts = { presets: [require('babel-preset-babili')], comments: false }
    const res = babel.transform(content, opts)
    if (err) throw err
    console.log(res.code)
  })

function js ({ name }) {
  return (
`import createServerRenderer from '${name}/server_renderer.js'
import dataToState from '${name}/lib/data_to_state'
import components from './components'
const { html, data } = __params
const renderer = createServerRenderer({
  components,
  state: dataToState(data)
})
const $ = renderer(html)
$('body').attr('data-penguin-built', 'true')
__params.output = $.html()
`
  )
}

Resolve browser field even in server runtime


/*
 * This file is part of the Sylius package.
 *
 * (c) Paweł Jędrzejewski
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

import 'semantic-ui-css/components/api';
import $ from 'jquery';

$.fn.extend({
  taxonMoveUp() {
    const element = this;

    element.api({
      method: 'PUT',
      on: 'click',
      beforeSend(settings) {
        /* eslint-disable-next-line no-param-reassign */
        settings.data = {
          position: jquery(this).data('position') - 1,
        };

        return settings;
      },
      onSuccess() {
        window.location.reload();
      },
    });
  },

  taxonMoveDown() {
    const element = this;

    element.api({
      method: 'PUT',
      on: 'click',
      beforeSend(settings) {
        /* eslint-disable-next-line no-param-reassign */
        settings.data = {
          position: jquery(this).data('position') + 1,
        };

        return settings;
      },
      onSuccess() {
        window.location.reload();
      },
    });
  },
});

/*
 * This file is part of the Sylius package.
 *
 * (c) Paweł Jędrzejewski
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

import 'semantic-ui-css/components/api';
import $ from 'jquery';

$.fn.extend({
  taxonMoveUp() {
    const element = this;

    element.api({
      method: 'PUT',
      on: 'click',
      beforeSend(settings) {
        /* eslint-disable-next-line no-param-reassign */
        settings.data = {
          position: $(this).data('position') - 1,
        };

        return settings;
      },
      onSuccess() {
        window.location.reload();
      },
    });
  },

  taxonMoveDown() {
    const element = this;

    element.api({
      method: 'PUT',
      on: 'click',
      beforeSend(settings) {
        /* eslint-disable-next-line no-param-reassign */
        settings.data = {
          position: $(this).data('position') + 1,
        };

        return settings;
      },
      onSuccess() {
        window.location.reload();
      },
    });
  },
});

Use "$" instead of "jquery" to follow the import statement


function displaySeriesNumber() {
  var s = document.getElementById("id_series");
  var name = s.options[s.selectedIndex].text;

  if (name != '---------') {
    document.getElementById("seriesNumber").style.visibility = "visible";
  }
  else {
      document.getElementById("seriesNumber").style.visibility = "hidden";
  }
}

function displaySeriesNumber() {
  var s = document.getElementById("id_series");
  var name = s.options[s.selectedIndex].text;

  if (name != '---------') {
    document.getElementById("seriesNumber").style.visibility = "visible";
  }
  else {
      var num = document.getElementById("id_number");
      num.value = '';
      document.getElementById("seriesNumber").style.visibility = "hidden";
  }
}

Clear out series number if the user removes the series option


import run from 'ember-metal/run_loop';
import EmberView from 'ember-views/views/view';
import compile from 'ember-template-compiler/system/compile';
import { runAppend, runDestroy } from "ember-runtime/tests/utils";

import { set } from 'ember-metal/property_set';

var view;

QUnit.module('ember-htmlbars: {{#with}} and {{#view}} integration', {
  teardown() {
    runDestroy(view);
  }
});

QUnit.test('can properly re-render an if/else with attribute morphs', function() {
  view = EmberView.create({
    trueClass: 'truthy',
    falseClass: 'falsey',
    switch: true,

    template: compile('{{#if view.switch}}
Truthy
{{else}}
Falsey
{{/if}}')
  });

  runAppend(view);

  equal(view.$('.truthy').length, 1, 'template block rendered properly');

  run(function() {
    set(view, 'switch', false);
  });

  equal(view.$('.falsey').length, 1, 'inverse block rendered properly');
});

import run from 'ember-metal/run_loop';
import EmberView from 'ember-views/views/view';
import compile from 'ember-template-compiler/system/compile';
import { runAppend, runDestroy } from "ember-runtime/tests/utils";

import { set } from 'ember-metal/property_set';

var view;

QUnit.module('ember-htmlbars: custom morph integration tests', {
  teardown() {
    runDestroy(view);
  }
});

QUnit.test('can properly re-render an if/else with attribute morphs', function() {
  view = EmberView.create({
    trueClass: 'truthy',
    falseClass: 'falsey',
    switch: true,

    template: compile('{{#if view.switch}}
Truthy
{{else}}
Falsey
{{/if}}')
  });

  runAppend(view);

  equal(view.$('.truthy').length, 1, 'template block rendered properly');

  run(function() {
    set(view, 'switch', false);
  });

  equal(view.$('.falsey').length, 1, 'inverse block rendered properly');
});

QUnit.test('can properly re-render an if/else with element morphs', function() {
  view = EmberView.create({
    trueClass: 'truthy',
    falseClass: 'falsey',
    switch: true,

    template: compile('{{#if view.switch}}
Truthy
{{else}}
Falsey
{{/if}}')
  });

  runAppend(view);

  equal(view.$('.truthy').length, 1, 'template block rendered properly');

  run(function() {
    set(view, 'switch', false);
  });

  equal(view.$('.falsey').length, 1, 'inverse block rendered properly');
});

Add test to ensure that ElementMorph's can be cleaned up.


var bcrypt = require('bcrypt');

var redisHelper = require('./redis');
var db = redisHelper.getConnection();

function findByUsername(username, next) {
  db.hget('users', username, function(err, hashedpw) {
    if (hashedpw != null) {
      return next(null, { username: username, hashedpw: hashedpw });
    }
    return next(null, null);
  });
}

function verifyUser(username, password, next) {
  findByUsername(username, function(err, user) {
    if (err) { return next(err); }
    if (!user) {
      return next(null, false,
        { message: 'Unknown user: ' + username });
    }
    bcrypt.compare(password, user.hashedpw, function(err, res) {
      if (err) { return next(err); }
      if (res === false) {
        return next(null, false,
          { message: 'Invalid password.' });
      }
      return next(null, user);
    });
  });
}

function setupPassport(passport) {
  passport.serializeUser(function(user, next) {
    next(null, user.username);
  });

  passport.deserializeUser(function(username, next) {
    findByUsername(username, next);
  });

  var LocalStrategy = require('passport-local').Strategy;
  var strategy = new LocalStrategy(verifyUser);
  passport.use(strategy);
}

exports.setupPassport = setupPassport;

var bcrypt = require('bcrypt');

var redisHelper = require('./redis');
var db = redisHelper.getConnection();

function findByUsername(username, next) {
  db.hget('users', username, function(err, hashedpw) {
    if (hashedpw != null) {
      return next(null, { username: username, hashedpw: hashedpw });
    }
    return next(null, null);
  });
}

function verifyUser(username, password, next) {
  findByUsername(username, function(err, user) {
    if (err) { return next(err); }
    if (!user) {
      return next(null, false,
        { message: 'Unknown user: ' + username });
    }
    bcrypt.compare(password, user.hashedpw, function(err, res) {
      if (err) { return next(err); }
      if (res === false) {
        return next(null, false,
          { message: 'Invalid password.' });
      }
      return next(null, user);
    });
  });
}

function createUser(username, password, next) {
  bcrypt.hash(password, 11, function(err, hashedpw) {
    if (err) { return next(err); }
    db.hset('users', username, hashedpw, function(err) {
      next(err);
    });
  });
}

function setupPassport(passport) {
  passport.serializeUser(function(user, next) {
    next(null, user.username);
  });

  passport.deserializeUser(function(username, next) {
    findByUsername(username, next);
  });

  var LocalStrategy = require('passport-local').Strategy;
  var strategy = new LocalStrategy(verifyUser);
  passport.use(strategy);
}

exports.verifyUser = verifyUser;
exports.createUser = createUser;
exports.setupPassport = setupPassport;

Add a 'createUser' function to the user helper module


var path = require('path');
var _ = require('lodash');
var assert = require('assert');

var fs = require("fs");
var fsUtil = require("../lib/utils/fs");

describe('Website Generator', function () {
    it('should correctly generate a book to website', function(done) {
        testGeneration(books[1], "site", function(output) {
            assert(fs.existsSync(path.join(output, "index.html")));
        }, done);
    });

    it('should correctly include styles in website', function(done) {
        testGeneration(books[0], "site", function(output) {
            assert(fs.existsSync(path.join(output, "styles/website.css")));

            var INDEX = fs.readFileSync(path.join(output, "index.html")).toString();
            assert(INDEX.indexOf("styles/website.css") > 0);
        }, done);
    });
});

var path = require('path');
var _ = require('lodash');
var assert = require('assert');

var fs = require("fs");
var fsUtil = require("../lib/utils/fs");

describe('Website Generator', function () {
    it('should correctly generate a book to website', function(done) {
        testGeneration(books[1], "site", function(output) {
            assert(fs.existsSync(path.join(output, "index.html")));
        }, done);
    });

    it('should correctly include styles in website', function(done) {
        testGeneration(books[0], "site", function(output) {
            assert(fs.existsSync(path.join(output, "styles/website.css")));

            var INDEX = fs.readFileSync(path.join(output, "index.html")).toString();
            assert(INDEX.indexOf("styles/website.css") > 0);
        }, done);
    });

    it('should correctly generate a multilingual book to website', function(done) {
        testGeneration(books[2], "site", function(output) {
            assert(fs.existsSync(path.join(output, "index.html")));
            assert(fs.existsSync(path.join(output, "fr/index.html")));
            assert(fs.existsSync(path.join(output, "en/index.html")));
        }, done);
    });
});

Add test for generation of multilingual book


angular.module('mini-data.controllers', [])
  .controller('mainCtrl', function($scope){

    var getTimeInfo = function(){
      var now = new Date();
      var hours = now.getHours();
      var period = 'AM';
      var minutes = now.getMinutes();
      if(hours > 12){
        hours -= 12;
        period = 'PM';
      }
      minutes < 10 && (minutes = '0' + minutes);
      return hours + ':' + minutes + period;
    }

    var getDateInfo = function(){
      var monthObj = {
        0: 'January', 1: 'February', 2: 'March', 3: 'April', 4: 'May', 5: 'June',
        6: 'July', 7: 'August', 8: 'September', 9: 'October', 10: 'November', 11: 'December'
      }

      var now = new Date();
      var day = now.getDate();
      var month = monthObj[now.getMonth()];
      var year = now.getFullYear();

      return month + ' ' + day + 'th, ' + year;
    }

    $scope.time = getTimeInfo();
    $scope.date = getDateInfo();
  });

angular.module('mini-data.controllers', [])
  .controller('mainCtrl', function($scope){

    var getTimeInfo = function(){
      var now = new Date();
      var hours = now.getHours();
      var period = 'AM';
      var minutes = now.getMinutes();
      if(hours > 12){
        hours -= 12;
        period = 'PM';
      }
      minutes < 10 && (minutes = '0' + minutes);
      return hours + ':' + minutes + period;
    }

    var getDateInfo = function(){
      var monthObj = {
        0: 'January', 1: 'February', 2: 'March', 3: 'April', 4: 'May', 5: 'June',
        6: 'July', 7: 'August', 8: 'September', 9: 'October', 10: 'November', 11: 'December'
      }

      var now = new Date();
      var day = now.getDate();
      var month = monthObj[now.getMonth()];
      var year = now.getFullYear();

      return month + ' ' + day + 'th, ' + year;
    }

    $scope.time = getTimeInfo();

    setInterval(function(){
      $scope.$apply(function(){
        $scope.time = getTimeInfo();
      });
    }, 10000);

    $scope.date = getDateInfo();
  });

Add setInterval to keep clock up to date


const { describe, it } = global;

import {expect} from 'chai';
import {default as GameUtil} from '../game';

describe('game utilities', () => {
  it('determines the correct monster', () => {
    const winMap = [
      {
        ingredients: [ 'BloodOfVirgin', 'EyeOfEnemy' ],
        expectedMonster: 'Ooze'
      },
      {
        ingredients: [ 'BloodOfVirgin', 'BasilPetal' ],
        expectedMonster: 'Soup'
      },
      {
        ingredients: [ 'BasilPetal', 'BasilPetal' ],
        expectedMonster: 'Basil'
      }
    ];

    winMap.forEach((test) => {
      let monster = GameUtil.determineMonster(
        test.ingredients
      );

      expect(monster.name).to.be.equal(
        test.expectedMonster
      );

      // // Test the inverse
      // monster = gameUtil.determineMonster(
      //   test.ingredients
      // );

      // expect(monster.label).to.be.equal(
      //   test.expectedMonster
      // );
    });
  });
});

const { describe, it } = global;

import {expect} from 'chai';
import {default as GameUtil} from '../game';

describe('game utilities', () => {
  it('determines the correct monster', () => {
    const winMap = [
      {
        ingredients: [ 'BloodOfVirgin', 'EyeOfEnemy' ],
        expectedMonster: 'Ooze'
      },
      {
        ingredients: [ 'BloodOfVirgin', 'BasilPetal' ],
        expectedMonster: 'Soup'
      },
      {
        ingredients: [ 'BasilPetal', 'BasilPetal' ],
        expectedMonster: 'Basil'
      },
      {
        ingredients: [ 'EyeOfEnemy', 'BasilPetal' ],
        expectedMonster: 'Fairy'
      }
    ];

    winMap.forEach((test) => {
      let monster = GameUtil.determineMonster(
        test.ingredients
      );

      expect(monster.name).to.be.equal(
        test.expectedMonster
      );

      // // Test the inverse
      // monster = gameUtil.determineMonster(
      //   test.ingredients
      // );

      // expect(monster.label).to.be.equal(
      //   test.expectedMonster
      // );
    });
  });
});

Add additional test to determine monster


/*
Code taken from django snippets
http://djangosnippets.org/snippets/1053/
*/
(function($){
    $(function() {
        $('div.inline-group').sortable({
            items: 'div.inline-related',
            handle: 'h3:first',
            update: function() {
                $(this).find('div.inline-related').each(function(i) {
                    if ($(this).find('input[id$="-id"]').val()) {
                        $(this).find('input[id$=order]').val(i+1);
                    }
                });
            }
        });
        $('div.inline-related h3').css('cursor', 'move');
        $('div.inline-related').find('input[id$=order]').parent('div').hide();
    });
})(jQuery);

/*
Code taken from django snippets
http://djangosnippets.org/snippets/1053/
*/
(function($){
    $(function() {
        $('div.inline-group').sortable({
            items: 'div.inline-related',
            handle: 'h3:first',
            update: function() {
                $(this).find('div.inline-related').each(function(i) {
                    if ($(this).find('input[id$="-id"]').val()) {
                        $(this).find('input[id$=order]').val(i+1);
                    }
                });
            },

            // Dirty hack for not loosing value of selects while mooving
            start: function(evt, ui) {
                ui.item.find('select').each(function(){
                    $(this).data('value', $(this).val());
                });
            },
            stop: function(evt, ui) {
                ui.item.find('select').each(function(){
                    $(this).val($(this).data('value'));
                });
            }
        });
        $('div.inline-related h3').css('cursor', 'move');
        $('div.inline-related').find('input[id$=order]').parent('div').hide();
    });
})(jQuery);

Add a dirty hack for not loosing value of selects while mooving


import { Builder, Key } from 'selenium-webdriver';

export const driver = new Builder()
  .forBrowser('chrome')
  .usingServer('http://localhost:4444/wd/hub')
    .build();

export const isMac = async () => {
    const capabilities = await driver.getCapabilities();
    const os = capabilities.get('platform');
    return os.toUpperCase().indexOf('MAC') >= 0;
};

export const isChrome = async () => {
    const capabilities = await driver.getCapabilities();
    const browser = capabilities.get('browserName');
    return browser.toUpperCase().indexOf('CHROME') >= 0;
}

export const getModifierKey = async () => {
  const mac = await isMac();
  return mac ? Key.COMMAND : Key.CONTROL;
}

afterAll(async () => {
  // Cleanup `process.on('exit')` event handlers to prevent a memory leak caused by the combination of `jest` & `tmp`.
  for (const listener of process.listeners('exit')) {
    listener();
    process.removeListener('exit', listener);
  }
  await driver.quit();
});

export const defaultTimeout = 10e3;

import { Builder, Key } from 'selenium-webdriver';

export const driver = new Builder()
  .forBrowser('chrome')
  .withCapabilities({
    'browserstack.local': true,
    'browserstack.localIdentifier': process.env.BROWSERSTACK_LOCAL_IDENTIFIER || process.env.TRAVIS_JOB_NUMBER
  })
  .usingServer('http://localhost:4444/wd/hub')
    .build();

export const isMac = async () => {
    const capabilities = await driver.getCapabilities();
    const os = capabilities.get('platform');
    return os.toUpperCase().indexOf('MAC') >= 0;
};

export const isChrome = async () => {
    const capabilities = await driver.getCapabilities();
    const browser = capabilities.get('browserName');
    return browser.toUpperCase().indexOf('CHROME') >= 0;
}

export const getModifierKey = async () => {
  const mac = await isMac();
  return mac ? Key.COMMAND : Key.CONTROL;
}

afterAll(async () => {
  // Cleanup `process.on('exit')` event handlers to prevent a memory leak caused by the combination of `jest` & `tmp`.
  for (const listener of process.listeners('exit')) {
    listener();
    process.removeListener('exit', listener);
  }
  await driver.quit();
});

export const defaultTimeout = 10e3;

Add browserstack capabilities to webdriver config


var context = require.context('./src', true, /^((?!image).)*\.js$/);
context.keys().forEach(context);

// For some reason, we can't store the paths in a list variable and then
// iterate. It sounds crazy but it just won't work. Hence, we manually unroll
// the would-be loop instead.

var context;
context = require.context('./components', true, /^((?!image).)*\.js$/);
context.keys().forEach(context);

context = require.context('./test', true, /^((?!image).)*\.js$/);
context.keys().forEach(context);

context = require.context('./util', true, /^((?!image).)*\.js$/);
context.keys().forEach(context);

context = require.context('./VisComponent', true, /^((?!image).)*\.js$/);
context.keys().forEach(context);

Make unit tests work with restructured source code


module("Hide department children", {
  setup: function() {
    this.$departments = $(
      '
'
    +   '
'
    +     '
'
    +       '
child content
'
    +     '
'
    +   '
'
    + '
');

    $('#qunit-fixture').append(this.$departments);
    this.oldWindowHash = window.location.hash;
    window.location.hash = '#department-name';
  },
  teardown: function(){
    window.location.hash = this.oldWindowHash;
  }
});

test("should create toggle link before department list", function() {
  GOVUK.hideDepartmentChildren.init();
  equals(this.$departments.find('.view-all').length, 1);
});

test("should toggle class when clicking view all link", function() {
  GOVUK.hideDepartmentChildren.init();

  ok(this.$departments.find('.department').hasClass('js-hiding-children'));
  this.$departments.find('.view-all').click();
  ok(!this.$departments.find('.department').hasClass('js-hiding-children'));
});

test("should not toggle class of department with id in window hash", function(){
  this.$departments.find('.child-organisations').append('');

  GOVUK.hideDepartmentChildren.init();

  ok(!this.$departments.find('.department').hasClass('js-hiding-children'));
});

module("Hide department children", {
  setup: function() {
    this.$departments = $(
      '
'
    +   '
'
    +     '
'
    +       '
child content
'
    +     '
'
    +   '
'
    + '
');

    $('#qunit-fixture').append(this.$departments);
    this.oldWindowHash = window.location.hash;
    window.location.hash = '#department-name';
  },
  teardown: function(){
    window.location.hash = this.oldWindowHash;
  }
});

test("should create toggle link before department list", function() {
  GOVUK.hideDepartmentChildren.init();
  equals(this.$departments.find('.view-all').length, 1);
});

test("should toggle class when clicking view all link", function() {
  GOVUK.hideDepartmentChildren.init();

  ok(this.$departments.find('.department').hasClass('js-hiding-children'));
  this.$departments.find('.view-all').click();
  ok(!this.$departments.find('.department').hasClass('js-hiding-children'));
});

test("should not toggle class of department with id in window hash", function(){
  this.$departments.find('.organisations-box').append('');

  GOVUK.hideDepartmentChildren.init();

  ok(!this.$departments.find('.department').hasClass('js-hiding-children'));
});

Update javascript test for new structure


// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['ng-scenario'],

    // list of files / patterns to load in the browser
    files: [
      'test/e2e/**/*.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false

    // Uncomment the following lines if you are using grunt's server to run the tests
    // proxies: {
    //   '/': 'http://localhost:9000/'
    // },
    // URL root prevent conflicts with the site root
    // urlRoot: '_karma_'
  });
};

// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function(config) {
  'use strict';
  config.set({
    // base path, that will be used to resolve files and exclude
    basePath: '',

    // testing framework to use (jasmine/mocha/qunit/...)
    frameworks: ['ng-scenario'],

    // list of files / patterns to load in the browser
    files: [
      'test/e2e/**/*.js'
    ],

    // list of files / patterns to exclude
    exclude: [],

    // web server port
    port: 8080,

    // level of logging
    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // Start these browsers, currently available:
    // - Chrome
    // - ChromeCanary
    // - Firefox
    // - Opera
    // - Safari (only Mac)
    // - PhantomJS
    // - IE (only Windows)
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, it capture browsers, run tests and exit
    singleRun: false

    // Uncomment the following lines if you are using grunt's server to run the tests
    // proxies: {
    //   '/': 'http://localhost:9000/'
    // },
    // URL root prevent conflicts with the site root
    // urlRoot: '_karma_'
  });
};

Use strict mode in karma E2E test congig file


/**
 * @flow
 */
export const DEFAULT_PATH = '/dispatch/';
export function DEFAULT_ENCODE(_: string, data: Object): string {
	return JSON.stringify(data);
}
export function DEFAULT_DECODE(_: string, data: string): Object {
	return JSON.parse(data);
}

export default {
	path: DEFAULT_PATH,
	encode: DEFAULT_ENCODE,
	decode: DEFAULT_DECODE
};

/**
 * @flow
 */
export const DEFAULT_PATH = '/dispatch/';
export function DEFAULT_ENCODE(_: string, data: Object): string {
	return JSON.stringify(data);
}
export function DEFAULT_DECODE(_: string, data: string): Object {
	return JSON.parse(data);
}

export default {
	path: DEFAULT_PATH,
	encodeState: DEFAULT_ENCODE,
	decodeState: DEFAULT_DECODE
};

Fix Default Encode/Decode State Functions


import React, { Component } from 'react';
import { Grid, Col, Form, Item, Label, Input, Card, CardItem } from 'native-base';

import CardHeader from '../../common/card-header/cardHeader';

// Temporary constants. These will be moved and implemented in another way in the future!
const DATE_STRING = 'Datum';
const TIME_STRING = 'Tid';

// Card with input fields to specify when the job should be started.
export default class CalendarCard extends Component {
  render() {
    return (
      <Card>
        <CardHeader icon="calendar" title="Datum" subtitle="När ska uppdraget utföras?" />
        <Form >
          <Grid>
            <Col>
              <Item floatingLabel>
                {DATE_STRING}
                <Input />
              </Item>
            </Col>
            <Col>
              <Item floatingLabel>
                {TIME_STRING}
                <Input />
              </Item>
            </Col>
          </Grid>
        </Form>
        <CardItem footer />
      </Card>
    );
  }
}

import React, { Component } from 'react';
import { Grid, Col, Form, Item, Label, Input, Card, CardItem } from 'native-base';

import CardHeader from '../../common/card-header/cardHeader';

// Temporary constants. These will be moved and implemented in another way in the future!
const DATE_STRING = 'Datum';
const TIME_STRING = 'Tid';

// Card with input fields to specify when the job should be started.
export default class CalendarCard extends Component {
  render() {
    return (
      <Card>
        <CardHeader icon="calendar" title="Datum" subtitle="När ska uppdraget utföras?" />
        <Form >
          <Grid>
            <Col>
              <Item floatingLabel>
                {DATE_STRING}
                <Input keyboardType="numeric" />
              </Item>
            </Col>
            <Col>
              <Item floatingLabel>
                {TIME_STRING}
                <Input keyboardType="numeric" />
              </Item>
            </Col>
          </Grid>
        </Form>
        <CardItem footer />
      </Card>
    );
  }
}

Make calendar input fields numeric


"use strict";

// src/app.js

let Post = {
  findAll() {
    return new Promise( (resolve, reject) => {
      resolve("OK posts!");
    });
  }
};

let ui = {
  renderPosts(posts) {
    console.log(posts);
  }
};

Post.findAll().then(ui.renderPosts);

"use strict";

// src/app.js

let Post = {
  findAll() {
    return new Promise( (resolve, reject) => {
      let uri = "http://localhost:3000/posts";
      let request = new XMLHttpRequest();

      request.open("GET", uri, true);
      request.onload = () => {
        let status = request.status;

        if (status >= 200 && status < 400) {
          resolve(JSON.parse(request.response));
        }
      };

      request.onerror = () => {
        reject(new Error("Something went wrong on the API"));
      }

      request.send();
    });
  }
};

let ui = {
  renderPosts(posts) {
    console.log(posts);
  }
};

Post.findAll()
.then(ui.renderPosts)
.catch( (error) => console.error("Error: ", error));

Write findApp method for Post. Catch the error


import Rollup from "../pages/rollup.page"

describe("Preview rollup page", () => {
  beforeEach("Open the rollup page", () => {
    Rollup.open()
    Rollup.rollup.waitForExist()
    Rollup.rollup.waitForDisplayed()
  })

  describe("When clicking the email preview button, the daily rollup should be generated", () => {
    it("Should show the correct header for the rollup", () => {
      Rollup.emailButton.waitForDisplayed()
      Rollup.emailButton.click()

      Rollup.emailPreviewButton.waitForDisplayed()
      Rollup.emailPreviewButton.click()

      const currentHandle = browser.getWindowHandle()
      const handles = browser.getWindowHandles()
      // Find new window handle; NOTE: https://w3c.github.io/webdriver/#dfn-get-window-handles
      // "The order in which the window handles are returned is arbitrary."
      let switchHandle
      while ((switchHandle = handles.pop()) === currentHandle) {}
      browser.switchToWindow(switchHandle)

      browser.waitUntil(
        () => {
          return (
            $("p:nth-of-type(1) i").getText() ===
            "Classification: DEMO USE ONLY"
          )
        },
        3000,
        "Expected classification"
      )
      browser.closeWindow()
    })
  })
})

import Rollup from "../pages/rollup.page"

describe("Preview rollup page", () => {
  beforeEach("Open the rollup page", () => {
    Rollup.open()
    Rollup.rollup.waitForExist()
    Rollup.rollup.waitForDisplayed()
  })

  describe("When clicking the email preview button, the daily rollup should be generated", () => {
    it("Should show the correct header for the rollup", () => {
      Rollup.emailButton.waitForDisplayed()
      Rollup.emailButton.click()

      const currentHandle = browser.getWindowHandle()
      Rollup.emailPreviewButton.waitForExist()
      Rollup.emailPreviewButton.waitForDisplayed()
      browser.pause(500)
      Rollup.emailPreviewButton.click()

      browser.pause(500)
      const handles = browser.getWindowHandles()

      // Find new window handle; NOTE: https://w3c.github.io/webdriver/#dfn-get-window-handles
      // "The order in which the window handles are returned is arbitrary."
      let switchHandle
      while ((switchHandle = handles.pop()) === currentHandle) {}
      browser.switchToWindow(switchHandle)

      browser.waitUntil(
        () => {
          return (
            $("p:nth-of-type(1) i").getText() ===
            "Classification: DEMO USE ONLY"
          )
        },
        3000,
        "Expected classification"
      )
      browser.closeWindow()
    })
  })
})

Make test slightly more robust with latest Chrome


import {describe, CourseSelect, Calendar, ReadingBuilder} from './helpers';
import {expect} from 'chai';

const TEACHER_USERNAME = 'teacher01';

describe('Draft Tests', function() {

  this.it('Shows Validation Error when saving a blank Reading, Homework, and External (idempotent)', async function() {
    this.addTimeout(30);

    const title = this.freshId();
    var y = await this.login(TEACHER_USERNAME);
    console.log('Done logging in. No need for promises');

    // Go to the 1st courses dashboard
    const courseSelect = new CourseSelect(this)
    var x = await courseSelect.goTo(this, 'ANY');
    console.log('Done selecting a course. No need for promises');
  });
});

import {describe, CourseSelect, Calendar, ReadingBuilder} from './helpers';
import {expect} from 'chai';

const TEACHER_USERNAME = 'teacher01';

describe('Simple Draft Tests', function() {

  this.it('Shows Validation Error when saving a blank Reading, Homework, and External (idempotent)', async function() {
    this.addTimeout(30);

    const title = this.freshId();
    var y = await this.login(TEACHER_USERNAME);
    console.log('Done logging in. No need for promises');

    // Go to the 1st courses dashboard
    const courseSelect = new CourseSelect(this)
    var x = await courseSelect.goTo(this, 'ANY');
    console.log('Done selecting a course. No need for promises');
  });
});

Rename tests to prevent confusion with Draft


var assert = require("assert");
var {defer, promises} = require("ringo/promise");

exports.testPromiseList = function() {
    var d1 = defer(), d2 = defer(), d3 = defer();
    var l = promises(d1.promise, d2.promise, d3); // promiseList should convert d3 to promise
    l.then(function(result) {
        assert.deepEqual(result, [{value: "ok"}, {value: 1}, {error: "error"}]);
    }, function(error) {
        assert.fail("promiseList called error callback");
    });
    d2.resolve(1);
    d3.resolve("error", true);
    d1.resolve("ok");
};

// start the test runner if we're called directly from command line
if (require.main == module.id) {
    system.exit(require('test').run(exports));
}

var assert = require("assert");
var {defer, promises} = require("ringo/promise");

exports.testPromiseList = function() {
    var d1 = defer(), d2 = defer(), d3 = defer(), done = defer();
    var l = promises(d1.promise, d2.promise, d3); // promiseList should convert d3 to promise
    l.then(function(result) {
        done.resolve(result);
    }, function(error) {
        done.resolve("promises called error callback", true);
    });
    d2.resolve(1);
    d3.resolve("error", true);
    d1.resolve("ok");
    // make sure promises resovle via wait()
    var result = l.wait();
    assert.deepEqual(result, [{value: "ok"}, {value: 1}, {error: "error"}]);
    // make sure promises resolve via callback
    result = done.promise.wait();
    assert.deepEqual(result, [{value: "ok"}, {value: 1}, {error: "error"}]);
};

// start the test runner if we're called directly from command line
if (require.main == module.id) {
    system.exit(require('test').run(exports));
}

Make sure promises test assertions are run by test runner thread


#!/usr/bin/env node

/*          _ _              _
 *  ___ ___| | |_ ___   ___ | |___
 * / __/ __| | __/ _ \ / _ \| / __|
 * \__ \__ \ | || (_) | (_) | \__ \
 * |___/___/_|\__\___/ \___/|_|___/
 *
 * Copyright (c) 2015 by Jonas Friedmann. This source is
 * subject to the Microsoft Permissive License. Please see
 * the LICENSE file for more information. All Rights Reserved.
 */

var cmdr = require('commander');

// Initiate the commander.js argument setup
cmdr
  .version('1.1.0')
  .command('chain', 'attempt to fix incomplete certificate chain')
  .command('csr <query>', 'decode certificate request information')
  .command('info <hostname>', 'display TLS information for given hostname')
  .parse(process.argv);

#!/usr/bin/env node

/*          _ _              _
 *  ___ ___| | |_ ___   ___ | |___
 * / __/ __| | __/ _ \ / _ \| / __|
 * \__ \__ \ | || (_) | (_) | \__ \
 * |___/___/_|\__\___/ \___/|_|___/
 *
 * Copyright (c) 2015 by Jonas Friedmann. This source is
 * subject to the Microsoft Permissive License. Please see
 * the LICENSE file for more information. All Rights Reserved.
 */

var cmdr = require('commander');

// Initiate the commander.js argument setup
cmdr
  .version('1.1.0')
  .command('chain', 'attempt to fix incomplete certificate chain')
  .command('csr <query>', 'decode certificate request information')
  .command('info <hostname> [port]', 'display TLS information for given hostname')
  .parse(process.argv);

Add notice for optional port


Emoji = {};

Emoji.baseImagePath = '/packages/davidfrancisco_twemoji/img/';

Emoji.convert = function (str) {
    if (typeof str !== 'string') {
        return '';
    }

    return str.replace(/:[\+\-a-z0-9_]+:/gi, function(match) {
        var imgName = match.slice(1, -1),
            path = Emoji.baseImagePath + imgName + '.png';
        return '<img class="emoji" title="' + match + '" src="' + path + '"/>';
    });
};

// borrowed code from https://github.com/meteor/meteor/blob/devel/packages/showdown/template-integration.js
if (Package.templating) {
  var Template = Package.templating.Template;
  var Blaze = Package.blaze.Blaze; // implied by `templating`
  var HTML = Package.htmljs.HTML; // implied by `blaze`

  Blaze.Template.registerHelper('emoji', new Template('emoji', function () {
    var view = this,
        content = '';

    if (view.templateContentBlock) {
      // this is for the block usage eg: {{#emoji}}:smile:{{/emoji}}
      content = Blaze._toText(view.templateContentBlock, HTML.TEXTMODE.STRING);
    }
    else {
      // this is for the direct usage eg: {{> emoji ':blush:'}}
      content = view.parentView.dataVar.get();
    }
    return HTML.Raw(Emoji.convert(content));
  }));
}

Emoji = {};

Emoji.baseImagePath = '/packages/davidfrancisco_twemoji/img/';

Emoji.convert = function (str) {
    if (typeof str !== 'string') {
        return '';
    }

    return str.replace(/:[\+\-a-z0-9_]+:/gi, function(match) {
        var imgName = match.slice(1, -1),
            path = Emoji.baseImagePath + imgName + '.png';
        return '<img class="emoji" alt="' + match + '" src="' + path + '"/>';
    });
};

// borrowed code from https://github.com/meteor/meteor/blob/devel/packages/showdown/template-integration.js
if (Package.templating) {
  var Template = Package.templating.Template;
  var Blaze = Package.blaze.Blaze; // implied by `templating`
  var HTML = Package.htmljs.HTML; // implied by `blaze`

  Blaze.Template.registerHelper('emoji', new Template('emoji', function () {
    var view = this,
        content = '';

    if (view.templateContentBlock) {
      // this is for the block usage eg: {{#emoji}}:smile:{{/emoji}}
      content = Blaze._toText(view.templateContentBlock, HTML.TEXTMODE.STRING);
    }
    else {
      // this is for the direct usage eg: {{> emoji ':blush:'}}
      content = view.parentView.dataVar.get();
    }
    return HTML.Raw(Emoji.convert(content));
  }));
}

Set required alt attr instead of title for <img>s