import express from 'express';
import bodyParser from 'body-parser';
import favicon from 'serve-favicon';
import path from 'path';
import helmet from 'helmet';
import fs from 'fs';
import webpackCommonConfig from '../../webpack/config';
import envConfig from '../config';

const app = express();
const outputPath = webpackCommonConfig.paths.output;
const outputServerPath = webpackCommonConfig.paths.outputServer;

const serverRenderer = require(path.join(outputServerPath, 'prod.render.js')).default;
const clientStats = require(path.join(outputPath, 'stats.json'));
const bundleManifest = require(path.join(outputPath, 'bundle.json'));
const mainCss = path.join(outputPath, bundleManifest['main.css']);

app.use(helmet());
app.use(bodyParser.json());
app.use(favicon(webpackCommonConfig.paths.favicon));
app.set('view engine', 'ejs');
app.set('views', path.resolve(__dirname, 'views'));

let isBuilt = false;

const done = () =>
!isBuilt &&
app.listen(3000, () => {
  isBuilt = true;
  console.log("\x1b[35m", 'BUILD COMPLETE -- Listening @ :3000');
});

fs.readFile(mainCss, 'utf8', (err, data) => {
  if (err) throw err;
  app.use(serverRenderer({ clientStats, options: {
    criticalCssRaw: data,
    envConfig,
    isProd: true
  } }));
  done();
});

import express from 'express';
import bodyParser from 'body-parser';
import favicon from 'serve-favicon';
import path from 'path';
import helmet from 'helmet';
import fs from 'fs';
import webpackCommonConfig from '../../webpack/config';
import envConfig from '../config';

const app = express();
const outputPath = webpackCommonConfig.paths.output;
const outputServerPath = webpackCommonConfig.paths.outputServer;

const serverRenderer = require(path.join(outputServerPath, 'prod.render.js')).default;
const clientStats = require(path.join(outputPath, 'stats.json'));
const bundleManifest = require(path.join(outputPath, 'bundle.json'));
const mainCss = path.join(outputPath, bundleManifest['main.css']);

app.use(helmet());
app.use(bodyParser.json());
app.use(favicon(webpackCommonConfig.paths.favicon));
app.set('view engine', 'ejs');
app.set('views', path.resolve(__dirname, 'views'));

fs.readFile(mainCss, 'utf8', (err, data) => {
  if (err) throw err;
  app.use(serverRenderer({ clientStats, options: {
    criticalCssRaw: data,
    envConfig,
    isProd: true
  } }));

  app.listen(3000, () => {
    console.log("\x1b[35m", 'BUILD COMPLETE -- Listening @ :3000');
  });
});

Update server: remove process.env read


const { clipboard, ipcRenderer } = require('electron');
const beautify_js = require('js-beautify');
const lang = require('language-classifier');

ipcRenderer.on('shortcut-hit', (event, arg) => {
  console.log(arg);
})

function formatClipboard() {
  let clipboard_contents = clipboard.readText(),
      language = lang(clipboard_contents),
      output = '';

  if (clipboard_contents.length === 0) return;

  switch(language) {
    case 'css':
      output = beautify_js.css(clipboard_contents);
      break;
    case 'html':
      output = beautify_js.html(clipboard_contents);
      break;
    case 'javascript':
    default:
      output = beautify_js.js_beautify(clipboard_contents);
      break;
  }

  clipboard.writeText(output);
}

const { clipboard, ipcRenderer } = require('electron');
const beautify_js = require('js-beautify');
const lang = require('language-classifier');

ipcRenderer.on('shortcut-hit', (event, arg) => {
  console.log(arg);
})

function formatClipboard() {
  let clipboard_contents = clipboard.readText(),
      language = lang(clipboard_contents),
      output = '';

  if (clipboard_contents.length === 0) return;

  switch(language) {
    case 'css':
      output = beautify_js.css(clipboard_contents);
      break;
    case 'html':
      output = beautify_js.html(clipboard_contents);
      break;
    case 'javascript':
    default:
      output = beautify_js.js_beautify(clipboard_contents);
      break;
  }

  clipboard.clear();
  clipboard.writeText(output);
}

Clear clipboard before writing... just in case


/*!
 * Top-Level Bootstrap
 */
require(

    {
        priority: [ 'require-config' ]
    },

    [ 'mediators/index' ],

    function() {

        var log = document.getElementById('log'),
	        msg = log.innerHTML += '
boot-index.js factory has been executed
';

	    // Debug
        console.log('#boot-index.js');
        console.log('-------------------');
        console.log(arguments);
        console.log('');
    }
);

/*!
 * Top-Level Bootstrap
 */
require(

    {
        priority: [ 'require-config' ] // Set paths and jQuery configuration before any other scripts are loaded
    },

    [ 'mediators/index' ],

    function() {

        var log = document.getElementById('log'),
	        msg = log.innerHTML += '
boot-index.js factory has been executed
';

	    // Debug
        console.log('#boot-index.js');
        console.log('-------------------');
        console.log(arguments);
        console.log('');
    }
);

Comment on why `priority` option is used.


import GeorgeForeman from '../../../utils/george-foreman';
import { module, test } from 'qunit';

module('Unit | Utility | george foreman');

// Replace this with your real tests.
test('it works', function(assert) {
  var result = new GeorgeForeman();
  assert.ok(result);
});

import GeorgeForeman from '../../../utils/george-foreman';
import { module, test } from 'qunit';

module('Unit | Utility | george foreman');

// Replace this with your real tests.
test('it correctly names the second George Foreman', (assert) => {
  var first = new GeorgeForeman();
  var second = new GeorgeForeman();
  assert.equal(second.name(), "George Foreman II");
});

Add test for George Foreman II


import PropTypes from 'prop-types';
import React from 'react';
import Relay from 'react-relay';

import QueryAggregator from './QueryAggregator';

const propTypes = {
  location: PropTypes.object.isRequired,
  children: PropTypes.element.isRequired,
};

const childContextTypes = {
  queryAggregator: React.PropTypes.object.isRequired,
};

class RelayRouterContext extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.queryAggregator = new QueryAggregator(props);
  }

  getChildContext() {
    return {
      queryAggregator: this.queryAggregator,
    };
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.location === this.props.location) {
      return;
    }

    this.queryAggregator.updateQueryConfig(nextProps);
  }

  renderCallback = (renderArgs) => {
    this.queryAggregator.setRenderArgs(renderArgs);
    return this.props.children;
  };

  render() {
    return (
      <Relay.Renderer
        {...this.props}
        Container={this.queryAggregator}
        render={this.renderCallback}
        queryConfig={this.queryAggregator.queryConfig}
      />
    );
  }
}

RelayRouterContext.propTypes = propTypes;
RelayRouterContext.childContextTypes = childContextTypes;

export default RelayRouterContext;

import PropTypes from 'prop-types';
import React from 'react';
import Relay from 'react-relay';

import QueryAggregator from './QueryAggregator';

const propTypes = {
  location: PropTypes.object.isRequired,
  children: PropTypes.element.isRequired,
};

const childContextTypes = {
  queryAggregator: PropTypes.object.isRequired,
};

class RelayRouterContext extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.queryAggregator = new QueryAggregator(props);
  }

  getChildContext() {
    return {
      queryAggregator: this.queryAggregator,
    };
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.location === this.props.location) {
      return;
    }

    this.queryAggregator.updateQueryConfig(nextProps);
  }

  renderCallback = (renderArgs) => {
    this.queryAggregator.setRenderArgs(renderArgs);
    return this.props.children;
  };

  render() {
    return (
      <Relay.Renderer
        {...this.props}
        Container={this.queryAggregator}
        render={this.renderCallback}
        queryConfig={this.queryAggregator.queryConfig}
      />
    );
  }
}

RelayRouterContext.propTypes = propTypes;
RelayRouterContext.childContextTypes = childContextTypes;

export default RelayRouterContext;

Use `PropTypes` instead of `React.PropTypes`


'use strict';

var renderDocument = require('./_business-process-document');

exports._parent = require('./user-base');
exports._match = 'document';

exports['sub-main'] = {
	class: { content: true, 'user-forms': true },
	content: function () {
		renderDocument(this.document);
	}
};

'use strict';

var renderDocument = require('./_business-process-document')
  , _ = require('../../i18n').bind('View: Official');

exports._parent = require('./user-base');
exports._match = 'document';

exports['sub-main'] = {
	class: { content: true, 'user-forms': true },
	content: function () {
		var rejectReasonDescription = insert(_if(eq(this.document.owner._status, 'invalid'),
			div({ class: 'info-main' },
				p(_('This document was rejected for following reason:'), ' '),
				p(this.document.owner.rejectReasonMemo))));
		renderDocument(this.document, rejectReasonDescription);
	}
};

Add reject reason to doc view


import React from 'react';
import firebase from 'firebase';

export default () => {
  const handleSignIn = async () => {
    const provider = new firebase.auth.GoogleAuthProvider();
    try {
      await firebase.auth().signInWithPopup(provider);
    } catch (error) {
      console.error(error);
      alert('failed to sign in.')
    }
  }

  return (
    <button onClick={handleSignIn}> Sign in with Google </button>
  );
}

import React from 'react';
import firebase from 'firebase';

export default class SignIn extends React.Component {
  async handleSignIn() {
    const provider = new firebase.auth.GoogleAuthProvider();
    try {
      await firebase.auth().signInWithPopup(provider);
    } catch (error) {
      console.error(error);
      alert('failed to sign in.')
    }
  }

  render() {
    return (
      <button onClick={this.handleSignIn.bind(this)}> Sign in with Google </button>
    );
  }
}

Revert "refactor sign in button"


// Turn on a global notification. Set this to `true` to show
// the message in NotificationComponent to all users.
export const showGlobalNotification = () => true

export const isAnonymousUserSignInEnabled = () => false

export const isVariousAdSizesEnabled = () => false

export const isSearchPageEnabled = () =>
  process.env.REACT_APP_FEATURE_FLAG_SEARCH_PAGE_ENABLED === 'true'

// Turn on a global notification. Set this to `true` to show
// the message in NotificationComponent to all users.
export const showGlobalNotification = () => false

export const isAnonymousUserSignInEnabled = () => false

export const isVariousAdSizesEnabled = () => false

export const isSearchPageEnabled = () =>
  process.env.REACT_APP_FEATURE_FLAG_SEARCH_PAGE_ENABLED === 'true'

Stop showing the "January Charity Spotlight" notification


module.exports = {
  verbose: true,
  styleLoader: "style-loader!css-loader!sass-loader",

  scripts: {
    'transition': true,
    'alert': true,
    'button': true,
    'carousel': true,
    'collapse': true,
    'dropdown': true,
    'modal': true,
    'tooltip': true,
    'popover': true,
    'scrollspy': true,
    'tab': true,
    'affix': true
  },
  styles: {
    "mixins": true,

    "normalize": true,
    "print": true,

    "scaffolding": true,
    "type": true,
    "code": true,
    "grid": true,
    "tables": true,
    "forms": true,
    "buttons": true,

    "component-animations": true,
    "glyphicons": true,
    "dropdowns": true,
    "button-groups": true,
    "input-groups": true,
    "navs": true,
    "navbar": true,
    "breadcrumbs": true,
    "pagination": true,
    "pager": true,
    "labels": true,
    "badges": true,
    "jumbotron": true,
    "thumbnails": true,
    "alerts": true,
    "progress-bars": true,
    "media": true,
    "list-group": true,
    "panels": true,
    "wells": true,
    "close": true,

    "modals": true,
    "tooltip": true,
    "popovers": true,
    "carousel": true,

    "utilities": true,
    "responsive-utilities": true
  }
};

module.exports = {
  verbose: false,
  styleLoader: "style-loader!css-loader!sass-loader",

  scripts: {
    'transition': true,
    'alert': true,
    'button': true,
    'carousel': true,
    'collapse': true,
    'dropdown': true,
    'modal': true,
    'tooltip': true,
    'popover': true,
    'scrollspy': true,
    'tab': true,
    'affix': true
  },
  styles: {
    "mixins": true,

    "normalize": true,
    "print": true,

    "scaffolding": true,
    "type": true,
    "code": true,
    "grid": true,
    "tables": true,
    "forms": true,
    "buttons": true,

    "component-animations": true,
    "glyphicons": true,
    "dropdowns": true,
    "button-groups": true,
    "input-groups": true,
    "navs": true,
    "navbar": true,
    "breadcrumbs": true,
    "pagination": true,
    "pager": true,
    "labels": true,
    "badges": true,
    "jumbotron": true,
    "thumbnails": true,
    "alerts": true,
    "progress-bars": true,
    "media": true,
    "list-group": true,
    "panels": true,
    "wells": true,
    "close": true,

    "modals": true,
    "tooltip": true,
    "popovers": true,
    "carousel": true,

    "utilities": true,
    "responsive-utilities": true
  }
};

Set default install to be not-verbose


const { get } = require('lodash')
const config = require('../../../config')
const { authorisedRequest } = require('../../lib/authorised-request')

function getAdvisers (token) {
  return authorisedRequest(token, `${config.apiRoot}/adviser/?limit=100000&offset=0`)
    .then(response => {
      const results = response.results.filter(adviser => get(adviser, 'name', '').trim().length)

      return {
        results,
        count: results.length,
      }
    })
}

function getAdviser (token, id) {
  return authorisedRequest(token, `${config.apiRoot}/adviser/${id}/`)
}

async function fetchAdviserSearchResults (token, params) {
  const url = `${config.apiRoot}/adviser/?autocomplete=${params.term}&is_active=${params.is_active}`
  const adviserResults = await authorisedRequest(token, { url })
  return adviserResults.results
}

module.exports = {
  getAdvisers,
  getAdviser,
  fetchAdviserSearchResults,
}

const { get } = require('lodash')
const config = require('../../../config')
const { authorisedRequest } = require('../../lib/authorised-request')

function getAdvisers (token) {
  return authorisedRequest(token, `${config.apiRoot}/adviser/?limit=100000&offset=0`)
    .then(response => {
      const results = response.results.filter(adviser => get(adviser, 'name', '').trim().length)

      return {
        results,
        count: results.length,
      }
    })
}

function getAdviser (token, id) {
  return authorisedRequest(token, `${config.apiRoot}/adviser/${id}/`)
}

async function fetchAdviserSearchResults (token, params) {
  const url = `${config.apiRoot}/adviser/?autocomplete=${params.term}`
  const adviserResults = await authorisedRequest(token, { url })
  return adviserResults.results
}

module.exports = {
  getAdvisers,
  getAdviser,
  fetchAdviserSearchResults,
}

Remove the param to filter inactive advisers


import React from 'react'
import './Footer.scss'

export const Footer = () => (

          About OONI

          Legal

          Help

          Explore data

)

export default Footer

import React from 'react'
import './Footer.scss'

export const Footer = () => (

          About OONI

          Data policy

          Help

          Explore data

)

export default Footer

Rename Legal to Data policy


/* eslint import/no-extraneous-dependencies: [2, { "devDependencies": true }] */

import expect from 'expect';

import { map, toUpper } from 'ramda';
import React from 'react';
import { shallow } from 'enzyme';

import createElement, { mapElementPropsWith } from './createElement';

describe('createElement', () => {
  it('supports omitting props', () => {
    const expected = React.createElement(
      'div', {}, React.createElement('span', {}, 't'), 'est'
    );
    const actual = createElement('div', createElement('span', 't'), 'est');
    expect(actual).toEqual(expected);
  });

  it('handles undefined props properly', () => {
    const expected = React.createElement('div', void 0);
    const actual = createElement('div', void 0);
    expect(actual).toEqual(expected);
  });

  it('lets the innerHtml prop insert unescaped HTML into the element', () => {
    const expected = '
Dangerous HTML!
';
    const actual = shallow(
      createElement('div', { innerHtml: 'Dangerous HTML!' })
    ).html();
    expect(actual).toBe(expected);
  });
});

describe('mapElementPropsWith', () => {
  it('takes a props mapper, and returns a createElement function', () => {
    const fn = mapElementPropsWith(map(toUpper));
    const expected = React.createElement(
      'div', { className: 'BAR', id: 'FOO' }
    );
    const actual = fn('div', { className: 'bar', id: 'foo' });
    expect(actual).toEqual(expected);
  });
});

/* eslint import/no-extraneous-dependencies: [2, { "devDependencies": true }] */

import expect from 'expect';

import { map, toUpper } from 'ramda';
import React from 'react';
import { shallow } from 'enzyme';

import createElement, { mapElementPropsWith } from './createElement';

describe('createElement', () => {
  it('supports omitting props', () => {
    const expected = React.createElement(
      'div', {}, React.createElement('span', {}, 't'), 'est'
    );
    const actual = createElement('div', createElement('span', 't'), 'est');
    expect(actual).toEqual(expected);
  });

  it('handles undefined props properly', () => {
    const expected = React.createElement('div', undefined);
    const actual = createElement('div', undefined);
    expect(actual).toEqual(expected);
  });

  it('lets the innerHtml prop insert unescaped HTML into the element', () => {
    const expected = '
Dangerous HTML!
';
    const actual = shallow(
      createElement('div', { innerHtml: 'Dangerous HTML!' })
    ).html();
    expect(actual).toBe(expected);
  });
});

describe('mapElementPropsWith', () => {
  it('takes a props mapper, and returns a createElement function', () => {
    const fn = mapElementPropsWith(map(toUpper));
    const expected = React.createElement(
      'div', { className: 'BAR', id: 'FOO' }
    );
    const actual = fn('div', { className: 'bar', id: 'foo' });
    expect(actual).toEqual(expected);
  });
});

Replace void 0 with undefined


INSERT INTO users (name, email, password) VALUES ('A', 'a@a.com', 'a'), ('B', 'b@b.com', 'b'), ('C', 'c@c.com', 'c')

INSERT INTO users (name, email, password, current_city) VALUES ('A', 'a@a.com', 'a', 'Wellington, NZ'), ('B', 'b@b.com', 'b', 'Oakland, CA'), ('C', 'c@c.com', 'c', 'Cambridge, England')

Update seed-data, include user's city


var genMaze = function(nodeSet) {
	if(nodeSet.length == 0) {
		return;
	}

	var current = nodeSet.shift();
	var neighbors = current.neighbors();

	for(n in neighbors) {
		if(Math.random() < 0.5 || neighbors[n].explored) {
			continue;
		}

		nodeSet.push(neighbors[n]);
		neighbors[n].explored = true;

		var split = n.split('-')
		split[1]? current.openWall(split[1], split[0]) : current.openWall(split[0])
	}

	setTimeout(function(){ genMaze(nodeSet); }, 10);
};

$(function(){
	$(".hexMaze").each(function() {
		var g = new HexGrid(25);
		$(this).html(g.html);

		genMaze([g.find(12, 12)]);
	});
});

var genMaze = function(nodeSet) {
	if(nodeSet.length == 0) {
		return;
	}

	var current = nodeSet.pop();
	var neighbors = current.neighbors();

	for(n in neighbors) {
		if(Math.random() < 0.5 || neighbors[n].explored) {
			continue;
		}

		nodeSet.push(neighbors[n]);
		neighbors[n].explored = true;

		var split = n.split('-')
		split[1]? current.openWall(split[1], split[0]) : current.openWall(split[0])
	}

	setTimeout(function(){ genMaze(nodeSet); }, 10);
};

$(function(){
	$(".hexMaze").each(function() {
		var g = new HexGrid(25);
		$(this).html(g.html);

		genMaze([g.find(12, 12)]);
	});
});

Switch to DFS for harder mazes


'use strict';

/**
 * @ngdoc service
 * @name dormManagementToolApp.userService
 * @description
 * # userService
 * Service in the dormManagementToolApp.
 */
angular.module('dormManagementToolApp')
  .service('UserService', function () {
    this.getName = function () {
      return JSON.parse(localStorage.getItem('user')).name;
    }
  });

'use strict';

/**
 * @ngdoc service
 * @name dormManagementToolApp.userService
 * @description
 * # userService
 * Service in the dormManagementToolApp.
 */
angular.module('dormManagementToolApp')
  .service('UserService', function () {
    this.getName = function () {
      return JSON.parse(localStorage.getItem('user')).name;
    };
    this.getId = function() {
      return JSON.parse(localStorage.getItem(('user')).id);
    }
  });

Add service getter for user id


var LostPassword = (function() {
    'use strict';

    var hiddenClass = 'invisible';

    function submitEmail() {
        var emailInput = $('#lp_email').val();

        if (emailInput === '') {
            $("#email_error").removeClass(hiddenClass);
        } else {
            BinarySocket.send({verify_email: emailInput, type: 'reset_password'});
        }
    }

    function onEmailInput(input) {
        if (input) {
            $("#email_error").addClass(hiddenClass);
        }
    }

    function lostPasswordWSHandler(msg) {
        var response = JSON.parse(msg.data);
        var type = response.msg_type;

        if (type === 'verify_email') {
            if (response.verify_email === 1) {
                load_with_pjax('reset_passwordws');
            } else if (response.error) {
                $("#email_error").removeClass(hiddenClass).text(text.localize('Invalid email format'));
            }
        }
    }

    function init() {
        $('#submit').click(function() {
            submitEmail();
        });

        $('#lp_email').change(function(ev) {
            onEmailInput(ev.target.value);
        });
    }

    return {
        lostPasswordWSHandler: lostPasswordWSHandler,
        init: init
    };
}());

var LostPassword = (function() {
    'use strict';

    var hiddenClass = 'invisible';

    function submitEmail() {
        var emailInput = $('#lp_email').val();

        if (emailInput === '') {
            $("#email_error").removeClass(hiddenClass);
        } else {
            BinarySocket.send({verify_email: emailInput, type: 'reset_password'});
        }
    }

    function onEmailInput(input) {
        if (input) {
            $("#email_error").addClass(hiddenClass);
        }
    }

    function lostPasswordWSHandler(msg) {
        var response = JSON.parse(msg.data);
        var type = response.msg_type;

        if (type === 'verify_email') {
            if (response.verify_email === 1) {
                load_with_pjax('reset_passwordws');
            } else if (response.error) {
                $("#email_error").removeClass(hiddenClass).text(text.localize('Invalid email format'));
            }
        }
    }

    function init() {
        $('#submit').click(function() {
            submitEmail();
        });

        $('#lp_email').keypress(function(ev) {
            if (ev.which === 13) {
                submitEmail();
            }
            onEmailInput(ev.target.value);
        });
    }

    return {
        lostPasswordWSHandler: lostPasswordWSHandler,
        init: init
    };
}());

Add enter key handling on email input field


// const webpack = require('webpack');

module.exports = (config) => {
  config.set({
    browsers: process.env.TRAVIS ? ['Chrome_travis_ci'] : ['Chrome'], // run in Chrome
    singleRun: true, // just run once by default
    frameworks: ['mocha', 'chai'], // use the mocha test framework
    files: [
      'tests.webpack.js', // just load this file
    ],
    preprocessors: {
      'tests.webpack.js': ['webpack', 'sourcemap'], // preprocess with webpack and our sourcemap loader
    },
    reporters: ['progress', 'coverage', 'coveralls'],
    webpack: { // kind of a copy of your webpack config
      devtool: 'inline-source-map', // just do inline source maps instead of the default
      module: {
        loaders: [
          { test: /\.js$/, loader: 'babel-loader' },
        ],
      },
    },
    coverageReporter: {
      check: {
        global: {
          statements: 11,
          branches: 0,
          functions: 0,
          lines: 11,
        },
      },
      reporters: [
        { type: 'lcov', dir: 'coverage/', subdir: '.' },
        { type: 'json', dir: 'coverage/', subdir: '.' },
        { type: 'text-summary' },
      ],
    },
    webpackMiddleware: { noInfo: true },
    colors: true,
    concurrency: Infinity,
    logLevel: config.LOG_DEBUG,
  });
};

// const webpack = require('webpack');

module.exports = (config) => {
  config.set({
    browsers: ['Chrome'], // run in Chrome
    singleRun: true, // just run once by default
    frameworks: ['mocha', 'chai'], // use the mocha test framework
    files: [
      'tests.webpack.js', // just load this file
    ],
    preprocessors: {
      'tests.webpack.js': ['webpack', 'sourcemap'], // preprocess with webpack and our sourcemap loader
    },
    reporters: ['progress', 'coverage', 'coveralls'],
    webpack: { // kind of a copy of your webpack config
      devtool: 'inline-source-map', // just do inline source maps instead of the default
      module: {
        loaders: [
          { test: /\.js$/, loader: 'babel-loader' },
        ],
      },
    },
    coverageReporter: {
      check: {
        global: {
          statements: 11,
          branches: 0,
          functions: 0,
          lines: 11,
        },
      },
      reporters: [
        { type: 'lcov', dir: 'coverage/', subdir: '.' },
        { type: 'json', dir: 'coverage/', subdir: '.' },
        { type: 'text-summary' },
      ],
    },
    webpackMiddleware: { noInfo: true },
    colors: true,
    concurrency: Infinity,
    logLevel: config.LOG_DEBUG,
  });
};

Fix Travis issue with Karma pt.2


const CONSTANTS = require('../../../data/constants');
const gameController = require('../../../../app/socket/controllers/game');
const knex = require('../../../../app/lib/db');
const chai = require('chai');
const chaiHttp = require('chai-http');

chai.use(chaiHttp);
const should = chai.should();

describe.only('Game Socket Functions: ', () => {
  before((done) => {
    // Run initial migrations and seed db
    knex.migrate.rollback()
      .then(() => {
        knex.migrate.latest()
          .then(() => {
            knex.seed.run()
              .then(() => done());
          });
      });
  });
  it('Should pass socket next_round', (done) => {
    gameController.nextRound(1, () => {
      setTimeout(() => {
        gameController.getGameById(1, (game) => {
          game.round_phase.should.equal(0);
          game.current_round.should.equal(1);
          done();
        });
      }, CONSTANTS.TIMEOUT);
    });
  });
});

const CONSTANTS = require('../../../data/constants');
const gameController = require('../../../../app/socket/controllers/game');
const knex = require('../../../../app/lib/db');
const chai = require('chai');
const chaiHttp = require('chai-http');

chai.use(chaiHttp);
const should = chai.should();

describe('Game Socket Functions: ', () => {
  before((done) => {
    // Run initial migrations and seed db
    knex.migrate.rollback()
      .then(() => {
        knex.migrate.latest()
          .then(() => {
            knex.seed.run()
              .then(() => done());
          });
      });
  });
  it('Should pass socket next_round', (done) => {
    gameController.nextRound(1, () => {
      setTimeout(() => {
        gameController.getGameById(1, (game) => {
          game.round_phase.should.equal(0);
          game.current_round.should.equal(1);
          done();
        });
      }, CONSTANTS.TIMEOUT);
    });
  });
});

Remove .only on socket game controller tests


var webpack = require('webpack');
// TODO: use BowerWebpackPlugin
// var BowerWebpackPlugin = require('bower-webpack-plugin');
var webpackCommon = require('./webpack.common.config.js');

// Put in separate file?
var webpackTestConfig = {
    devtool: 'inline-source-map',
    plugins: [
       new webpack.ResolverPlugin(
            new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('bower.json', ['main'])
        ),

        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
            'window.$': 'jquery'
        }),
    ],
    resolve: webpackCommon.resolve,
    module: {
        loaders: [
            // Assume test files are ES6
            {test: /\.test\.js$/, loader: 'babel-loader'},
        ]
    }

};

module.exports = function (config) {
    config.set({
        browsers: ['PhantomJS'],
        frameworks: ['mocha', 'sinon'],
        files: [
            // Only need to target one file, which will load all files in tests/ that
            // match *.test.js
            'website/static/js/tests/tests.webpack.js',
        ],
        reporters: ['spec'],
        preprocessors: {
            // add webpack as preprocessor
            'website/static/js/tests/tests.webpack.js': ['webpack', 'sourcemap'],
        },
        webpack: webpackTestConfig,
        webpackServer: {
            noInfo: true // don't spam the console
        }
    });
};

var webpack = require('webpack');
// TODO: use BowerWebpackPlugin
// var BowerWebpackPlugin = require('bower-webpack-plugin');
var webpackCommon = require('./webpack.common.config.js');

// Put in separate file?
var webpackTestConfig = {
    devtool: 'inline-source-map',
    plugins: [
       new webpack.ResolverPlugin(
            new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin('bower.json', ['main'])
        ),

        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
            'window.$': 'jquery'
        }),
    ],
    resolve: webpackCommon.resolve,
    externals: {'jquery': 'jQuery', 'jquery-ui': 'jQuery.ui'},
    module: {
        loaders: [
            // Assume test files are ES6
            {test: /\.test\.js$/, loader: 'babel-loader'},
        ]
    }
};

module.exports = function (config) {
    config.set({
        browsers: ['PhantomJS'],
        frameworks: ['mocha', 'sinon'],
        files: [
            'website/static/vendor/bower_components/jquery/dist/jquery.js',
            'website/static/vendor/bower_components/jquery-ui/ui/jquery-ui.js',
            // Only need to target one file, which will load all files in tests/ that
            // match *.test.js
            'website/static/js/tests/tests.webpack.js',
        ],
        reporters: ['spec'],
        preprocessors: {
            // add webpack as preprocessor
            'website/static/js/tests/tests.webpack.js': ['webpack', 'sourcemap'],
        },
        webpack: webpackTestConfig,
        webpackServer: {
            noInfo: true // don't spam the console
        }
    });
};

Make sure karma can access jquery-ui


module.exports = function(config){
    config.set({
        basePath : 'client',

        files : [
            'components/angular/angular.js',
            'components/angular-cookies/angular-cookies.js',
            'components/angular-mocks/angular-mocks.js',
            'components/angular-ui-router/release/angular-ui-router.js',
            'components/underscore/underscore.js',
            'js/**/*.js',
            'tests/unit/**/*.js'
        ],

        /*exclude : [
            'app/lib/angular/angular-loader.js',
            'app/lib/angular/*.min.js',
            'app/lib/angular/angular-scenario.js'
        ],*/

        autoWatch : true,

        frameworks: ['mocha', 'chai'],

        browsers : ['Chrome'],

        plugins : [
            'karma-junit-reporter',
            'karma-chrome-launcher',
            'karma-firefox-launcher',
            'karma-mocha',
            'karma-chai'
        ],

        junitReporter : {
            outputFile: 'test_out/unit.xml',
            suite: 'unit'
        }

    })
};

module.exports = function(config){
    config.set({
        basePath : 'client',

        files : [
            'components/angular/angular.js',
            'components/angular-cookies/angular-cookies.js',
            'components/angular-mocks/angular-mocks.js',
            'components/angular-ui-router/release/angular-ui-router.js',
            'js/**/*.js',
            'tests/unit/**/*.js'
        ],

        /*exclude : [
            'app/lib/angular/angular-loader.js',
            'app/lib/angular/*.min.js',
            'app/lib/angular/angular-scenario.js'
        ],*/

        autoWatch : true,

        frameworks: ['mocha', 'chai'],

        browsers : ['Chrome'],

        plugins : [
            'karma-junit-reporter',
            'karma-chrome-launcher',
            'karma-firefox-launcher',
            'karma-mocha',
            'karma-chai'
        ],

        junitReporter : {
            outputFile: 'test_out/unit.xml',
            suite: 'unit'
        }

    })
};

Remove Underscore as it's not longer a dependency


import React from 'react';
import {I18nextProvider} from 'react-i18next';
import i18next from 'i18next';
import keyBy from 'lodash/keyBy';
import mapValues from 'lodash/mapValues';
import mapKeys from 'lodash/mapKeys';

const localesContext = require.context('../../../../../locales', false, /\.json$/);

const listOfLocalePaths = localesContext.keys();
const requireLocaleByPath = localesContext;
const localesByPath =
  mapValues(
    keyBy(listOfLocalePaths, (s) => s),
    (localePath) => ({ translation: requireLocaleByPath(localePath) })
  );

const localesByName =
  mapKeys(localesByPath, (_, localePath) => {
    return localePath.replace(/^\.\//, '').replace(/\.json$/, '');
  });

const i18n =
  i18next
    .init({
      resources: localesByName,
      lng: 'en' // @todo: How should the user pick their preferred language? #UX
    }, (err, t) => {
      // @todo: do we have some error reporting mechanism in production?
      if (err) {
        console.log(err, t);
      }
    });

export default class TranslationProvider extends React.Component {
  render() {
    return <I18nextProvider i18n={i18n}>{this.props.children}</I18nextProvider>;
  }
}

import React from 'react';
import {I18nextProvider} from 'react-i18next';
import i18next from 'i18next';
import keyBy from 'lodash/keyBy';
import mapValues from 'lodash/mapValues';
import mapKeys from 'lodash/mapKeys';
import {connect} from 'react-redux';
import {getLanguage} from '../../../home/selectors';

const localesContext = require.context('../../../../../locales', false, /\.json$/);

const listOfLocalePaths = localesContext.keys();
const requireLocaleByPath = localesContext;
const localesByPath =
  mapValues(
    keyBy(listOfLocalePaths, (s) => s),
    (localePath) => ({ translation: requireLocaleByPath(localePath) })
  );

const localesByName =
  mapKeys(localesByPath, (_, localePath) => {
    return localePath.replace(/^\.\//, '').replace(/\.json$/, '');
  });

const i18n =
  i18next
    .init({
      resources: localesByName,
      lng: 'se' // @todo: How should the user pick their preferred language? #UX
    }, (err, t) => {
      // @todo: do we have some error reporting mechanism in production?
      if (err) {
        console.log(err, t);
      }
    });

class TranslationProvider extends React.Component {

  componentWillReceiveProps(nextProps) {
    if (nextProps.language !== this.props.language) {
      i18n.changeLanguage(nextProps.language);
    }
  }

  render() {
    return <I18nextProvider i18n={i18n}>{this.props.children}</I18nextProvider>;
  }
}

const mapStateToProps = (state) => ({
  language: getLanguage(state)
});

export default connect(mapStateToProps)(TranslationProvider);

Add functionality to change language dynamically


import React from 'react'
import Autosuggest from 'react-autosuggest'

import style from './bpk-autosuggest.scss'

const defaultTheme = {
  container:            'bpk-autosuggest__container',
  containerOpen:        'bpk-autosuggest__container--open',
  input:                'bpk-autosuggest__input',
  suggestionsContainer: 'bpk-autosuggest__suggestions-container',
  suggestionsList:      'bpk-autosuggest__suggestions-list',
  suggestion:           'bpk-autosuggest__suggestion',
  suggestionFocused:    'bpk-autosuggest__suggestion--focused',
  sectionContainer:     'bpk-autosuggest__section-container',
  sectionTitle:         'bpk-autosuggest__section-title'
}

const BpkAutosuggest = (props) => {
  const inputProps = {
    value: props.value,
    onChange: props.onChange
  }

  return (
    <Autosuggest
      suggestions={props.suggestions}
      onSuggestionsUpdateRequested={props.onSuggestionsUpdateRequested}
      onSuggestionsClearRequested={props.onSuggestionsClearRequested || (() => {})}
      getSuggestionValue={props.getSuggestionValue || ((suggestion) => suggestion)}
      renderSuggestion={props.renderSuggestion}
      onSuggestionSelected={props.onSuggestionSelected || (() => {})}
      inputProps={inputProps}
      theme={defaultTheme}
    />
  )
}

export default BpkAutosuggest

import React from 'react'
import Autosuggest from 'react-autosuggest'

import style from './bpk-autosuggest.scss'

const defaultTheme = {
  container:            'bpk-autosuggest__container',
  containerOpen:        'bpk-autosuggest__container--open',
  input:                'bpk-autosuggest__input',
  suggestionsContainer: 'bpk-autosuggest__suggestions-container',
  suggestionsList:      'bpk-autosuggest__suggestions-list',
  suggestion:           'bpk-autosuggest__suggestion',
  suggestionFocused:    'bpk-autosuggest__suggestion--focused',
  sectionContainer:     'bpk-autosuggest__section-container',
  sectionTitle:         'bpk-autosuggest__section-title'
}

const BpkAutosuggest = (props) => {
  const inputProps = {
    value: props.value,
    onChange: props.onChange
  }

  return (
    <Autosuggest
      suggestions={props.suggestions}
      onSuggestionsUpdateRequested={props.onSuggestionsUpdateRequested}
      onSuggestionsClearRequested={props.onSuggestionsClearRequested || (() => {})}
      getSuggestionValue={props.getSuggestionValue || ((suggestion) => suggestion)}
      renderSuggestion={props.renderSuggestion}
      onSuggestionSelected={props.onSuggestionSelected || (() => {})}
      inputProps={inputProps}
      theme={props.theme || defaultTheme}
    />
  )
}

export default BpkAutosuggest

Allow consumers to specify custom theme for react-autosuggest in autosuggest component


module.exports = [
      {
        test: /\.ts$/,
        loader: 'tslint-loader',
        options: {
          emitErrors: true,
          failOnHint: true
        }
      }
];

module.exports = [
      {
        test: /\.ts$/,
        loader: "tslint-loader",
        options: {
          emitErrors: true,
          failOnHint: true
        }
      }
];

Replace single quotes -> double quotes


Add pass message to test script


import { enqueueRender } from './component';

export let i = 0;

/**
 *
 * @param {any} defaultValue
 */
export function createContext(defaultValue) {
	let context = {
		_id: '__cC' + i++,
		_defaultValue: defaultValue
	};

	function Consumer(props, context) {
		return props.children(context);
	}
	Consumer.contextType = context;
	context.Consumer = Consumer;

	let ctx = {};

	function initProvider(comp) {
		const subs = [];
		comp.getChildContext = () => {
			ctx[context._id] = comp;
			return ctx;
		};
		comp.shouldComponentUpdate = props => {
			subs.map(c => {
				// Check if still mounted
				if (c._parentDom) {
					c.context = props.value;
					enqueueRender(c);
				}
			});
		};
		comp.sub = (c) => {
			subs.push(c);
			let old = c.componentWillUnmount;
			c.componentWillUnmount = () => {
				subs.splice(subs.indexOf(c), 1);
				old && old();
			};
		};
	}

	function Provider(props) {
		if (!this.getChildContext) initProvider(this);
		return props.children;
	}
	context.Provider = Provider;

	return context;
}

import { enqueueRender } from './component';

export let i = 0;

/**
 *
 * @param {any} defaultValue
 */
export function createContext(defaultValue) {
	let context = {
		_id: '__cC' + i++,
		_defaultValue: defaultValue
	};

	function Consumer(props, context) {
		return props.children(context);
	}
	Consumer.contextType = context;
	context.Consumer = Consumer;

	let ctx = {};

	function initProvider(comp) {
		const subs = [];
		comp.getChildContext = () => {
			ctx[context._id] = comp;
			return ctx;
		};
		comp.shouldComponentUpdate = props => {
			subs.some(c => {
				// Check if still mounted
				if (c._parentDom) {
					c.context = props.value;
					enqueueRender(c);
				}
			});
		};
		comp.sub = (c) => {
			subs.push(c);
			let old = c.componentWillUnmount;
			c.componentWillUnmount = () => {
				subs.splice(subs.indexOf(c), 1);
				old && old();
			};
		};
	}

	function Provider(props) {
		if (!this.getChildContext) initProvider(this);
		return props.children;
	}
	context.Provider = Provider;

	return context;
}

Replace .map with .some, get rid of intermediate array


var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;

var Objectid = mongoose.Schema.Types.ObjectId;

var ConfigSchema   = new Schema({
	name: { type: String, unique: true, index: true },
	value: String,
});

ConfigSchema.set("_perms", {
	admin: "crud",
	all: "r",
});

module.exports = mongoose.model('Config', ConfigSchema);

var mongoose     = require('mongoose');
var Schema       = mongoose.Schema;

var Objectid = mongoose.Schema.Types.ObjectId;

var ConfigSchema   = new Schema({
	_id: String,
	value: String,
});

ConfigSchema.set("_perms", {
	admin: "crud",
	all: "r",
});

module.exports = mongoose.model('Config', ConfigSchema);

Allow user to specify id for config


import rootDomain from '../../../lib/rootDomain';

export function isPostNSFW(post) {
  if (!post || !post.title) {
    return false;
  }

  return !!post.title.match(/nsf[wl]/gi) || post.over_18;
}

export function postShouldRenderMediaFullbleed(post) {
  const postHint = post.post_hint;
  const media = post.media;
  return !!(postHint && postHint !== 'link' && postHint !== 'self' ||
    media && media.oembed && media.oembed.type !== 'rich' ||
    rootDomain(post.cleanUrl) === 'imgur.com' && post.preview);
}

export function isPostDomainExternal(post) {
  return post.domain !== `self.${post.subreddit}`;
}

export function cleanPostDomain(domain) {
  return domain.replace(/\.com$/, '');
}

export function cleanPostHREF(href) {
  return href.replace(/https?:\/\/i.imgur.com/, 'https://imgur.com');
}

import rootDomain from '../../../lib/rootDomain';

export function isPostNSFW(post) {
  if (!post || !post.title) {
    return false;
  }

  return !!post.title.match(/nsf[wl]/gi) || post.over_18;
}

export function postShouldRenderMediaFullbleed(post) {
  const postHint = post.post_hint;
  const media = post.media;
  return !!(postHint && postHint !== 'link' && postHint !== 'self' ||
    media && media.oembed && media.oembed.type !== 'rich' ||
    rootDomain(post.cleanUrl) === 'imgur.com' && post.preview);
}

export function isPostDomainExternal(post) {
  return post.domain !== `self.${post.subreddit}`;
}

export function cleanPostDomain(domain) {
  return (domain || '').replace(/\.com$/, '');
}

export function cleanPostHREF(href) {
  return (href || '').replace(/https?:\/\/i.imgur.com/, 'https://imgur.com');
}

Fix replace of undefined error


/*jshint node:true*/
module.exports = {
  "framework": "qunit",
  "test_page": "tests/index.html?hidepassed",
  "disable_watching": true,
  "launch_in_ci": [
    "PhantomJS"
  ],
  "launch_in_dev": [
    "PhantomJS",
    "Chrome"
  ]
};

/*jshint node:true*/
module.exports = {
  test_page: 'tests/index.html?hidepassed',
  disable_watching: true,
  launch_in_ci: [
    'Chrome'
  ],
  launch_in_dev: [
    'Chrome'
  ],
  browser_args: {
    Chrome: {
      mode: 'ci',
      args: [
        // --no-sandbox is needed when running Chrome inside a container
        process.env.TRAVIS ? '--no-sandbox' : null,

        '--disable-gpu',
        '--headless',
        '--remote-debugging-port=0',
        '--window-size=1440,900'
      ].filter(Boolean)
    }
  }
};

Use headless Chrome for testing


import * as languageHandler from "../src/languageHandler";

languageHandler.setLanguage('en');
languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]);

require('jest-fetch-mock').enableMocks();

import * as languageHandler from "../src/languageHandler";
import { TextEncoder, TextDecoder } from 'util';

languageHandler.setLanguage('en');
languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]);

require('jest-fetch-mock').enableMocks();

// polyfilling TextEncoder as it is not available on JSDOM
// view https://github.com/facebook/jest/issues/9983
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

Add TextEncoder polyfill for test run


// 1: template strings - basics
// To do: make all tests pass

describe('template string, are wrapped in backticks', function() {

  it('prints x into a string using ${x}', function() {
    var x = 42;
    assert.equal(`x=${x}`, 'x=' + x);
  });

  it('add two numbers inside the ${...}', function() {
    var x = 42;
    var y = 23;
    assert.equal(`${ x } + $ { y }`, x+y);
  });

  it('get a function call result inside ${...}', function() {
    function getDomain(){ return document.domain }
    assert.equal(`${ getDomain }`, 'tddbin.com');
  });

});

// 1: template strings - basics
// To do: make all tests pass, leave the asserts unchanged!

describe('a template string, is wrapped in backticks', function() {

  describe('by default, behaves like a normal string', function() {
    it('just surrounded by backticks', function() {
      var str = `like a string`;
      assert.equal(str, 'like a string');
    });
  });

  var x = 42;
  var y = 23;
  describe('can evaluate variables, which are wrapped in "${" and "}"', function() {
    it('e.g. a simple variable "${x}" just gets evaluated', function() {
      var evaluated = `x=${x}`;
      assert.equal(evaluated, 'x=' + x);
    });
    it('multiple variables get evaluated too', function() {
      var evaluated = '${ x } + $ { y }';
      assert.equal(evaluated, x + '+' + y);
    });
  });

  describe('can evaluate any expression, wrapped inside "${...}"', function() {
    it('all inside "${...}" gets evaluated', function() {
      var evaluated = `${ x } + $ { y }`;
      assert.equal(evaluated, x+y);
    });

    it('inside "${...}" can also be a function call', function() {
      function getDomain(){
        return document.domain;
      }
      var evaluated = `${ getDomain }`;
      assert.equal(evaluated, 'tddbin.com');
    });
  });

});

Make it more explicit and improve instructions and group it better.


// Based on https://facebook.github.io/relay/docs/guides-babel-plugin.html#using-other-graphql-implementations

import fetch from 'node-fetch';
import fs from 'fs';
import path from 'path';

import {
  buildClientSchema,
  introspectionQuery,
  printSchema,
} from 'graphql/utilities';

import { ApolloError } from './errors'

export default async function downloadSchema(url, outputPath) {
  let result;
  try {
    const response = await fetch(`${url}`, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 'query': introspectionQuery }),
    });

    result = await response.json();
  } catch (error) {
    throw new ApolloError(`Error while fetching introspection query result: ${error.message}`);
  }

  if (result.errors) {
    throw new ApolloError(`Errors in introspection query result: ${result.errors}`);
  }

  const schemaData = result.data;
  if (!schemaData) {
    throw new ApolloError('No instrospection query result data');
  }

  fs.writeFileSync(outputPath, JSON.stringify(schemaData, null, 2));
}

// Based on https://facebook.github.io/relay/docs/guides-babel-plugin.html#using-other-graphql-implementations

import fetch from 'node-fetch';
import fs from 'fs';
import path from 'path';

import {
  buildClientSchema,
  introspectionQuery,
  printSchema,
} from 'graphql/utilities';

import { ApolloError } from './errors'

export default async function downloadSchema(url, outputPath) {
  let result;
  try {
    const response = await fetch(`${url}`, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ 'query': introspectionQuery }),
    });

    result = await response.json();
  } catch (error) {
    throw new ApolloError(`Error while fetching introspection query result: ${error.message}`);
  }

  if (result.errors) {
    throw new ApolloError(`Errors in introspection query result: ${result.errors}`);
  }

  const schemaData = result.data;
  if (!schemaData) {
    throw new ApolloError(`No introspection query result data found, server responded with: ${JSON.stringify(result)}`);
  }

  fs.writeFileSync(outputPath, JSON.stringify(schemaData, null, 2));
}

Print server response when introspection query result does not contain data


import React from 'react';
import PropTypes from 'prop-types';
import QuestionForm from './Form';
import { formValuesToRequest, responseToFormValues } from './transforms';
import QuestionDetailsConnector from './DetailsConnector';

export const createSubmitHandler = (question, updateQuestion) => (values, addHandlers) => {
  console.log('UPDATE VALUES: ');
  console.log(values);
  // This is where `addHandlers` comes in handy as the form controls its state
  addHandlers(
    updateQuestion({
      variables: { input: { id: question.id, patch: formValuesToRequest(values) } },
    })
      .then(resp => {
        console.log('UpdateForm addHandlers OK:');
        console.log(resp);
        // this.props.showNotification();
      })
      .catch(error => {
        // The component is and should not be aware of this being a GraphQL error.
        console.log('UpdateForm addHandlers ERROR:');
        console.log(error);
        // this.props.showApiErrorNotification(error);
      }),
  );
};

const QuestionUpdateForm = ({ id }) => (
  <QuestionDetailsConnector id={id}>
    {({ isLoading, question, updateQuestion }) => (
      <QuestionForm
        initialValues={responseToFormValues(question)}
        onSubmit={createSubmitHandler(question, updateQuestion)}
      />
    )}
  </QuestionDetailsConnector>
);

QuestionUpdateForm.propTypes = {
  id: PropTypes.string.isRequired,
};

export default QuestionUpdateForm;

import React from 'react';
import PropTypes from 'prop-types';
import QuestionForm from './Form';
import { formValuesToRequest, responseToFormValues } from './transforms';
import QuestionDetailsConnector from './DetailsConnector';
import Router from 'next/router';

export const createSubmitHandler = (question, updateQuestion) => (values, addHandlers) => {
  console.log('UPDATE VALUES: ');
  console.log(values);
  // This is where `addHandlers` comes in handy as the form controls its state
  addHandlers(
    updateQuestion({
      variables: { input: { id: question.id, patch: formValuesToRequest(values) } },
    })
      .then(resp => {
        console.log('UpdateForm addHandlers OK:');
        console.log(resp);
        // this.props.showNotification();
        Router.push(`/admin/questao?id=${question.id}`);
      })
      .catch(error => {
        // The component is and should not be aware of this being a GraphQL error.
        console.log('UpdateForm addHandlers ERROR:');
        console.log(error);
        // this.props.showApiErrorNotification(error);
      }),
  );
};

const QuestionUpdateForm = ({ id }) => (
  <QuestionDetailsConnector id={id}>
    {({ isLoading, question, updateQuestion }) => (
      <QuestionForm
        initialValues={responseToFormValues(question)}
        onSubmit={createSubmitHandler(question, updateQuestion)}
      />
    )}
  </QuestionDetailsConnector>
);

QuestionUpdateForm.propTypes = {
  id: PropTypes.string.isRequired,
};

export default QuestionUpdateForm;

Add redirect to details page after creating question


var cp = require('child_process'),
    ps = require('ps-node'),
    Q = require('q'),
    debug = require('debug')('cheapseats'),
    phantomPath = require('phantomjs').path;

var webdriverProcess;

var driver = {

  init: function (browser, config) {
    return browser.init()
      .setWindowSize(config.browserWidth, config.browserHeight)
      .then(function () {
        process.stdout.write('\nConnected to webdriver server on port ' + config.port + '\n');
      }, function (err) {
        if (err.code === 'ECONNREFUSED') {
          var promise = Q.defer();
          ps.lookup({
            command: 'phantomjs',
            arguments: ['--webdriver', config.port]
          }, function (err, processes) {
            if (!processes.length) {
              process.stdout.write('No webdriver instance found on port ' + config.port + '. Starting phantomjs...');
              webdriverProcess = cp.spawn(phantomPath, ['--webdriver', config.port, '--ssl-protocol', 'tlsv1'], {
                stdio: 'ignore'
              });
            } else {
              process.stdout.write('.');
            }
            setTimeout(function () {
              promise.resolve();
            }, 2000);
          });
          return promise.promise.then(function () {
            return driver.init(browser, config);
          });
        } else {
          return Q.reject(err);
        }
      });

  },

  kill: function () {
    if (webdriverProcess) {
      webdriverProcess.kill();
      debug('Tearing down webdriver');
    }
  }

};

module.exports = driver;

var cp = require('child_process'),
    ps = require('ps-node'),
    Q = require('q'),
    debug = require('debug')('cheapseats'),
    phantomPath = require('phantomjs').path;

var webdriverProcess;

var driver = {

  init: function (browser, config) {
    return browser.init()
      .setWindowSize(config.browserWidth, config.browserHeight)
      .then(function () {
        process.stdout.write('\nConnected to webdriver server on port ' + config.port + '\n');
      }, function (err) {
        if (err.code === 'ECONNREFUSED') {
          var promise = Q.defer();
          ps.lookup({
            command: 'phantomjs',
            arguments: ['--webdriver', config.port]
          }, function (err, processes) {
            if (!processes.length) {
              process.stdout.write('No webdriver instance found on port ' + config.port + '. Starting phantomjs...');
              webdriverProcess = cp.spawn(phantomPath, ['--webdriver', config.port, '--ssl-protocol', 'tlsv1.2'], {
                stdio: 'ignore'
              });
            } else {
              process.stdout.write('.');
            }
            setTimeout(function () {
              promise.resolve();
            }, 2000);
          });
          return promise.promise.then(function () {
            return driver.init(browser, config);
          });
        } else {
          return Q.reject(err);
        }
      });

  },

  kill: function () {
    if (webdriverProcess) {
      webdriverProcess.kill();
      debug('Tearing down webdriver');
    }
  }

};

module.exports = driver;

Use version 1.2 of TLS


/**
* Copyright 2016 Jorne Roefs
*
**/

module.exports = function(RED) {
  "use strict";

  var fs = require('fs');
  var child_process = require('child_process');

  function ThsenseNode(n) {
    RED.nodes.createNode(this,n);

    var node = this;

    var readth = __dirname + '/../bin/readth.sh';
    fs.access(readth, fs.X_OK, (err) => {
      console.log(err ? 'No execution access to ' + readth : 'ReadTH access OK');
    });

    this.on('input', function (msg) {
      node.log(__dirname);

      child_process.execFile(readth, (err, stdout, stderr) => {
        if (err) {
          node.error(err);
          return null;
        }

        node.log(stdout);
        node.send(msg);
      });
    });

    this.on("close", function() {

    });
  }

  RED.nodes.registerType("thsense",ThsenseNode);

}

/**
* Copyright 2016 Jorne Roefs
*
**/

module.exports = function(RED) {
  "use strict";

  var fs = require('fs');
  var child_process = require('child_process');

  function ThsenseNode(n) {
    RED.nodes.createNode(this,n);

    var node = this;

    var readth = __dirname + '/../bin/readth.sh';
    fs.access(readth, fs.X_OK, (err) => {
      console.log(err ? 'No execution access to ' + readth : 'ReadTH access OK');
    });

    this.on('input', function (msg) {
      node.log(__dirname);

      child_process.execFile(readth, (err, stdout, stderr) => {
        if (err) {
          node.error(err);
          return null;
        }

        // node.log(stdout);
        msg.payload = JSON.parse(stdout);
        node.send(msg);
      });
    });

    this.on("close", function() {

    });
  }

  RED.nodes.registerType("thsense",ThsenseNode);

}

Send measurement data as msg.payload


var gulp = require("gulp");
var CFG = require("./utils/config.js");
var $ = require("gulp-load-plugins")();
var path = require("path");
var pkg = require(path.join("..", CFG.FILE.config.pkg));
var karma = require("karma").server;
var notify = require("./utils/notify-script-test-unit");

/**
 * script:test:unit
 * @see www.npmjs.org/package/karma
 * @see karma-runner.github.io
 */
gulp.task("script:test:unit", ["script:lint"], function (callback) {
  karma.start({
    configFile: __dirname + "/../" + CFG.FILE.config.karma,
    singleRun: true
  }, function(exitCode) {
    notify(exitCode);
    callback(exitCode);
  });
});

var gulp = require("gulp");
var CFG = require("./utils/config.js");
var $ = require("gulp-load-plugins")();
var path = require("path");
var pkg = require(path.join("..", CFG.FILE.config.pkg));
var karma = require("karma");
var notify = require("./utils/notify-script-test-unit");

/**
 * script:test:unit
 * @see www.npmjs.org/package/karma
 * @see karma-runner.github.io
 */
gulp.task("script:test:unit", ["script:lint"], function (callback) {
  var karmaServer = new karma.Server({
    configFile: __dirname + "/../" + CFG.FILE.config.karma,
    singleRun: true
  }, function(exitCode) {
    notify(exitCode);
    callback(exitCode);
  });

  karmaServer.start();
});

Use the new way of starting the Karma server.


import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import signOut from '../actions/sign-out-user'
import FlatButton from 'material-ui/FlatButton'

class CreateGame extends Component {
  signOut() {
    this.props.signOut()
  }

  render() {
    const { currentUser } = this.props

    return(

Hi, { currentUser.name }!

          <FlatButton
            onClick={ this.signOut.bind(this) }
            label="Sign out"/>

    )
  }
}

const mapStateToProps = (state) => {
  return {
    currentUser: state.currentUser,
  }
}

export default connect(mapStateToProps, { signOut })(CreateGame);

import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import signOut from '../actions/sign-out-user'
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
import FlatButton from 'material-ui/FlatButton'

const errorMargin = {
  marginTop: '2rem'
}

const dialogStyle = {
  width: '400px',
  margin: '50px auto',
  padding: '2rem',
}

const buttonStyle = {
  float: 'right',
  marginLeft: '2rem',
}

class CreateGame extends Component {
  signOut() {
    this.props.signOut()
  }

  render() {
    const { currentUser } = this.props

    return(
      <Paper style={ dialogStyle }>

Hi, { currentUser.name }!

          <TextField type="player" ref="player" hintText="Add a player"/>

          <FlatButton
            onClick={ this.signOut.bind(this) }
            label="Sign out"/>

          <RaisedButton
            style={ buttonStyle }
            label='Create game'
            primary={true} />

      </Paper>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    currentUser: state.currentUser,
  }
}

export default connect(mapStateToProps, { signOut })(CreateGame);

Add text field and button


var createBetNotification = function(bet){
  var bet = Bets.findOne({ _id: bet });

  BetNotifications.insert({
    toNotify: bet.bettors[1],
    betBy: bet.bettors[0]
  });
}

Template.createBetForm.events({
  "submit .create-bet" : function(event){
    var status = "open",
        title = event.target.betTitle.value;
        wager = event.target.betWager.value;
        user = Meteor.user(),
        defender_requested = event.target.defender.value;
        defender = Meteor.users.findOne({ username: defender_requested });

    var bet = Bets.insert({
      bettors: [ user.username, defender.username ],
      status: status,
      title: title,
      wager: wager
    });

    createBetNotification(bet);
  }
});

var createBetNotification = function(bet){
  var bet = Bets.findOne({ _id: bet });

  BetNotifications.insert({
    toNotify: bet.bettors[1],
    betBy: bet.bettors[0]
  });
}

Template.createBetForm.events({
  "submit .create-bet" : function(event){
    event.preventDefault();

    var status = "open",
        title = event.target.betTitle.value;
        wager = event.target.betWager.value;
        user = Meteor.user(),
        defender_requested = event.target.defender.value;
        defender = Meteor.users.findOne({ username: defender_requested });

    var bet = Bets.insert({
      bettors: [ user.username, defender.username ],
      status: status,
      title: title,
      wager: wager
    });

    event.target.betTitle.value = '';
    event.target.betWager.value = '';
    event.target.defender.value = '';

    createBetNotification(bet);
  }
});

Create form: prevent refresh, clean forms after enter.


import { join } from 'path';
import Router from 'express/lib/router';
import cookieMiddleware from 'universal-cookie-express';
import createLocaleMiddleware from './createLocaleMiddleware';

const defaultOptions = {
  routes: {},
  redirects: [],
};

export default (app, {
  routes = defaultOptions.routes,
  redirects = defaultOptions.redirects,
  defaultLocale,
  siteLocales,
  compression,
} = defaultOptions) => {
  const router = Router();
  const handle = app.getRequestHandler();
  if (!app.dev) {
    router.use(require('compression')(compression));
  }
  router.use(cookieMiddleware());
  if (defaultLocale && siteLocales) {
    router.use(createLocaleMiddleware({ defaultLocale, siteLocales }));
  }
  router.get('/_soya/:path(*)', async (req, res) => {
    const p = join(app.dir, app.dist, 'dist', 'static', req.params.path);
    await app.serveStatic(req, res, p);
  });
  redirects.forEach(({ from, to, method = 'get', type = 301 }) => {
    router[method](from, (req, res) => res.redirect(type, to));
  });
  Object.keys(routes).forEach(path => {
    const { method = 'get', page } = routes[path];
    router[method](path, (req, res) => {
      app.render(req, res, page, Object.assign({}, req.query, req.params));
    });
  });
  router.get('*', (req, res) => handle(req, res));
  return router;
};

import { join } from 'path';
import Router from 'express/lib/router';
import cookieMiddleware from 'universal-cookie-express';
import createLocaleMiddleware from './createLocaleMiddleware';

const defaultOptions = {
  routes: {},
  redirects: [],
};

export default (app, {
  routes = defaultOptions.routes,
  redirects = defaultOptions.redirects,
  defaultLocale,
  siteLocales,
  compression,
} = defaultOptions) => {
  const router = Router();
  const handle = app.getRequestHandler();
  if (!app.dev) {
    router.use(require('compression')(compression));
  }
  router.use(cookieMiddleware());
  if (defaultLocale && siteLocales) {
    router.use(createLocaleMiddleware({ defaultLocale, siteLocales }));
  }
  router.get('/_soya/:path(*)', async (req, res) => {
    const p = join(app.dir, app.dist, 'dist', 'static', req.params.path);
    await app.serveStatic(req, res, p);
  });
  redirects.forEach(({ from, to, method = 'GET', type = 301 }) => {
    router[method.toLowerCase()](from, (req, res) => res.redirect(type, to));
  });
  Object.keys(routes).forEach(path => {
    const { method = 'GET', page } = routes[path];
    router[method.toLowerCase()](path, (req, res) => {
      app.render(req, res, page, Object.assign({}, req.query, req.params));
    });
  });
  router.get('*', (req, res) => handle(req, res));
  return router;
};

Make HTTP method of custom routing and URL redirection case insensitive


var http = require('http');
var q = require('q');
var request = require('request');

describe('HTTP', function() {

  var encode = function(command) {
    var host = 'http://localhost:13000/testSelector';
    return host + '?popupInput=' + encodeURIComponent(command);
  };

  var callElementor = function(command) {
    var deferred = q.defer();

    var url = encode(command);
    request(url, function(error, response, body) {
      if (error) {
        console.log("Got error: " + error);
        return deferred.reject(error);
      }
      deferred.resolve(JSON.parse(body));
    });

    return deferred.promise;
  };

  it('should navigate to protractor website', function(done) {
    var url = 'browser.get(\'http://angular.github.io/protractor/#/api\')';

    callElementor(url).then(function(response) {
      expect(response).toEqual({
        results: {
          'browser.get(\'http://angular.github.io/protractor/#/api\')': null
        }
      });
      return callElementor('browser.getCurrentUrl()');
    }).then(function(response) {
      expect(response).toEqual({
        results: {
          'browser.getCurrentUrl()': 'http://angular.github.io/protractor/#/api'
        }
      });
      done();
    });
  });
})
;

var http = require('http');
var q = require('q');
var request = require('request');

describe('HTTP', function() {

  var encode = function(command) {
    var host = 'http://localhost:13000/testSelector';
    return host + '?popupInput=' + encodeURIComponent(command);
  };

  var callElementor = function(command) {
    var deferred = q.defer();

    var url = encode(command);
    request(url, function(error, response, body) {
      if (error) {
        console.log("Got error: " + error);
        return deferred.reject(error);
      }
      deferred.resolve(JSON.parse(body));
    });

    return deferred.promise;
  };

  it('should navigate to protractor website', function(done) {
    var url = 'browser.get(\'http://angular.github.io/protractor/#/api\')';

    // Given that you navigate to the protractor website.
    callElementor(url).then(function(response) {
      // When you get the current URL.
      return callElementor('browser.getCurrentUrl()');
    }).then(function(response) {
      // Then ensure the URL has changed.
      expect(response).toEqual({
        results: {
          'browser.getCurrentUrl()': 'http://angular.github.io/protractor/#/api'
        }
      });
      done();
    });
  });
})
;

Add given / when / them


var formatData = function(restaurants) {
  var formattedData = [];
  // for each restaurant element in restaurants
    //  {
      // yelpId:
      // name:
      // rating:
      // price:
      // location:  (might want to use display_address here - it's an array)
        // address:
        // zip_code:
    //  }
  return formattedData;
};

var chooseTasks = function(restaurants) {
  var tasks = [];
  const numTasks = 3;

  for (var i = 0; i < numTasks; i++) {
    var random = Math.floor( Math.random() * numTasks );
    tasks.push( restaurants[random] );
  }
  return tasks;
};

module.exports.formatData = formatData;
module.exports.chooseTasks = chooseTasks;

var formatData = function(restaurants) {
  var formattedData = [];
  // for each restaurant element in restaurants
    //  {
      // yelpId:
      // name:
      // rating:
      // price:
      // location:  (might want to use display_address here - it's an array)
        // address:
        // zip_code:
    //  }
  return formattedData;
};

var chooseTasks = function(restaurants, size) {
  var tasks = [];
  var numTasks = size || 3;

  for (var i = 0; i < numTasks; i++) {
    var random = Math.floor( Math.random() * numTasks );
    tasks.push( restaurants[random] );
  }
  return tasks;
};

module.exports.formatData = formatData;
module.exports.chooseTasks = chooseTasks;

Add size parameter to chooseTasks, in case we need it


/* 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 () {
  $("#start-towtruck").click(function () {
    TowTruck(this, function () {
      var session = require({context: "towtruck"})("session");
      session.on("shareId", function () {
        var other;
        console.log("got shareId", session.shareId, window.name);
        if (window.name == "left") {
          other = "right";
        } else if (window.name == "right") {
          other = "left";
        }
        if (other) {
          console.log("window.open", session.shareUrl(), other);
          window.open("about:blank", other);
          setTimeout(function () {
            window.open(session.shareUrl(), other);
          }, 500);
        }
      });
    });
    return false;
  });
});

$(function () {
  $("#how_it_works_carousel").on("slide", function (event) {
    var allSlides = $(event.target).find(".item");
    var slide = event.relatedTarget;
    var index = Array.prototype.slice.call(allSlides).indexOf(slide);
    $("#how_it_works_indicators li").removeClass("active");
    $("#how_it_works_indicators li[data-slide-index=\"" + index + "\"]").addClass("active");
  });
});

Update the .active class on the indicators that are outside the carousel


define([
  'zepto',
  'backbone',
  'collections/items',
  'collections/users',
  'collections/likes',
  'views/create_item_page',
  'views/show_item_page',
  'views/search_form_page',
  'views/search_results_page'
], function(
  $, Backbone, itemCollection, userCollection, likeCollection,
  CreateItemPageView, ShowItemPageView,
  SearchFormPageView, SearchResultsPageView){

  var views = {
        CreateItemPageView: CreateItemPageView,
        ShowItemPageView: ShowItemPageView,
        SearchFormPageView: SearchFormPageView,
        SearchResultsPageView: SearchResultsPageView,
      },
      Router = Backbone.Router.extend({
        routes: {
          '': 'CreateItemPageView',
          'item/:itemId': 'ShowItemPageView',
          'search_form': 'SearchFormPageView',
          'search?*query': 'SearchResultsPageView',
          'search': 'SearchResultsPageView'
        },
        initialize: function(){

          Backbone.history.on('route', function(router, viewName, params){
            if(this.view){
              this.view.close();
            }
            this.view = new views[viewName]({params: params});
            $('#container').html(this.view.el);
          }, this);

          // Internally handle anchor clicks if route is defined
          $(document).delegate('a', 'click', function(e){
            var route = e.target.getAttribute('href').replace(/^\//, '');
            Backbone.history.navigate(route, {trigger: true});
            return false;
          });

        }
      });

  return Router;

});

define([
  'zepto',
  'backbone',
  'collections/items',
  'collections/users',
  'collections/likes',
  'views/create_item_page',
  'views/show_item_page',
  'views/search_form_page',
  'views/search_results_page'
], function(
  $, Backbone, itemCollection, userCollection, likeCollection,
  CreateItemPageView, ShowItemPageView,
  SearchFormPageView, SearchResultsPageView){

  var Router = Backbone.Router.extend({
        routes: {
          ''              : CreateItemPageView,
          'item/:itemId'  : ShowItemPageView,
          'search_form'   : SearchFormPageView,
          'search?*query' : SearchResultsPageView,
          'search'        : SearchResultsPageView
        },
        initialize: function(){

          Backbone.history.on('route', function(router, viewClass, params){
            if(this.view){
              this.view.close();
            }
            this.view = new viewClass({params: params});
            $('#container').html(this.view.el);
          }, this);

          // Internally handle anchor clicks if route is defined
          $(document).delegate('a', 'click', function(e){
            var route = e.target.getAttribute('href').replace(/^\//, '');
            Backbone.history.navigate(route, {trigger: true});
            return false;
          });

        }
      });

  return Router;

});

Use view class directly in routing


const config = require('../../config'),
      sparqlQueryBuilder = require('../utils/sparql-query-builder'),
      sparqlClient = require('sparql-client'),
      util = require('util');

/* To be moved to its own file */
class Resource {
  constructor(uri, properties = {}, relationships = {}) {
    this.uri = uri,
    this.properties = properties;
    this.relationships = relationships;
  }
}

class DBPedia {
  constructor() {
    this.resources = {};
    this.anchor_resources = [];
  }

  addAnchors(anchors) {
    this.anchor_resources.push(...anchors.filter(anchor => !(anchor in this.anchor_resources)));
  }

  fetch(onSuccess, onFail) {
    var resources_to_fetch = this.anchor_resources.filter(anchor => !this.resources[anchor]);
    var query = sparqlQueryBuilder.DBPedia.fetch(resources_to_fetch);
    var client = new sparqlClient(config.DBPEDIA_ENDPOINT);

    client.query(query)
      .execute((err, results) => {
        if(err) onFail(err);
        else onSuccess(results);
      });
  }

  getResources() { return this.resources; }

  _createResourceFromSparqlResult(result) {
    /* To be implemented */
  }
}

module.exports = DBPedia;

var config = require('../../config');
var sparqlQueryBuilder = require('../utils/sparql-query-builder');
var sparqlClient = require('sparql-client');
var util = require('util');

/* To be moved to its own file */
class Resource {
  constructor(uri, properties = {}, relationships = {}) {
    this.uri = uri,
    this.properties = properties;
    this.relationships = relationships;
  }
}

class DBPedia {
  constructor() {
    this.resources = {};
    this.anchor_resources = [];
  }

  addAnchors(anchors) {
    this.anchor_resources.push(...anchors.filter(anchor => !(anchor in this.anchor_resources)));
  }

  fetch(onSuccess, onFail) {
    var resources_to_fetch = this.anchor_resources.filter(anchor => !this.resources[anchor]);
    var query = sparqlQueryBuilder.DBPedia.fetch(resources_to_fetch);
    var client = new sparqlClient(config.DBPEDIA_ENDPOINT);

    client.query(query)
      .execute((err, results) => {
        if(err) onFail(err);
        else onSuccess(results);
      });
  }

  getResources() { return this.resources; }

  _createResourceFromSparqlResult(result) {
    /* To be implemented */
  }
}

module.exports = DBPedia;

Fix var type convention conflict


import React, { Component } from 'react';

import CustomTitle from '../Cards/CustomTitle';
import PresalesCard from '../Cards/Tickets/PresalesCard';
import OnSiteCard from '../Cards/Tickets/OnSiteCard';

const i18n_strings = {
  fr: {
    title: 'Tickets',
    desc: 'Sur place et Préventes',
  },
  en: {
    title: 'Tickets',
    desc: 'On site and pre sales',
  },
  nl: {
    title: 'Tickets',
    desc: 'Ter plaatse / Voorverkoop',
  },
}

export default class TicketsPage extends React.Component {

  render() {
    var strings = i18n_strings[this.props.lang] || i18n_strings['fr'];

    return (
      <div>
        <CustomTitle title={strings.title} desc={strings.desc} />
        <PresalesCard lang={this.props.lang} />
        <OnSiteCard lang={this.props.lang} />
      </div>
    );
  }
}

import React, { Component } from 'react';

import CustomTitle from '../Cards/CustomTitle';
import PresalesCard from '../Cards/Tickets/PresalesCard';
import OnSiteCard from '../Cards/Tickets/OnSiteCard';

const i18n_strings = {
  fr: {
    title: 'Tickets',
    desc: 'Sur place et Préventes',
  },
  en: {
    title: 'Tickets',
    desc: 'On site and pre sales',
  },
  nl: {
    title: 'Tickets',
    desc: 'Ter plaatse / Voorverkoop',
  },
}

export default class TicketsPage extends React.Component {

  render() {
    var strings = i18n_strings[this.props.lang] || i18n_strings['fr'];

    return (

        <CustomTitle title={strings.title} desc={strings.desc} />
        <OnSiteCard lang={this.props.lang} />

        <PresalesCard lang={this.props.lang} />

    );
  }
}

Change tickets page with on site first


import Resolver from '../../resolver';
import config from '../../config/environment';

const resolver = Resolver.create();

resolver.namespace = {
  modulePrefix: config.modulePrefix,
  podModulePrefix: config.podModulePrefix,
};

export default resolver;

import Resolver from '../../resolver';
import config from '../../config/environment';

const resolver = Resolver.create({
  namespace: {
    modulePrefix: config.modulePrefix,
    podModulePrefix: config.podModulePrefix,
  },
});

export default resolver;

Fix tests Resolver creation under Ember 3.3


import { combineReducers } from 'redux';
import notificationList from './notifications-list';
import visibilityFilter from './visibility-filter';
import isFetching from './is-fetching';

//This is a reduce function to ease reducer composition.
export default combineReducers({
    visibilityFilter,
    notificationList,
    isFetching
});

import { combineReducers } from 'redux';
import notificationList from './notifications-list';
import visibilityFilter from './visibility-filter';
import isFetching from './is-fetching';
import isOpen from './is-open';
//This is a reduce function to ease reducer composition.
export default combineReducers({
    visibilityFilter,
    notificationList,
    isFetching,
    isOpen
});

Add reducer into the list.


require('../../node_modules/cookieconsent/build/cookieconsent.min.js');

// We're using http://cookieconsent.wpengine.com/documentation/javascript-api/

window.cookieconsent.initialise({
  cookie: {
    name: config.features.cookieName
  },
  content: {
    header: 'Cookies used on the website!',
    message: 'Vi bruger cookies for at give dig en bedre brugeroplevelse.',
    dismiss: 'OK',
    link: 'Læs mere',
    href: '/cookies',
  }
});

require('cookieconsent/build/cookieconsent.min.js');

// We're using http://cookieconsent.wpengine.com/documentation/javascript-api/

window.cookieconsent.initialise({
  cookie: {
    name: config.features.cookieName
  },
  content: {
    header: 'Cookies used on the website!',
    message: 'Vi bruger cookies for at give dig en bedre brugeroplevelse.',
    dismiss: 'OK',
    link: 'Læs mere',
    href: '/cookies',
  }
});

Fix attempt browserify require node module path


// Babel required for es6 code in e2e tests
require('babel-register');

let config = {
  baseUrl: 'http://localhost:8080',
  specs: ['./src/**/*.e2e.js'],
  capabilities: {
    'browserName': 'chrome'
  },
  // Current firefox version does not navigate to page. Disable until problem
  // fixed, though it seems to be a reocurring issue
  // See: https://github.com/angular/protractor/issues/3150
  // {
  //   'browserName': 'firefox'
  // }
	onPrepare: function() {
		browser.manage().window().setSize(1280, 1024);
	}
};

if (process.env.TRAVIS) {
  config.sauceUser = process.env.SAUCE_USERNAME;
  config.sauceKey = process.env.SAUCE_ACCESS_KEY;
  config.capabilities = {
    'browserName': 'chrome',
    'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
    'build': process.env.TRAVIS_BUILD_NUMBER
  };
}

exports.config = config;

// Babel required for es6 code in e2e tests
require('babel-register');

var config = {
  baseUrl: 'http://localhost:8080',
  specs: ['./src/**/*.e2e.js'],
  capabilities: {
    'browserName': 'chrome'
  },
  // Current firefox version does not navigate to page. Disable until problem
  // fixed, though it seems to be a reocurring issue
  // See: https://github.com/angular/protractor/issues/3150
  // {
  //   'browserName': 'firefox'
  // }
	onPrepare: function() {
		browser.manage().window().setSize(1280, 1024);
	}
};

if (process.env.TRAVIS) {
  config.sauceUser = process.env.SAUCE_USERNAME;
  config.sauceKey = process.env.SAUCE_ACCESS_KEY;
  config.capabilities = {
    'browserName': 'chrome',
    'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
    'build': process.env.TRAVIS_BUILD_NUMBER
  };
}

exports.config = config;

Change let to var for node 5 support


if (typeof (Stars) === 'undefined') {Stars = {}};

Stars.New = (function($) {
  var init = function() {
    setSelected();
    $('form li').click(selectUser);
  };

  var selectUser = function() {
    var li = $(this);
    li.siblings().removeClass('selected');
    li.addClass('selected');
    li.find('input:radio').attr('checked', true);
  };

  var setSelected = function() {
    radio = $('#new_star input:radio:checked').parent().addClass('selected');
  }

  return {
    init: init
  };
})(jQuery);

$(document).ready(function() {
  Stars.New.init();
});

if (typeof (Stars) === 'undefined') {Stars = {}};

Stars.New = (function($) {
  var init = function() {
    setSelected();
    $('form li').click(selectUser);
  };

  var selectUser = function(e) {
    e.preventDefault();
    var li = $(this);
    li.siblings().removeClass('selected');
    li.addClass('selected');
    li.find('input:radio').attr('checked', true);
  };

  var setSelected = function() {
    radio = $('#new_star input:radio:checked').parent().addClass('selected');
  }

  return {
    init: init
  };
})(jQuery);

$(document).ready(function() {
  Stars.New.init();
});

Stop profile links from firing on the star form


var csv = require('binary-csv');
var through2 = require('through2');

/**
 * Parses an input stream.
 *
 * @param {object} stream
 * @param {function} item_callback
 * @param {function} done
 * @return {void}
 */
module.exports.read = function(stream, item_callback, done) {
	var parser = csv({
		separator: '\t',
		newline: '\n',
		detectNewlines: true
	});

	var waiting = 0;
	var transformer = through2(function(chunk, enc, callback) {
		var cells = parser.line(chunk).map(function(cell) {
			return cell.toString('utf8');
		});
		waiting++;
		item_callback(cells, function(err) {
			waiting--;
			callback(err);
		});
	});

	var finish = function(err) {
		if (!waiting) {
			return done(err);
		}
		setTimeout(finish, 10);
	};

	stream.on('end', finish);
	stream.pipe(parser).pipe(transformer);
};

var csv = require('binary-csv');
var through2 = require('through2');

/**
 * Parses an input stream.
 *
 * @param {object} stream
 * @param {function} item_callback
 * @param {function} done
 * @return {void}
 */
module.exports.read = function(stream, item_callback, done) {
	var parser = csv({
		separator: '\t',
		newline: '\n',
		detectNewlines: true
	});

	var waiting = 0;
	var transformer = through2(function(chunk, enc, callback) {
		var cells, was_async;

		cells = parser.line(chunk).map(function(cell) {
			return cell.toString('utf8');
		});

		waiting++;
		was_async = false;
		item_callback(cells, function(err) {
			waiting--;
			cells = null;

			if (!was_async) {
				process.nextTick(function() {
					callback(err);
				});
			} else {
				callback(err);
			}
		});
		was_async = true;
	});

	var finish = function(err) {
		if (!waiting) {
			return done(err);
		}
		setTimeout(finish, 10);
	};

	stream.on('end', finish);
	stream.pipe(parser).pipe(transformer);
};

Fix for memleak when user callback is not truly-async.


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

'use strict';

// Pixel Manipulation Functions.

(function () {
  window.getColor = function (data, x, y, width) {
    var offset = (y * width + x) * 4;
    return {
      red: data[offset],
      green: data[offset + 1],
      blue: data[offset + 2],
      alpha: data[offset + 3]
    };
  };

  window.setColor = function (data, x, y, width, color) {
    var offset = (y * width + x) * 4;
    data[offset] = color.red;
    data[offset + 1] = color.green;
    data[offset + 2] = color.blue;
    data[offset + 3] = color.alpha;
  };
})();

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

'use strict';

// Pixel Manipulation Functions.

(function () {
  window.getColor = function (data, x, y, width) {
    var offset = (y * width + x) * 4;
    return {
      red: data[offset],
      green: data[offset + 1],
      blue: data[offset + 2],
      alpha: data[offset + 3]
    };
  };

  window.setColor = function (data, x, y, width, color) {
    var offset = (y * width + x) * 4;
    data[offset] = color.red;
    data[offset + 1] = color.green;
    data[offset + 2] = color.blue;
    data[offset + 3] = color.alpha;
  };

  window.getIntercepts = function (rho, theta, canvas, log) {
    var x = rho * Math.sin(theta);
    var y = rho * Math.cos(theta);
    if (log) {
      console.log('Plotting ' + rho + '@' + (theta/Math.PI*180) + ' as '+ x + ',' + y);
    }
    var x1 = x + y / rho * canvas.width;
    var y1 = y + x / rho * canvas.height;
    return [{x:x, y:y}, {x:x1, y:y1}];
  };
})();

Add the first cut at a function to get the x and y intercepts of a hough line. (Needs much testing and fixing!)


/**
 * The Sea.js plugin for collecting HTTP requests and sending all at once
 */
(function(seajs) {

  var Module = seajs.Module
  var _load = Module.prototype._load

  var data = seajs.data
  var cid = 0
  var stack = []

  Module.prototype._load = function() {
    stack.push(this)
  }

  seajs.flush = function() {
    var deps = []

    // Collect dependencies
    for (var i = 0, len = stack.length; i < len; i++) {
      deps = deps.concat(stack[i].dependencies)
    }

    // Create an anonymous module for flushing
    var mod = Module.get(
        uri || data.cwd + "_flush_" + cid++,
        deps
    )

    mod._load = _load

    mod._callback = function() {
      for (var i = 0, len = stack.length; i < len; i++) {
        stack[i]._onload()
      }

      // Empty stack
      stack.length = 0
    }

    // Load it
    mod._load()
  }

  // Flush on loaded
  seajs.on("requested", function() {
    seajs.flush()
  })

  // Register as module
  define("seajs-flush", [], {})

})(seajs);

/**
 * The Sea.js plugin for collecting HTTP requests and sending all at once
 */
(function(seajs) {

  var Module = seajs.Module
  var load = Module.prototype.load

  var data = seajs.data
  var cid = 0
  var stack = []

  Module.prototype.load = function() {
    stack.push(this)
  }

  seajs.flush = function() {
    var currentStack = stack.splice(0)
    var deps = []

    // Collect dependencies
    for (var i = 0, len = currentStack.length; i < len; i++) {
      deps = deps.concat(currentStack[i].resolve())
    }

    // Create an anonymous module for flushing
    var mod = Module.get(
        data.cwd + "_flush_" + cid++,
        deps
    )

    mod.load = load

    mod.callback = function() {
      for (var i = 0, len = currentStack.length; i < len; i++) {
        currentStack[i].onload()
      }
    }

    // Load it
    mod.load()
  }

  // Flush to load dependencies
  seajs.on("requested", function() {
    seajs.flush()
  })

  // Flush to load `require.async` when module.factory is executed
  seajs.on("exec", function() {
    seajs.flush()
  })

  // Register as module
  define("seajs-flush", [], {})

})(seajs);

Enhance flush plugin to support load dependencies and require.async modules


const assert = require('assert');
const nativeImage = require('electron').nativeImage;
const path = require('path');

describe('nativeImage module', function () {
  describe('createFromPath(path)', function () {
    it('normalizes the path', function () {
      const nonAbsolutePath = path.join(__dirname, 'fixtures', 'api') + path.sep + '..' + path.sep + path.join('assets', 'logo.png');
      const image = nativeImage.createFromPath(nonAbsolutePath);
      assert(!image.isEmpty());
    });
  });
});

'use strict';

const assert = require('assert');
const nativeImage = require('electron').nativeImage;
const path = require('path');

describe('nativeImage module', () => {
  describe('createFromPath(path)', () => {
    it('normalizes the path', () => {
      const nonAbsolutePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`;
      const image = nativeImage.createFromPath(nonAbsolutePath);
      assert(!image.isEmpty());
    });
  });
});

Use template string and arrow functions


"use strict";

function abortRequest(res) {
    res.statusCode = 415;
    res.end();
}

function checkContentType(acceptedTypes, encoding) {
    var actualType,
        acceptedType,
        i, l;

    for (i = 0, l = acceptedTypes.length; i < l; i++) {
        acceptedTypes[i] = acceptedTypes[i].toLowerCase();
    }

    return function checkContentType(req, res, next) {
        actualType = req.headers['content-type'];
        if (req.method === 'POST' || req.method === 'PUT') {
            actualType = actualType.toLowerCase();
            if (encoding && actualType.search(encoding) === -1) {
                abortRequest(res);
                next(new Error("(alamid) Invalid request type: " + req.headers['content-type']));
                return;
            }
            actualType = actualType.replace(/; *charset *=.*/gi, '');
            for (i = 0, l = acceptedTypes.length; i < l; i++) {
                acceptedType = acceptedTypes[i];
                if (acceptedType === actualType) {
                    next();
                    return;
                }
            }
            abortRequest(res);
            next(new Error("(alamid) Invalid request type: " + req.headers['content-type']));
            return;
        }
        next();
    };
}

module.exports = checkContentType;

"use strict";

function abortRequest(res) {
    res.statusCode = 415;
    res.end();
}

function checkContentType(acceptedTypes, encoding) {
    var actualType,
        acceptedType,
        i, l;

    for (i = 0, l = acceptedTypes.length; i < l; i++) {
        acceptedTypes[i] = acceptedTypes[i].toLowerCase();
    }

    return function checkContentType(req, res, next) {
        actualType = req.headers["content-type"] || "";
        if (req.method === "POST" || req.method === "PUT") {
            actualType = actualType.toLowerCase();
            if (encoding && actualType.search(encoding) === -1) {
                abortRequest(res);
                next(new Error("(alamid) Invalid request type: " + req.headers['content-type']));
                return;
            }
            actualType = actualType.replace(/; *charset *=.*/gi, '');
            for (i = 0, l = acceptedTypes.length; i < l; i++) {
                acceptedType = acceptedTypes[i];
                if (acceptedType === actualType) {
                    next();
                    return;
                }
            }
            abortRequest(res);
            next(new Error("(alamid) Invalid request type: " + req.headers["content-type"]));
            return;
        }
        next();
    };
}

module.exports = checkContentType;

Fix check for content type


import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
import {merge} from 'lodash'

export default merge(lightBaseTheme, {
  palette: {
    primary1Color: '#f09442',
    primary2Color: '#e95d3b',
    accent1Color: '#99dc3c',
    pickerHeaderColor: '#f09442',
    footerColor: 'rgb(0, 122, 204)'
  }
})

import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme'
import {merge} from 'lodash'

export default merge(lightBaseTheme, {
  palette: {
    primary1Color: '#f09442',
    primary2Color: '#e95d3b',
    accent1Color: '#99dc3c',
    pickerHeaderColor: '#f09442',
    footerColor: '#0097A7'
  }
})

Use Cyan color (in google's color palette)


var flowTypes = require("engine-flow-types")

flowTypes.Emit.prototype.enny = function () {
    return [];
};

flowTypes.DataHandler.prototype.enny = function () {
    return [];
};

flowTypes.StreamHandler.prototype.enny = function () {
    return [];
};

flowTypes.Listener.prototype.enny = function () {
    var event = {
        data: []
      , error: null
      , end: null
    };
    return event;
};

var flowTypes = require("engine-flow-types")

flowTypes.Emit.prototype.enny = function () {
    return [];
};

flowTypes.DataHandler.prototype.enny = function () {
    return [];
};

flowTypes.StreamHandler.prototype.enny = function () {
    return [];
};

flowTypes.Listener.prototype.enny = function () {
    var event = {
        data: this.data.map(c => c.enny())
      , error: null
      , end: null
    };
    return event;
};

Convert the data into enny


// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import Button from './Button';
import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.TAB;

export default class Tab extends Component {

  constructor() {
    super();

    this._onClickTab = this._onClickTab.bind(this);
  }

  _onClickTab (event) {
    const { onRequestForActive } = this.props;
    if (event) {
      event.preventDefault();
    }
    onRequestForActive();
  }

  render () {
    const { active, className, id, title, ...props } = this.props;
    delete props.onRequestForActive;
    const classes = classnames(
      CLASS_ROOT, {
        [`${CLASS_ROOT}--active`]: active
      },
      className
    );

    return (
      <li {...props} className={classes} id={id}>
        <Button className={`${CLASS_ROOT}__button`} plain={true}
          role='tab' aria-selected={active}
          onClick={this._onClickTab} aria-expanded={active}>
          <label className={`${CLASS_ROOT}__label`} htmlFor={id}>
            {title}
          </label>
        </Button>
      </li>
    );
  }
}

Tab.propTypes = {
  title: PropTypes.string.isRequired,
  active: PropTypes.bool,
  id: PropTypes.string,
  onRequestForActive: PropTypes.func // from Tabs
};

// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import Button from './Button';
import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.TAB;

export default class Tab extends Component {

  constructor() {
    super();

    this._onClickTab = this._onClickTab.bind(this);
  }

  _onClickTab (event) {
    const { onRequestForActive } = this.props;
    if (event) {
      event.preventDefault();
    }
    onRequestForActive();
  }

  render () {
    const { active, className, id, title, ...props } = this.props;
    delete props.onRequestForActive;
    const classes = classnames(
      CLASS_ROOT, {
        [`${CLASS_ROOT}--active`]: active
      },
      className
    );

    return (
      <li {...props} className={classes} id={id}>
        <Button className={`${CLASS_ROOT}__button`} plain={true}
          role='tab' aria-selected={active}
          onClick={this._onClickTab} aria-expanded={active}>
          <label className={`${CLASS_ROOT}__label`} htmlFor={id}>
            {title}
          </label>
        </Button>
      </li>
    );
  }
}

Tab.propTypes = {
  title: PropTypes.node.isRequired,
  active: PropTypes.bool,
  id: PropTypes.string,
  onRequestForActive: PropTypes.func // from Tabs
};

Switch label prop type to node


(function(root, factory) {
    'use strict';
    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.

    /* istanbul ignore next */
    if (typeof define === 'function' && define.amd) {
        define('stack-generator', ['stackframe'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('stackframe'));
    } else {
        root.StackGenerator = factory(root.StackFrame);
    }
}(this, function(StackFrame) {
    return {
        backtrace: function StackGenerator$$backtrace(opts) {
            var stack = [];
            var maxStackSize = 10;

            if (typeof opts === 'object' && typeof opts.maxStackSize === 'number') {
                maxStackSize = opts.maxStackSize;
            }

            var curr = arguments.callee;
            while (curr && stack.length < maxStackSize) {
                if (!curr['arguments']) {
                  break;
                }
                // Allow V8 optimizations
                var args = new Array(curr['arguments'].length);
                for (var i = 0; i < args.length; ++i) {
                    args[i] = curr['arguments'][i];
                }
                if (/function(?:\s+([\w$]+))+\s*\(/.test(curr.toString())) {
                    stack.push(new StackFrame({functionName: RegExp.$1 || undefined, args: args}));
                } else {
                    stack.push(new StackFrame({args: args}));
                }

                try {
                    curr = curr.caller;
                } catch (e) {
                    break;
                }
            }
            return stack;
        }
    };
}));

(function(root, factory) {
    'use strict';
    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.

    /* istanbul ignore next */
    if (typeof define === 'function' && define.amd) {
        define('stack-generator', ['stackframe'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('stackframe'));
    } else {
        root.StackGenerator = factory(root.StackFrame);
    }
}(this, function(StackFrame) {
    return {
        backtrace: function StackGenerator$$backtrace(opts) {
            var stack = [];
            var maxStackSize = 10;

            if (typeof opts === 'object' && typeof opts.maxStackSize === 'number') {
                maxStackSize = opts.maxStackSize;
            }

            var curr = arguments.callee;
            while (curr && stack.length < maxStackSize && curr['arguments']) {
                // Allow V8 optimizations
                var args = new Array(curr['arguments'].length);
                for (var i = 0; i < args.length; ++i) {
                    args[i] = curr['arguments'][i];
                }
                if (/function(?:\s+([\w$]+))+\s*\(/.test(curr.toString())) {
                    stack.push(new StackFrame({functionName: RegExp.$1 || undefined, args: args}));
                } else {
                    stack.push(new StackFrame({args: args}));
                }

                try {
                    curr = curr.caller;
                } catch (e) {
                    break;
                }
            }
            return stack;
        }
    };
}));

Add falsey arguments check to loop condition