<commit_before>var crypto = require('crypto');

exports.id = '1a';

// env-specific crypto methods
exports.crypt = function(ecc,aes)
{
  crypto.ecc = ecc;
  crypto.aes = aes;
}

exports.generate = function(cb)
{
  try {
    var k = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1);
  }catch(E){
    return cb(E);
  }
  cb(null, {key:k.PublicKey, secret:k.PrivateKey});
}

exports.Local = function(pair)
{
  var self = this;
  try{
    self.key = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, pair.key, true);
    self.secret = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, pair.secret);
    if(self.key.PublicKey.toString() != pair.key.toString()) throw new Error('invalid public key data');
    if(self.secret.PrivateKey.toString() != pair.secret.toString()) throw new Error('invalid secret key data');
  }catch(E){
    self.err = E;
  }

  // decrypt message body and return the inner
  self.decrypt = function(body){
    if(!Buffer.isBuffer(body)) return false;
    if(body.length < 21+4+4) return false;

    var keybuf = body.slice(0,21);
    var iv = body.slice(21,21+4);
    var innerc = body.slice(21+4,body.length-4);
    // mac is handled during verify stage

    try{
      var ephemeral = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, keybuf, true);
      var secret = self.secret.deriveSharedSecret(ephemeral);
    }catch(E){
      return false;
    }

    var key = fold(1,crypto.createHash("sha256").update(secret).digest());
    var ivz = new Buffer(12);
    ivz.fill(0);

    // aes-128 decipher the inner
    try{
      var inner = crypto.aes(false, key, Buffer.concat([iv,ivz]), innerc);
    }catch(E){
      return false;
    }

    return inner;
  };
}

exports.Remote = function(key)
{
  var self = this;
  try{
    self.endpoint = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, key, true);
    self.ephemeral = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1);
    self.token = crypto.createHash('sha256').update(self.ephemeral.PublicKey.slice(0,16)).digest().slice(0,16);
    self.seq = crypto.randomBytes(4).readUInt32LE(0); // start from random place
  }catch(E){
    self.err = E;
  }

  // verifies the hmac on an incoming message body
  self.verify = function(local, body){
    if(!Buffer.isBuffer(body)) return false;

    // derive shared secret from both identity keys
    var secret = local.secret.deriveSharedSecret(self.endpoint);

    // hmac key is the secret and seq bytes combined to make it unique each time
    var iv = body.slice(21,21+4);
    var mac = fold(3,crypto.createHmac("sha256", Buffer.concat([secret,iv])).update(body.slice(0,body.length-4)).digest());
    if(mac.toString('hex') != body.slice(body.length-4).toString('hex')) return false;

    return true;
  };

  self.encrypt = function(local, inner){
    if(!Buffer.isBuffer(inner)) return false;

    // get the shared secret to create the iv+key for the open aes
    try{
      var secret = self.ephemeral.deriveSharedSecret(self.endpoint);
    }catch(E){
      return false;
    }
    var key = fold(1,crypto.createHash("sha256").update(secret).digest());
    var iv = new Buffer(4);
    iv.writeUInt32LE(self.seq++,0);
    var ivz = new Buffer(12);
    ivz.fill(0);

    // encrypt the inner
    try{
      var innerc = crypto.aes(true, key, Buffer.concat([iv,ivz]), inner);
      var macsecret = local.secret.deriveSharedSecret(self.endpoint);
    }catch(E){
      return false;
    }

    // prepend the key and hmac it
    var macd = Buffer.concat([self.ephemeral.PublicKey,iv,innerc]);
    // key is the secret and seq bytes combined
    var hmac = fold(3,crypto.createHmac("sha256", Buffer.concat([macsecret,iv])).update(macd).digest());

    // create final message body
    return Buffer.concat([macd,hmac]);
  };

}

exports.Ephemeral = function(remote, body)
{
  var self = this;

  self.seq = crypto.randomBytes(4).readUInt32LE(0); // start from random place

  try{
    // sender token
    self.token = crypto.createHash('sha256').update(body.slice(0,16)).digest().slice(0,16);

    // extract received ephemeral key
    var key = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, body.slice(0,21), true);

    // get shared secret to make channel keys
    var secret = remote.ephemeral.deriveSharedSecret(key);
    self.encKey = fold(1,crypto.createHash("sha256")
      .update(secret)
      .update(remote.ephemeral.PublicKey)
      .update(key.PublicKey)
      .digest());
    self.decKey = fold(1,crypto.createHash("sha256")
      .update(secret)
      .update(key.PublicKey)
      .update(remote.ephemeral.PublicKey)
      .digest());
  }catch(E){
    self.err = E;
  }

  self.decrypt = function(outer){
    // extract the three buffers
    var seq = outer.slice(0,4);
    var cbody = outer.slice(4,outer.length-4);
    var mac1 = outer.slice(outer.length-4);

    // validate the hmac
    var key = Buffer.concat([self.decKey,seq]);
    var mac2 = fold(3,crypto.createHmac("sha256", key).update(cbody).digest());
    if(mac1.toString('hex') != mac2.toString('hex')) return false;

    // decrypt body
    var ivz = new Buffer(12);
    ivz.fill(0);
    try{
      var body = crypto.aes(false,self.decKey,Buffer.concat([seq,ivz]),cbody);
    }catch(E){
      return false;
    }
    return body;
  };

  self.encrypt = function(inner){
    // now encrypt the packet
    var iv = new Buffer(16);
    iv.fill(0);
    iv.writeUInt32LE(self.seq++,0);

    var cbody = crypto.aes(true, self.encKey, iv, inner);

    // create the hmac
    var key = Buffer.concat([self.encKey,iv.slice(0,4)]);
    var mac = fold(3,crypto.createHmac("sha256", key).update(cbody).digest());

    // return final body
    return Buffer.concat([iv.slice(0,4),cbody,mac]);
  };
}

// simple xor buffer folder
function fold(count, buf)
{
  if(!count || buf.length % 2) return buf;
  var ret = buf.slice(0,buf.length/2);
  for(i = 0; i < ret.length; i++) ret[i] = ret[i] ^ buf[i+ret.length];
  return fold(count-1,ret);
}

<commit_msg>declare i in for loop to avoid global leak<commit_after>var crypto = require('crypto');

exports.id = '1a';

// env-specific crypto methods
exports.crypt = function(ecc,aes)
{
  crypto.ecc = ecc;
  crypto.aes = aes;
}

exports.generate = function(cb)
{
  try {
    var k = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1);
  }catch(E){
    return cb(E);
  }
  cb(null, {key:k.PublicKey, secret:k.PrivateKey});
}

exports.Local = function(pair)
{
  var self = this;
  try{
    self.key = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, pair.key, true);
    self.secret = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, pair.secret);
    if(self.key.PublicKey.toString() != pair.key.toString()) throw new Error('invalid public key data');
    if(self.secret.PrivateKey.toString() != pair.secret.toString()) throw new Error('invalid secret key data');
  }catch(E){
    self.err = E;
  }

  // decrypt message body and return the inner
  self.decrypt = function(body){
    if(!Buffer.isBuffer(body)) return false;
    if(body.length < 21+4+4) return false;

    var keybuf = body.slice(0,21);
    var iv = body.slice(21,21+4);
    var innerc = body.slice(21+4,body.length-4);
    // mac is handled during verify stage

    try{
      var ephemeral = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, keybuf, true);
      var secret = self.secret.deriveSharedSecret(ephemeral);
    }catch(E){
      return false;
    }

    var key = fold(1,crypto.createHash("sha256").update(secret).digest());
    var ivz = new Buffer(12);
    ivz.fill(0);

    // aes-128 decipher the inner
    try{
      var inner = crypto.aes(false, key, Buffer.concat([iv,ivz]), innerc);
    }catch(E){
      return false;
    }

    return inner;
  };
}

exports.Remote = function(key)
{
  var self = this;
  try{
    self.endpoint = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, key, true);
    self.ephemeral = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1);
    self.token = crypto.createHash('sha256').update(self.ephemeral.PublicKey.slice(0,16)).digest().slice(0,16);
    self.seq = crypto.randomBytes(4).readUInt32LE(0); // start from random place
  }catch(E){
    self.err = E;
  }

  // verifies the hmac on an incoming message body
  self.verify = function(local, body){
    if(!Buffer.isBuffer(body)) return false;

    // derive shared secret from both identity keys
    var secret = local.secret.deriveSharedSecret(self.endpoint);

    // hmac key is the secret and seq bytes combined to make it unique each time
    var iv = body.slice(21,21+4);
    var mac = fold(3,crypto.createHmac("sha256", Buffer.concat([secret,iv])).update(body.slice(0,body.length-4)).digest());
    if(mac.toString('hex') != body.slice(body.length-4).toString('hex')) return false;

    return true;
  };

  self.encrypt = function(local, inner){
    if(!Buffer.isBuffer(inner)) return false;

    // get the shared secret to create the iv+key for the open aes
    try{
      var secret = self.ephemeral.deriveSharedSecret(self.endpoint);
    }catch(E){
      return false;
    }
    var key = fold(1,crypto.createHash("sha256").update(secret).digest());
    var iv = new Buffer(4);
    iv.writeUInt32LE(self.seq++,0);
    var ivz = new Buffer(12);
    ivz.fill(0);

    // encrypt the inner
    try{
      var innerc = crypto.aes(true, key, Buffer.concat([iv,ivz]), inner);
      var macsecret = local.secret.deriveSharedSecret(self.endpoint);
    }catch(E){
      return false;
    }

    // prepend the key and hmac it
    var macd = Buffer.concat([self.ephemeral.PublicKey,iv,innerc]);
    // key is the secret and seq bytes combined
    var hmac = fold(3,crypto.createHmac("sha256", Buffer.concat([macsecret,iv])).update(macd).digest());

    // create final message body
    return Buffer.concat([macd,hmac]);
  };

}

exports.Ephemeral = function(remote, body)
{
  var self = this;

  self.seq = crypto.randomBytes(4).readUInt32LE(0); // start from random place

  try{
    // sender token
    self.token = crypto.createHash('sha256').update(body.slice(0,16)).digest().slice(0,16);

    // extract received ephemeral key
    var key = new crypto.ecc.ECKey(crypto.ecc.ECCurves.secp160r1, body.slice(0,21), true);

    // get shared secret to make channel keys
    var secret = remote.ephemeral.deriveSharedSecret(key);
    self.encKey = fold(1,crypto.createHash("sha256")
      .update(secret)
      .update(remote.ephemeral.PublicKey)
      .update(key.PublicKey)
      .digest());
    self.decKey = fold(1,crypto.createHash("sha256")
      .update(secret)
      .update(key.PublicKey)
      .update(remote.ephemeral.PublicKey)
      .digest());
  }catch(E){
    self.err = E;
  }

  self.decrypt = function(outer){
    // extract the three buffers
    var seq = outer.slice(0,4);
    var cbody = outer.slice(4,outer.length-4);
    var mac1 = outer.slice(outer.length-4);

    // validate the hmac
    var key = Buffer.concat([self.decKey,seq]);
    var mac2 = fold(3,crypto.createHmac("sha256", key).update(cbody).digest());
    if(mac1.toString('hex') != mac2.toString('hex')) return false;

    // decrypt body
    var ivz = new Buffer(12);
    ivz.fill(0);
    try{
      var body = crypto.aes(false,self.decKey,Buffer.concat([seq,ivz]),cbody);
    }catch(E){
      return false;
    }
    return body;
  };

  self.encrypt = function(inner){
    // now encrypt the packet
    var iv = new Buffer(16);
    iv.fill(0);
    iv.writeUInt32LE(self.seq++,0);

    var cbody = crypto.aes(true, self.encKey, iv, inner);

    // create the hmac
    var key = Buffer.concat([self.encKey,iv.slice(0,4)]);
    var mac = fold(3,crypto.createHmac("sha256", key).update(cbody).digest());

    // return final body
    return Buffer.concat([iv.slice(0,4),cbody,mac]);
  };
}

// simple xor buffer folder
function fold(count, buf)
{
  if(!count || buf.length % 2) return buf;
  var ret = buf.slice(0,buf.length/2);
  for(var i = 0; i < ret.length; i++) ret[i] = ret[i] ^ buf[i+ret.length];
  return fold(count-1,ret);
}
<|endoftext|>


<commit_before>import React from 'react'
import * as ToccoUI from 'tocco-ui'
import './styles.scss'
import {SearchForm} from './SearchForm'

export class EntityBrowser extends React.Component {
  constructor(props) {
    super()
    props.initializeTable()
  }

  onOrderByChange = orderBy => {
    this.props.setOrderBy(orderBy)
  }

  onPageChange = page => {
    this.props.changePage(page)
  }

  cellRenderer = (v, r) => {
    // temporary solution. Should display combined values as well
    if (v.length > 0) {
      return <ToccoUI.FormattedValue type={v[0].type} value={v[0].value}/>
    } else {
      return
    }
  }

  render() {
    const props = this.props

    return (

EntityBrowser

        <SearchForm
          formDefinition={props.searchFormDefinition}
          setSearchTerm={props.setSearchTerm}
        />

        <ToccoUI.Table
          columnDefinitions={props.columnDefinitions}
          records={props.records}
          className="table-striped"
          onOrderByChange={this.onOrderByChange}
          orderBy={props.orderBy}
          loading={props.recordRequestInProgress}
          cellRenderer={this.cellRenderer}
        />
        <ToccoUI.Pagination
          totalRecords={props.recordCount}
          recordsPerPage={props.limit}
          onPageChange={this.onPageChange}
          currentPage={props.currentPage}
        />
        <button onClick={props.resetDataSet}>Reset</button>

    )
  }
}

EntityBrowser.propTypes = {
  initializeTable: React.PropTypes.func.isRequired,
  changePage: React.PropTypes.func.isRequired,
  records: React.PropTypes.array,
  currentPage: React.PropTypes.number,
  limit: React.PropTypes.number,
  searchFormDefinition: React.PropTypes.array,
  columnDefinitions: React.PropTypes.array,
  recordCount: React.PropTypes.number,
  setOrderBy: React.PropTypes.func,
  orderBy: React.PropTypes.object.isRequired,
  resetDataSet: React.PropTypes.func,
  recordRequestInProgress: React.PropTypes.bool,
  setSearchTerm: React.PropTypes.func
}
<commit_msg>refactor(entity-browser): support combined field values<commit_after>import React from 'react'
import * as ToccoUI from 'tocco-ui'
import './styles.scss'
import {SearchForm} from './SearchForm'

export class EntityBrowser extends React.Component {
  constructor(props) {
    super()
    props.initializeTable()
  }

  onOrderByChange = orderBy => {
    this.props.setOrderBy(orderBy)
  }

  onPageChange = page => {
    this.props.changePage(page)
  }

  cellRenderer = (values, r) => (

      {
        values.map((v, idx) => {
          return <ToccoUI.FormattedValue key={idx} type={v.type} value={v.value}/>
        })
      }

  )

  render() {
    const props = this.props

    return (

EntityBrowser

        <SearchForm
          formDefinition={props.searchFormDefinition}
          setSearchTerm={props.setSearchTerm}
        />

        <ToccoUI.Table
          columnDefinitions={props.columnDefinitions}
          records={props.records}
          className="table-striped"
          onOrderByChange={this.onOrderByChange}
          orderBy={props.orderBy}
          loading={props.recordRequestInProgress}
          cellRenderer={this.cellRenderer}
        />
        <ToccoUI.Pagination
          totalRecords={props.recordCount}
          recordsPerPage={props.limit}
          onPageChange={this.onPageChange}
          currentPage={props.currentPage}
        />
        <button onClick={props.resetDataSet}>Reset</button>

    )
  }
}

EntityBrowser.propTypes = {
  initializeTable: React.PropTypes.func.isRequired,
  changePage: React.PropTypes.func.isRequired,
  records: React.PropTypes.array,
  currentPage: React.PropTypes.number,
  limit: React.PropTypes.number,
  searchFormDefinition: React.PropTypes.array,
  columnDefinitions: React.PropTypes.array,
  recordCount: React.PropTypes.number,
  setOrderBy: React.PropTypes.func,
  orderBy: React.PropTypes.object.isRequired,
  resetDataSet: React.PropTypes.func,
  recordRequestInProgress: React.PropTypes.bool,
  setSearchTerm: React.PropTypes.func
}
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var Wire = function() {

    var signal = false;

    var observers = [];

    this.getSignal = function() {
	return signal;
    };

    this.setSignal = function(s) {
	var n = observers.length;
	signal = !!s;
	for (var i=0;i<n;++i)
	    observers[i](signal);
    };

    this.onChange = function(f) {
	observers.push(f);
	f(signal);
    };

    return this;
};

var Inverter = function(input,output) {

    input.onChange(function(signal) {
	output.setSignal(!signal);
    });

    return this;
};

var makeWire = function() {
    return new Wire();
};

var makeInverter = function(input,output) {
    return new Inverter(input,output);
};

describe('circuit simulator', function() {
    it('wires have a signal', function() {
	var w = makeWire();
	assert.equal(false, w.getSignal());

	w.setSignal(true);
	assert.equal(true, w.getSignal());
    });

    it('has an inverter', function() {
	var input = makeWire();
	var output = makeWire();

	var inverter = makeInverter(input,output);

	assert(output.getSignal());

	input.setSignal(false);
	assert(!output.getSignal());
    });
});
<commit_msg>thats because I am stupid<commit_after>"use strict";

var assert = require('assert');

var Wire = function() {

    var signal = false;

    var observers = [];

    this.getSignal = function() {
	return signal;
    };

    this.setSignal = function(s) {
	var n = observers.length;
	signal = !!s;
	for (var i=0;i<n;++i)
	    observers[i](signal);
    };

    this.onChange = function(f) {
	observers.push(f);
	f(signal);
    };

    return this;
};

var Inverter = function(input,output) {

    input.onChange(function(signal) {
	output.setSignal(!signal);
    });

    return this;
};

var makeWire = function() {
    return new Wire();
};

var makeInverter = function(input,output) {
    return new Inverter(input,output);
};

describe('circuit simulator', function() {
    it('wires have a signal', function() {
	var w = makeWire();
	assert.equal(false, w.getSignal());

	w.setSignal(true);
	assert.equal(true, w.getSignal());
    });

    it('has an inverter', function() {
	var input = makeWire();
	var output = makeWire();

	var inverter = makeInverter(input,output);

	assert(output.getSignal());

	input.setSignal(true);
	assert(!output.getSignal());
    });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
    Text,
    TouchableWithoutFeedback,
    View
} from 'react-native';
import { CardSection } from './common';

class ListItem extends Component {
    onRowPress() {
        console.log("Pressed");
    }

    renderLecturers() {
        let lecturersArray = JSON.parse(this.props.eventLecturers);
        let lecturersString = '';
        for (let lecturer in lecturersArray) {
            lecturersString = lecturersString + lecturersArray[lecturer];
            if (!(lecturer + 1) == lecturersArray.length) {  // Check if it's NOT the last iteration
                lecturersString = lecturersString + ', ';
            }
        }
        return lecturersString;
    }

    render() {
        return (
            <TouchableWithoutFeedback onPress={this.onRowPress.bind(this)}>
                <View style={{ flexDirection: "row" }}>
                    <CardSection style={styles.cardSection}>
                        <Text>{this.props.timeslot}</Text>
                    </CardSection>
                    <CardSection style={styles.cardSection}>
                        <Text style={styles.title}>
                            {this.props.eventName}
                        </Text>
                        <Text style={styles.title}>
                            {this.props.eventRoom}
                        </Text>
                        <Text style={styles.title}>
                            {this.renderLecturers()}
                        </Text>
                        <Text style={styles.title}>
                            {this.props.eventNote}
                        </Text>
                    </CardSection>
                </View>
            </TouchableWithoutFeedback>
        );
    }
}

const styles = {
    cardSection: {
        flexDirection: "column"
    },
    title: {
        fontSize: 18,
        paddingLeft: 15
    }
};

export default ListItem;
<commit_msg>Made the 'CardSection(s)' in the 'ListItem' component to full-width<commit_after>import React, { Component } from 'react';
import {
    Text,
    TouchableWithoutFeedback,
    View
} from 'react-native';
import { CardSection } from './common';

class ListItem extends Component {
    onRowPress() {
        console.log("Pressed");
    }

    renderLecturers() {
        let lecturersArray = JSON.parse(this.props.eventLecturers);
        let lecturersString = '';
        for (let lecturer in lecturersArray) {
            lecturersString = lecturersString + lecturersArray[lecturer];
            if (!(lecturer + 1) == lecturersArray.length) {  // Check if it's NOT the last iteration
                lecturersString = lecturersString + ', ';
            }
        }
        return lecturersString;
    }

    render() {
        return (
            <TouchableWithoutFeedback onPress={this.onRowPress.bind(this)}>
                <View style={{ flexDirection: "row" }}>
                    <CardSection>
                        <Text>{this.props.slot}</Text>
                    </CardSection>
                    <CardSection style={styles.cardSection}>
                        <Text style={styles.title}>
                            {this.props.eventName}
                        </Text>
                        <Text style={styles.title}>
                            {this.props.eventRoom}
                        </Text>
                        <Text style={styles.title}>
                            {this.renderLecturers()}
                        </Text>
                        <Text style={styles.title}>
                            {this.props.eventNote}
                        </Text>
                    </CardSection>
                </View>
            </TouchableWithoutFeedback>
        );
    }
}

const styles = {
    cardSection: {
        flex: 1,
        flexDirection: "column"
    },
    title: {
        fontSize: 18,
        paddingLeft: 15
    }
};

export default ListItem;
<|endoftext|>


<commit_before>/**
	maintain heads up display

	@namespace GAS
	@class hud
**/

GAS.hud = {

	PROSE_EFFECT_TIME: 250,
	CONTINUE_TIMEOUT: 5000,
	RATING_TIMEOUT: 5000,

	CONTINUE_HTML: "
SPACE Continue
",

	/**
		establish jQuery shells around UI DOM objects &
		assign methods for simple behaviors (resize, etc)

		@method init
	**/

	init: function() {

		this.dom = {
			window: jQuery(window),
			body: jQuery(document.body),

			fader: jQuery("#fader"),

			tracker: jQuery("#tracker"),

			prompts: {
				box: jQuery("#prompts")
			},

			prose: {
				box: jQuery("#prose"),
				text: jQuery("#prose-text"),
				cont: jQuery("#prose-cont"),
				warn: false
			},

			progress: {
				box: jQuery("#progress"),
				bar: jQuery("#progress-bar")
			},

			rating: {
				box: jQuery("#rating"),
				time: 0
			},

			debug: jQuery("#debug")
		};

		this.dom.prompts.resize = function() {
			var p = GAS.hud.dom.prompts.box;
			p.offset({
				top: (GAS.display.height - p.height()) * 0.85,
				left: (GAS.display.width - p.width()) * 0.5
			});
		};

		this.dom.window.bind("keydown", this.onKeyDown);
		this.dom.window.bind("resize", this.resize);
		this.resize();
	},

	/**
		add to debug window

		@method debug
	**/

	debug: function(msg) {
		this.dom.debug.html(msg);
	},

	/**
		clear debug window

		@method clearDebug
	**/

	clearDebug: function() {
		this.dom.debug.html("");
	},

	/**
		adjust UI elements in response to browser window resize

		some elements are attached to the edges via CSS, and do
		not require manual resizing or recentering

		@method resize
	**/

	resize: function() {
		var that = GAS.hud;

		that.dom.fader.width(GAS.display.width);
		that.dom.fader.height(GAS.display.height);

		that.dom.tracker.width(GAS.display.width);
		that.dom.tracker.height(GAS.display.height);

		that.dom.prompts.resize();
	},

	/**
		handle a keypress

		note that the hud object only handles keys related to
		HUD activity. see player.js for motion control keys

		@method onKeyDown
		@param event browser object containing event information
		@return true to enable default key behavior
	**/

	onKeyDown: function(event) {
		var that = GAS.hud;

		switch(event.keyCode) {
		case SOAR.KEY.ESCAPE:
			break;
		case SOAR.KEY.PAUSE:
			// toggle pause (dark screen/no updates)
			if (SOAR.running) {
				that.setCurtain(0.75);
				SOAR.running = false;
			} else {
				that.setCurtain(0);
				SOAR.running = true;
			}
			break;
		case SOAR.KEY.TAB:
			// prevent accidental TAB keypress from changing focus
			return false;
			break;
		case SOAR.KEY.E:
			// interact with something if prompted to do so
			if (that.dom.prompts.object && SOAR.running) {
				// activate the interface
				that.dom.prompts.object.interact();
				// turn off the prompt
				that.prompt();
			}
			break;
		case SOAR.KEY.SPACE:
			// if prose box is active
			if (that.dom.prose.warn && SOAR.running) {
				// continue the plot
				GAS.game.advance();
			}
			break;
		default:
			//console.log(event.keyCode);
			break;
		}
		return true;
	},

	/**
		update any HUD animations

		called on every animation frame

		@method update
	**/

	update: function() {
		var prose = this.dom.prose;
		var rating = this.dom.rating;

		// if the height of the prose box doesn't match the height of its content
		var inht = prose.text.height();
		var outh = prose.box.height();
		if (Math.abs(inht - outh) > 0.5) {
			// ease it into the correct size
			prose.box.height(outh + (inht - outh) * 0.1 );
		}

		// if the prose box is too small to display, hide it
		var disp = prose.box.css("visibility");
		if (outh === 0 && disp === "visible") {
			prose.box.css("visibility", "hidden");
		}
		if (outh !== 0 && disp === "hidden") {
			prose.box.css("visibility", "visible");
		}

		// if prose continue warning is set
		if (prose.warn) {
			// update the timeout value
			prose.time -= SOAR.interval;
			// if timeout is under a second
			if (prose.time < 1000) {
				// blink the continue indicator at half-second intervals
				if (prose.time > 500) {
					prose.cont.css("visibility", "visible");
				} else {
					prose.cont.css("visibility", "hidden");
					if (prose.time < 0) {
						prose.time = 1000;
					}
				}
			}
		}

		// if a rating is displayed
		if (rating.time > 0) {
			rating.time -= SOAR.interval;
			if (rating.time <= 0) {
				rating.box.fadeTo(250, 0);
			}
		}
	},

	/**
		change mouse tracker opacity

		mouse tracker div sits on top of all other HUD elements.
		use for "disable" effects

		@method setCurtain
		@param opacity number, transparency value (0..1)
	**/

	setCurtain: function(opacity) {
		this.dom.tracker.css("background-color", "rgba(0, 0, 0, " + opacity + ")");
	},

	/**
		control fader, then advance game plot

		fader sits on top of GL canvas
		use for situations where the rendered scene is to be faded in/out

		@method setFade
		@param time number, fade time in ms
		@param opacity number, transparency value (0..1)
	**/

	setFade: function(time, opacity) {
		this.dom.fader.fadeTo(time, opacity, function() {
			GAS.game.advance()
		});
	},

	/**
		display prompt with specified message
		if no object specified, remove prompt

		@method prompt
		@method object object, what the prompt applies to
		@param verb string, what action is implied
		@param name string, who action applies to
	**/

	prompt: function(object, verb, name) {
		var pr = this.dom.prompts;

		if (object) {
			pr.box.html("
E " + verb + "

" + name + "
");
			pr.resize();
			pr.box.css("visibility", "visible");
			pr.object = object;
		} else {
			pr.box.css("visibility", "hidden");
			delete pr.object;
		}
	},

	/**
		display a line in the prose box with optional continue
		will hide the prose box if the text is unspecified

		@method showProse
		@param text string, the line to display
		@param cont boolean, true if continue prompt is to be shown
	**/

	showProse: function(text, cont) {
		var prose = this.dom.prose;
		if (text) {
			if (cont) {
				prose.text.html(text + this.CONTINUE_HTML);
				prose.cont = jQuery("#prose-cont");
			} else {
				prose.text.html(text);
			}
			prose.warn = cont;
			prose.time = this.CONTINUE_TIMEOUT;
		} else {
			prose.text.html("");
			prose.warn = false;
		}
	},

	/**
		displays minigame instructions in the speech box

		@method showInstructions
		@param text string, the instructions
		@param help string, any feedback or help info
	**/

	showInstructions: function(text, help) {
		var speech = this.dom.speech;
		var str = text;
		if (help) {
			str += "

" + help + "
";
		}
		// display without continue
		this.showProse(str, false);
	},

	/**
		show/hide/update progress bar

		if value is non-negative and the bar isn't visible,
		it will be displayed. if value is negative, and the
		bar is visible, it will be hidden.

		@method showProgress
		@param value number, value to display, range {0..1}
	**/

	showProgress: function(value) {
		var prog = this.dom.progress;

		// if value is non-negative
		if (value >= 0) {
			// if the bar isn't visible
			if (!prog.visible) {
				// show it
				prog.box.show();
				prog.visible = true;
			}
			// update the bar's width (as a percentage of parent)
			prog.bar.css("width", Math.floor(100 * value) + "%");
		} else {
			// negative, hide the bar
			prog.box.hide();
			prog.visible = false;
		}
	},

	/**
		show rating

		displays the rating and starts the fade timer.

		@method showRating
		@param score number, how many stars to display (1-5)
	**/

	showRating: function(score) {
		var rating = this.dom.rating;
		var i, s = "";

		// generate the star string
		for (i = 0; i < score; i++) {
			s = s + "★ "
		}
		rating.box.html(s);
		// set timeout
		rating.time = this.RATING_TIMEOUT;
		// and display rating
		rating.box.fadeTo(250, 1);
	}

};
<commit_msg>Fixes prose box hiding on FF.<commit_after>/**
	maintain heads up display

	@namespace GAS
	@class hud
**/

GAS.hud = {

	PROSE_EFFECT_TIME: 250,
	CONTINUE_TIMEOUT: 5000,
	RATING_TIMEOUT: 5000,

	CONTINUE_HTML: "
SPACE Continue
",

	/**
		establish jQuery shells around UI DOM objects &
		assign methods for simple behaviors (resize, etc)

		@method init
	**/

	init: function() {

		this.dom = {
			window: jQuery(window),
			body: jQuery(document.body),

			fader: jQuery("#fader"),

			tracker: jQuery("#tracker"),

			prompts: {
				box: jQuery("#prompts")
			},

			prose: {
				box: jQuery("#prose"),
				text: jQuery("#prose-text"),
				cont: jQuery("#prose-cont"),
				warn: false
			},

			progress: {
				box: jQuery("#progress"),
				bar: jQuery("#progress-bar")
			},

			rating: {
				box: jQuery("#rating"),
				time: 0
			},

			debug: jQuery("#debug")
		};

		this.dom.prompts.resize = function() {
			var p = GAS.hud.dom.prompts.box;
			p.offset({
				top: (GAS.display.height - p.height()) * 0.85,
				left: (GAS.display.width - p.width()) * 0.5
			});
		};

		this.dom.window.bind("keydown", this.onKeyDown);
		this.dom.window.bind("resize", this.resize);
		this.resize();
	},

	/**
		add to debug window

		@method debug
	**/

	debug: function(msg) {
		this.dom.debug.html(msg);
	},

	/**
		clear debug window

		@method clearDebug
	**/

	clearDebug: function() {
		this.dom.debug.html("");
	},

	/**
		adjust UI elements in response to browser window resize

		some elements are attached to the edges via CSS, and do
		not require manual resizing or recentering

		@method resize
	**/

	resize: function() {
		var that = GAS.hud;

		that.dom.fader.width(GAS.display.width);
		that.dom.fader.height(GAS.display.height);

		that.dom.tracker.width(GAS.display.width);
		that.dom.tracker.height(GAS.display.height);

		that.dom.prompts.resize();
	},

	/**
		handle a keypress

		note that the hud object only handles keys related to
		HUD activity. see player.js for motion control keys

		@method onKeyDown
		@param event browser object containing event information
		@return true to enable default key behavior
	**/

	onKeyDown: function(event) {
		var that = GAS.hud;

		switch(event.keyCode) {
		case SOAR.KEY.ESCAPE:
			break;
		case SOAR.KEY.PAUSE:
			// toggle pause (dark screen/no updates)
			if (SOAR.running) {
				that.setCurtain(0.75);
				SOAR.running = false;
			} else {
				that.setCurtain(0);
				SOAR.running = true;
			}
			break;
		case SOAR.KEY.TAB:
			// prevent accidental TAB keypress from changing focus
			return false;
			break;
		case SOAR.KEY.E:
			// interact with something if prompted to do so
			if (that.dom.prompts.object && SOAR.running) {
				// activate the interface
				that.dom.prompts.object.interact();
				// turn off the prompt
				that.prompt();
			}
			break;
		case SOAR.KEY.SPACE:
			// if prose box is active
			if (that.dom.prose.warn && SOAR.running) {
				// continue the plot
				GAS.game.advance();
			}
			break;
		default:
			//console.log(event.keyCode);
			break;
		}
		return true;
	},

	/**
		update any HUD animations

		called on every animation frame

		@method update
	**/

	update: function() {
		var prose = this.dom.prose;
		var rating = this.dom.rating;

		// if the height of the prose box doesn't match the height of its content
		var inht = prose.text.height();
		var outh = prose.box.height();
		if (Math.abs(inht - outh) > 0.5) {
			// ease it into the correct size
			prose.box.height(outh + (inht - outh) * 0.1 );
		}

		// if the prose box is too small to display, hide it
		var disp = prose.box.css("visibility");
		if (outh < 10 && disp === "visible") {
			prose.box.css("visibility", "hidden");
		}
		if (outh >= 10 && disp === "hidden") {
			prose.box.css("visibility", "visible");
		}

		// if prose continue warning is set
		if (prose.warn) {
			// update the timeout value
			prose.time -= SOAR.interval;
			// if timeout is under a second
			if (prose.time < 1000) {
				// blink the continue indicator at half-second intervals
				if (prose.time > 500) {
					prose.cont.css("visibility", "visible");
				} else {
					prose.cont.css("visibility", "hidden");
					if (prose.time < 0) {
						prose.time = 1000;
					}
				}
			}
		}

		// if a rating is displayed
		if (rating.time > 0) {
			rating.time -= SOAR.interval;
			if (rating.time <= 0) {
				rating.box.fadeTo(250, 0);
			}
		}
	},

	/**
		change mouse tracker opacity

		mouse tracker div sits on top of all other HUD elements.
		use for "disable" effects

		@method setCurtain
		@param opacity number, transparency value (0..1)
	**/

	setCurtain: function(opacity) {
		this.dom.tracker.css("background-color", "rgba(0, 0, 0, " + opacity + ")");
	},

	/**
		control fader, then advance game plot

		fader sits on top of GL canvas
		use for situations where the rendered scene is to be faded in/out

		@method setFade
		@param time number, fade time in ms
		@param opacity number, transparency value (0..1)
	**/

	setFade: function(time, opacity) {
		this.dom.fader.fadeTo(time, opacity, function() {
			GAS.game.advance()
		});
	},

	/**
		display prompt with specified message
		if no object specified, remove prompt

		@method prompt
		@method object object, what the prompt applies to
		@param verb string, what action is implied
		@param name string, who action applies to
	**/

	prompt: function(object, verb, name) {
		var pr = this.dom.prompts;

		if (object) {
			pr.box.html("
E " + verb + "

" + name + "
");
			pr.resize();
			pr.box.css("visibility", "visible");
			pr.object = object;
		} else {
			pr.box.css("visibility", "hidden");
			delete pr.object;
		}
	},

	/**
		display a line in the prose box with optional continue
		will hide the prose box if the text is unspecified

		@method showProse
		@param text string, the line to display
		@param cont boolean, true if continue prompt is to be shown
	**/

	showProse: function(text, cont) {
		var prose = this.dom.prose;
		if (text) {
			if (cont) {
				prose.text.html(text + this.CONTINUE_HTML);
				prose.cont = jQuery("#prose-cont");
			} else {
				prose.text.html(text);
			}
			prose.warn = cont;
			prose.time = this.CONTINUE_TIMEOUT;
		} else {
			prose.text.html("");
			prose.warn = false;
		}
	},

	/**
		displays minigame instructions in the speech box

		@method showInstructions
		@param text string, the instructions
		@param help string, any feedback or help info
	**/

	showInstructions: function(text, help) {
		var speech = this.dom.speech;
		var str = text;
		if (help) {
			str += "

" + help + "
";
		}
		// display without continue
		this.showProse(str, false);
	},

	/**
		show/hide/update progress bar

		if value is non-negative and the bar isn't visible,
		it will be displayed. if value is negative, and the
		bar is visible, it will be hidden.

		@method showProgress
		@param value number, value to display, range {0..1}
	**/

	showProgress: function(value) {
		var prog = this.dom.progress;

		// if value is non-negative
		if (value >= 0) {
			// if the bar isn't visible
			if (!prog.visible) {
				// show it
				prog.box.show();
				prog.visible = true;
			}
			// update the bar's width (as a percentage of parent)
			prog.bar.css("width", Math.floor(100 * value) + "%");
		} else {
			// negative, hide the bar
			prog.box.hide();
			prog.visible = false;
		}
	},

	/**
		show rating

		displays the rating and starts the fade timer.

		@method showRating
		@param score number, how many stars to display (1-5)
	**/

	showRating: function(score) {
		var rating = this.dom.rating;
		var i, s = "";

		// generate the star string
		for (i = 0; i < score; i++) {
			s = s + "★ "
		}
		rating.box.html(s);
		// set timeout
		rating.time = this.RATING_TIMEOUT;
		// and display rating
		rating.box.fadeTo(250, 1);
	}

};
<|endoftext|>


<commit_before>$(function() {
	var $globalBar = $( ".bar-yellow");

	if($globalBar.length > 0) {
	    window.globalBar = $globalBar.clone();
	    $globalBar.remove();
    	newbar();
  // }

  // else if($('.notification').hasClass('notification--white')) {
  //     $('.notification').removeClass('notification--white');
  //     $('.notification').addClass('notification--tan');
  //   }

  // else if($('.notification').hasClass('notification--tan')) {
  //     $('.notification').removeClass('notification--tan');
  //     $('.notification').addClass('notification--sky');
  //   }

  else {
    $('.notification').remove();
    $('body').prepend(window.globalBar);
  }

function newbar() {

    $("body").prepend(
     "<svg display='none' xmlns='http://www.w3.org/2000/svg'><defs><symbol id='icon-close' viewBox='0 0 100 100'><path d='M76.83,32.561L59.392,50L76.83,67.439c2.594,2.594,2.594,6.798,0,9.392 c-1.296,1.296-2.995,1.945-4.694,1.945c-1.7,0-3.399-0.648-4.695-1.945L50,59.391l-17.44,17.44 c-1.296,1.296-2.995,1.945-4.695,1.945c-1.699,0-3.397-0.648-4.694-1.945c-2.594-2.593-2.594-6.797,0-9.392L40.608,50L23.17,32.561 c-2.594-2.594-2.594-6.798,0-9.392c2.593-2.592,6.795-2.592,9.389,0L50,40.609l17.44-17.44c2.594-2.592,6.796-2.592,9.389,0C79.424,25.763,79.424,29.967,76.83,32.561z'></path></symbol></defs></svg>");

    $("body").prepend(
     "
Read the ePaper edition. The complete print edition, in its exact layout. Browse the print edition page by page, including stories and ads.<button class='notification__toggle'><svg class='icon-close'><use xlink:href='#icon-close' /></svg></button>
");

    $('.notification__toggle').click(function() {

      $('.notification').slideToggle('300');

      return false;

  });
  }

});<commit_msg>(ﾉ◕ヮ◕)ﾉ*:･ﾟ✧<commit_after>$(function() {
	var $globalBar = $( ".global-bar");

	if($globalBar.length > 0) {
	    window.globalBar = $globalBar.clone();
	    $globalBar.remove();
    	newbar();
  // }

  // else if($('.notification').hasClass('notification--white')) {
  //     $('.notification').removeClass('notification--white');
  //     $('.notification').addClass('notification--tan');
  //   }

  // else if($('.notification').hasClass('notification--tan')) {
  //     $('.notification').removeClass('notification--tan');
  //     $('.notification').addClass('notification--sky');
  //   }

  else {
    $('.notification').remove();
    $('body').prepend(window.globalBar);
  }

function newbar() {

    $("body").prepend(
     "<svg display='none' xmlns='http://www.w3.org/2000/svg'><defs><symbol id='icon-close' viewBox='0 0 100 100'><path d='M76.83,32.561L59.392,50L76.83,67.439c2.594,2.594,2.594,6.798,0,9.392 c-1.296,1.296-2.995,1.945-4.694,1.945c-1.7,0-3.399-0.648-4.695-1.945L50,59.391l-17.44,17.44 c-1.296,1.296-2.995,1.945-4.695,1.945c-1.699,0-3.397-0.648-4.694-1.945c-2.594-2.593-2.594-6.797,0-9.392L40.608,50L23.17,32.561 c-2.594-2.594-2.594-6.798,0-9.392c2.593-2.592,6.795-2.592,9.389,0L50,40.609l17.44-17.44c2.594-2.592,6.796-2.592,9.389,0C79.424,25.763,79.424,29.967,76.83,32.561z'></path></symbol></defs></svg>");

    $("body").prepend(
     "
Read the ePaper edition. The complete print edition, in its exact layout. Browse the print edition page by page, including stories and ads.<button class='notification__toggle'><svg class='icon-close'><use xlink:href='#icon-close' /></svg></button>
");

    $('.notification__toggle').click(function() {

      $('.notification').slideToggle('300');

      return false;

  });
  }

});<|endoftext|>


<commit_before>import React, { Component } from "react";
import * as PropTypes from "prop-types";

import { filter, group, mergeState, getModuleState } from "./model";

export default function WithState(WrappedComponent) {
  class SideNavWithState extends Component {
    static propTypes = {
      links: PropTypes.arrayOf(PropTypes.object),
      modules: PropTypes.arrayOf(
        PropTypes.shape({
          id: PropTypes.string.isRequired,
          title: PropTypes.string.isRequired
        })
      ),
      onModuleChange: PropTypes.func.isRequired,
      submodules: PropTypes.arrayOf(
        PropTypes.shape({
          id: PropTypes.string.isRequired,
          title: PropTypes.string.isRequired
        })
      ),
      activeModuleId: PropTypes.string
    };

    static defaultProps = {
      activeModuleId: null,
      query: "",
      modules: [],
      submodules: [],
      links: []
    };

    constructor(props) {
      super(props);

      const moduleStates = {};

      const activeSubmodule = this.props.submodules.find(
        s => s.id === this.props.activeModuleId
      );
      if (activeSubmodule) {
        const activeModule = this.props.modules.find(
          m => m.id === activeSubmodule.moduleId
        );
        moduleStates[activeModule.id] = { minimized: false };
      }

      this.state = {
        query: "",
        activeModuleId: "",
        moduleStates
      };
    }

    setQuery = event => {
      this.setState({ query: event.target.value || "" });
    };

    setActiveModuleId = id => {
      this.setState({ activeModuleId: id });

      if (this.props.onModuleChange) {
        this.props.onModuleChange(id);
      }
    };

    toggleModuleMinimized = id => {
      const moduleState = getModuleState(this.state.moduleStates, id);

      this.setState({
        moduleStates: {
          ...this.state.moduleStates,
          [id]: {
            ...moduleState,
            minimized: !moduleState.minimized
          }
        }
      });
    };

    handleModuleClick = id => {
      const submodules = this.props.submodules.filter(s => s.moduleId === id);

      if (submodules.length > 0) {
        this.toggleModuleMinimized(id);
        this.setActiveModuleId(submodules[0].id);
      } else {
        this.setActiveModuleId(id);
      }
      if (this.props.onModuleClick) {
        this.props.onModuleClick(id);
      }
    };

    handleSubmoduleClick = id => {
      this.setActiveModuleId(id);
      if (this.props.onSubmoduleClick) {
        this.props.onSubmoduleClick(id);
      }
    };

    renderedQuery() {
      return this.props.query ? this.props.query : this.state.query;
    }

    render() {
      const query = this.renderedQuery();
      const { modules, submodules, ...otherProps } = this.props;
      const filteredProps = filter(this.props, query);
      const filteredPropsWithState = mergeState(filteredProps, this.state);
      const groupedProps = group(filteredPropsWithState);

      return (
        <WrappedComponent
          {...otherProps}
          groups={groupedProps}
          query={query}
          setQuery={this.setQuery}
          setActiveModuleId={this.setActiveModuleId}
          toggleModuleMinimized={this.toggleModuleMinimized}
          onModuleClick={this.handleModuleClick}
          onSubmoduleClick={this.handleSubmoduleClick}
          disableCollapse={modules.length <= 5 || query.length > 0}
          searchable={this.props.searchable}
        />
      );
    }
  }

  SideNavWithState.__docgenInfo = {
    props: {
      activeModuleId: {
        description: "id of the active module or submodule"
      },
      links: {
        description:
          "An array of props for building links on the bottom of the side nav"
      },
      modules: {
        description: "An array of props for building modules"
      },
      onModuleChange: {
        description:
          "A funciton that will be called when the user activates a module or submodule"
      },
      onModuleClick: {
        description:
          "A funciton that will be called when the user clicks on a module"
      },
      onSubmoduleClick: {
        description:
          "A funciton that will be called when the user clicks on a submodule"
      },
      submodules: {
        description: "An array of props for building submodules"
      }
    }
  };

  return SideNavWithState;
}
<commit_msg>Keep modules with 5 or fewer submodules expanded<commit_after>import React, { Component } from "react";
import * as PropTypes from "prop-types";

import { filter, group, mergeState, getModuleState } from "./model";

export default function WithState(WrappedComponent) {
  class SideNavWithState extends Component {
    static propTypes = {
      links: PropTypes.arrayOf(PropTypes.object),
      modules: PropTypes.arrayOf(
        PropTypes.shape({
          id: PropTypes.string.isRequired,
          title: PropTypes.string.isRequired
        })
      ),
      onModuleChange: PropTypes.func.isRequired,
      submodules: PropTypes.arrayOf(
        PropTypes.shape({
          id: PropTypes.string.isRequired,
          title: PropTypes.string.isRequired
        })
      ),
      activeModuleId: PropTypes.string
    };

    static defaultProps = {
      activeModuleId: null,
      query: "",
      modules: [],
      submodules: [],
      links: []
    };

    constructor(props) {
      super(props);

      this.state = {
        query: "",
        activeModuleId: this.props.activeModuleId,
        moduleStates: this.initialModuleStates()
      };
    }

    setQuery = event => {
      this.setState({ query: event.target.value || "" });
    };

    setActiveModuleId = id => {
      this.setState({ activeModuleId: id });

      if (this.props.onModuleChange) {
        this.props.onModuleChange(id);
      }
    };

    _submodulesByModuleId = () =>
      this.props.submodules.reduce((result, submodule) => {
        const key = submodule.moduleId;

        if (result[key]) {
          result[key].push(submodule);
        } else {
          result[key] = [submodule]; // eslint-disable-line no-param-reassign
        }
        return result;
      }, {});

    initialModuleStates = () => {
      const moduleStates = {};
      const groupedSubmodules = this._submodulesByModuleId();
      Object.keys(groupedSubmodules).forEach(moduleId => {
        const isLargeModule = groupedSubmodules[moduleId].length > 5;
        const isInactiveModule = moduleId !== this.props.activeModuleId;

        moduleStates[moduleId] = {
          minimized: isLargeModule && isInactiveModule
        };
      });

      return moduleStates;
    };

    toggleModuleMinimized = id => {
      const moduleState = getModuleState(this.state.moduleStates, id);

      this.setState({
        moduleStates: {
          ...this.state.moduleStates,
          [id]: {
            ...moduleState,
            minimized: !moduleState.minimized
          }
        }
      });
    };

    handleModuleClick = id => {
      const submodules = this.props.submodules.filter(s => s.moduleId === id);

      if (submodules.length > 0) {
        if (submodules.length > 5) {
          this.toggleModuleMinimized(id);
        }

        this.setActiveModuleId(submodules[0].id);
      } else {
        this.setActiveModuleId(id);
      }
      if (this.props.onModuleClick) {
        this.props.onModuleClick(id);
      }
    };

    handleSubmoduleClick = id => {
      this.setActiveModuleId(id);
      if (this.props.onSubmoduleClick) {
        this.props.onSubmoduleClick(id);
      }
    };

    renderedQuery() {
      return this.props.query ? this.props.query : this.state.query;
    }

    render() {
      const query = this.renderedQuery();
      const { modules, submodules, ...otherProps } = this.props;
      const filteredProps = filter(this.props, query);
      const filteredPropsWithState = mergeState(filteredProps, this.state);
      const groupedProps = group(filteredPropsWithState);

      return (
        <WrappedComponent
          {...otherProps}
          groups={groupedProps}
          query={query}
          setQuery={this.setQuery}
          setActiveModuleId={this.setActiveModuleId}
          toggleModuleMinimized={this.toggleModuleMinimized}
          onModuleClick={this.handleModuleClick}
          onSubmoduleClick={this.handleSubmoduleClick}
          disableCollapse={modules.length <= 5 || query.length > 0}
          searchable={this.props.searchable}
        />
      );
    }
  }

  SideNavWithState.__docgenInfo = {
    props: {
      activeModuleId: {
        description: "id of the active module or submodule"
      },
      links: {
        description:
          "An array of props for building links on the bottom of the side nav"
      },
      modules: {
        description: "An array of props for building modules"
      },
      onModuleChange: {
        description:
          "A funciton that will be called when the user activates a module or submodule"
      },
      onModuleClick: {
        description:
          "A funciton that will be called when the user clicks on a module"
      },
      onSubmoduleClick: {
        description:
          "A funciton that will be called when the user clicks on a submodule"
      },
      submodules: {
        description: "An array of props for building submodules"
      }
    }
  };

  return SideNavWithState;
}
<|endoftext|>


<commit_before>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';
import NumericInput from './NumericInput';

const expect = chai.expect;

// Tests to write:
// new props
// validation message
// remove formatting on focus
// format on blur
// initial format

/* *****************************************************************************
on blur, the NumericInput component
    should format a numeric value as a plain number, if isCurrency=false and decimals=null
    should format a numeric value as a number with n decimal places, if isCurrency=false and decimals=n
    should format a numeric values as a $#.## if isCurrency=true
    should leave a blank value as-is
*/

/* *****************************************************************************
when the value is initialized, the NumericInput component
    should format a numeric value as a plain number, if isCurrency=false and decimals=null
    should format a numeric value as a number with n decimal places, if isCurrency=false and decimals=n
    should format a numeric values as a $#.## if isCurrency=true
    should leave a blank value as-is
*/
describe('when the value is initialized, the NumericInput component', () => {

    // it('should format a numeric value as a number with n decimal places, if isCurrency=false and decimals=n', () => {});

    // it('should format a numeric values as a $#.## if isCurrency=true', () => {});

    // it('should leave a blank value as-is', () => {});

});

/* *****************************************************************************
the onChange handler for the NumericInput component
    should not be called on initialization
    should be called on edit when the value has changed
    should not be called on edit when the value has not changed
    should be called on blur when the value has changed
    should not be called on blur when the value has not changed
*/
describe('the onChange handler for the NumericInput component', () => {

    it('should not be called on initialization', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;

        mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);
    });

    it('should be called on edit when the value has changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = 99.9999;

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue)).to.equal(true);
    });

    it('should not be called on edit when the value has not changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = initialValue;

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(0);
    });

    it('should be called on blur when the value has changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = 4213.21;

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('blur', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue)).to.equal(true);
    });

    it('should not be called on blur when the value has not changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = initialValue;

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('blur', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(0);
    });
});

/* *****************************************************************************
the onValidation handler for the NumericInput component
    should be called on initialization with required=true, value=valid
    should be called on initialization with required=true, value=invalid
    should be called on initialization with required=true, value=blank
    should be called on initialization with required=false, value=valid
    should be called on initialization with required=false, value=invalid
    should be called on initialization with required=false, value=blank
    should not be called after change without prior blur event
    should be called on blur event
    should be called after change following a previous blur event
*/
describe('the onValidation handler for the NumericInput component', () => {

    const description = 'a numeric input';
    const expectedMessage = `${description} is required`;

    it('should be called on initialization with required=true, value=valid', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = 123.456;

        mount(
            <NumericInput
                required={required}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should be called on initialization with required=true, value=invalid', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = 'this is not a number';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(false, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(expectedMessage, 'args[0][2]');
    });

    it('should be called on initialization with required=true, value=blank', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = '';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(false, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(expectedMessage, 'args[0][2]');
    });

    it('should be called on initialization with required=false, value=valid', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = '101.9';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should be called on initialization with required=false, value=invalid', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = 'woohoo!';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should be called on initialization with required=false, value=blank', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = '';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should not be called after change without prior blur event', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = '';
        const newValue = '33.3';

        const component = mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(1);
    });

    it('should be called on blur event', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = '';
        const newValue = '33.3';

        const component = mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('blur', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2);
        expect(onValidation.args[1][0]).to.equal(true, 'args[1][0]');
        expect(onValidation.args[1][1]).to.equal(true, 'args[1][1]');
        expect(onValidation.args[1][2]).to.equal(null, 'args[1][2]');
    });

    it('should be called after change following a previous blur event', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = '';
        const newValue = '33.3';
        const finalValue = '';

        const component = mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('blur', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        expect(onValidation.callCount).to.equal(3);

        expect(onValidation.args[2][0]).to.equal(true, 'args[2][0]');
        expect(onValidation.args[2][1]).to.equal(false, 'args[2][1]');
        expect(onValidation.args[2][2]).to.equal(expectedMessage, 'args[2][2]');
    });

});

/* *****************************************************************************
in terms of basic markup, the NumericInput component
    should be a div.form-group
    should include an input[type="text"]
    should include an input.form-control
    should include a label with the appropriate text if a label is specified
    should include a placeholder if a placeholder is specified
    should include a label with the the required flag if a label is specified & required is set
    should not include a label with the the required flag if a label is
        specified & required is not set
    should not include a label with the the required flag if a label is not
        specified & required is set
*/
describe('in terms of basic markup, the NumericInput component', () => {

    it('should be a div.form-group', () => {
        const component = shallow(<NumericInput />);

        expect(component.is('div.form-group')).to.equal(true);
    });

    it('should include an input[type="text"]', () => {
        const component = shallow(<NumericInput />);

        expect(component.find('input').length).to.equal(1);
        expect(component.find('input[type="text"]').length).to.equal(1);
    });

    it('should include an input.form-control', () => {
        const component = shallow(<NumericInput />);

        expect(component.find('input').length).to.equal(1);
        expect(component.find('input.form-control').length).to.equal(1);
    });

    it('should include a label with the appropriate text if a label is specified', () => {
        const label = 'some label';

        const component = shallow(<NumericInput label={label} />);

        expect(component.find('Label').length).to.equal(1);
        expect(component.find('Label').props().label).to.equal(label);
    });

    it('should include a placeholder if a placeholder is specified', () => {
        const placeholder = 'a number goes here';

        const component = shallow(<NumericInput placeholder={placeholder} />);

        expect(component.find('input').length).to.equal(1);
        expect(component.find('input').props().placeholder).to.equal(placeholder);
    });

    it('should include a label with the the required flag if a label is ' +
        'specified & required is set', () => {
        const label = 'some label';
        const required = true;

        const component = shallow(<NumericInput required={required} label={label} />);

        expect(component.find('Label').length).to.equal(1);
        expect(component.find('Label').props().required).to.equal(required);
    });

    it('should not include a label with the the required flag if a label is ' +
        'specified & required is not set', () => {
        const label = 'some label';
        const required = false;

        const component = shallow(<NumericInput required={required} label={label} />);

        expect(component.find('Label').length).to.equal(1);
        expect(component.find('Label').props().required).to.equal(required);
    });

    it('should not include a label with the the required flag if a label is ' +
        'not specified & required is set', () => {
        const required = false;

        const component = shallow(<NumericInput required={required} />);

        expect(component.find('Label').length).to.equal(0);
    });
});
<commit_msg>added specs for initial formatting<commit_after>// the eslint "no-unused-expressions" rule is triggered by the `expect`
// assertions in the tests. So, in order to avoid problems with eslint, I'm
// disabling that rule in this file
//
/* eslint-disable no-unused-expressions */

import React from 'react';
import { shallow, mount } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';
import NumericInput from './NumericInput';

const expect = chai.expect;

// Tests to write:
// new props
// validation message
// remove formatting on focus
// format on blur
// initial format

/* *****************************************************************************
on blur, the NumericInput component
    should format a numeric value as a plain number, if isCurrency=false and decimals=null
    should format a numeric value as a number with n decimal places, if isCurrency=false and decimals=n
    should format a numeric values as a $#.## if isCurrency=true
    should leave a blank value as-is
*/

/* *****************************************************************************
when the value is initialized, the NumericInput component
    should format a numeric value as a plain number, if isCurrency=false and decimals=null
    should format a numeric value as a number with n decimal places, if isCurrency=false and decimals=n
    should format a numeric values as a $#.## if isCurrency=true
    should leave a blank value as-is
*/
describe('when the value is initialized, the NumericInput component', () => {

    it('should format a numeric value as a plain number, if isCurrency=false and decimals=null', () => {
        const isCurrency = false;
        const decimals = null;
        const value = 123.45678;

        const expectedValue = `${value}`;

        const component = shallow(
            <NumericInput
                isCurrency={isCurrency}
                decimals={decimals}
                value={value}
            />
        );

        expect(component.find('input').props().value).to.equal(expectedValue);

    });

    it('should format a numeric value as a number with n decimal places, if isCurrency=false and decimals=n', () => {
        const isCurrency = false;
        const decimals = 3;
        const value = 123.45678;

        const expectedValue = '123.457';

        const component = shallow(
            <NumericInput
                isCurrency={isCurrency}
                decimals={decimals}
                value={value}
            />
        );

        expect(component.find('input').props().value).to.equal(expectedValue);

    });

    it('should format a numeric values as a $#.## if isCurrency=true', () => {
        const isCurrency = true;
        const decimals = 3;
        const value = 123.45678;

        const expectedValue = '$ 123.46';

        const component = shallow(
            <NumericInput
                isCurrency={isCurrency}
                decimals={decimals}
                value={value}
            />
        );

        expect(component.find('input').props().value).to.equal(expectedValue);

    });

    it('should leave a blank value as-is', () => {
        const isCurrency = true;
        const decimals = 3;
        const value = '';

        const expectedValue = '';

        const component = shallow(
            <NumericInput
                isCurrency={isCurrency}
                decimals={decimals}
                value={value}
            />
        );

        expect(component.find('input').props().value).to.equal(expectedValue);

    });

});

/* *****************************************************************************
the onChange handler for the NumericInput component
    should not be called on initialization
    should be called on edit when the value has changed
    should not be called on edit when the value has not changed
    should be called on blur when the value has changed
    should not be called on blur when the value has not changed
*/
describe('the onChange handler for the NumericInput component', () => {

    it('should not be called on initialization', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;

        mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);
    });

    it('should be called on edit when the value has changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = '99.9999';

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue)).to.equal(true);
    });

    it('should not be called on edit when the value has not changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = initialValue;

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(0);
    });

    it('should be called on blur when the value has changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = '4213.21';

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('blur', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(1);
        expect(onChange.calledWith(finalValue)).to.equal(true);
    });

    it('should not be called on blur when the value has not changed', () => {
        const onChange = sinon.spy();

        const required = true;
        const initialValue = 123.456;
        const finalValue = initialValue;

        const component = mount(
            <NumericInput
                required={required}
                value={initialValue}
                onChange={onChange}
            />
        );

        expect(onChange.callCount).to.equal(0);

        component.find('input').simulate('blur', {
            target: { value: finalValue }
        });

        expect(onChange.callCount).to.equal(0);
    });
});

/* *****************************************************************************
the onValidation handler for the NumericInput component
    should be called on initialization with required=true, value=valid
    should be called on initialization with required=true, value=invalid
    should be called on initialization with required=true, value=blank
    should be called on initialization with required=false, value=valid
    should be called on initialization with required=false, value=invalid
    should be called on initialization with required=false, value=blank
    should not be called after change without prior blur event
    should be called on blur event
    should be called after change following a previous blur event
*/
describe('the onValidation handler for the NumericInput component', () => {

    const description = 'a numeric input';
    const expectedMessage = `${description} is required`;

    it('should be called on initialization with required=true, value=valid', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = 123.456;

        mount(
            <NumericInput
                required={required}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should be called on initialization with required=true, value=invalid', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = 'this is not a number';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(false, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(expectedMessage, 'args[0][2]');
    });

    it('should be called on initialization with required=true, value=blank', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = '';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(false, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(expectedMessage, 'args[0][2]');
    });

    it('should be called on initialization with required=false, value=valid', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = '101.9';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should be called on initialization with required=false, value=invalid', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = 'woohoo!';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should be called on initialization with required=false, value=blank', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = '';

        mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);
        expect(onValidation.args[0][0]).to.equal(false, 'args[0][0]');
        expect(onValidation.args[0][1]).to.equal(true, 'args[0][1]');
        expect(onValidation.args[0][2]).to.equal(null, 'args[0][2]');
    });

    it('should not be called after change without prior blur event', () => {
        const onValidation = sinon.spy();

        const required = false;
        const value = '';
        const newValue = '33.3';

        const component = mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('change', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(1);
    });

    it('should be called on blur event', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = '';
        const newValue = '33.3';

        const component = mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('blur', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2);
        expect(onValidation.args[1][0]).to.equal(true, 'args[1][0]');
        expect(onValidation.args[1][1]).to.equal(true, 'args[1][1]');
        expect(onValidation.args[1][2]).to.equal(null, 'args[1][2]');
    });

    it('should be called after change following a previous blur event', () => {
        const onValidation = sinon.spy();

        const required = true;
        const value = '';
        const newValue = '33.3';
        const finalValue = '';

        const component = mount(
            <NumericInput
                required={required}
                description={description}
                value={value}
                onValidation={onValidation}
            />
        );

        expect(onValidation.callCount).to.equal(1);

        component.find('input').simulate('blur', {
            target: { value: newValue }
        });

        expect(onValidation.callCount).to.equal(2);

        component.find('input').simulate('change', {
            target: { value: finalValue }
        });

        expect(onValidation.callCount).to.equal(3);

        expect(onValidation.args[2][0]).to.equal(true, 'args[2][0]');
        expect(onValidation.args[2][1]).to.equal(false, 'args[2][1]');
        expect(onValidation.args[2][2]).to.equal(expectedMessage, 'args[2][2]');
    });

});

/* *****************************************************************************
in terms of basic markup, the NumericInput component
    should be a div.form-group
    should include an input[type="text"]
    should include an input.form-control
    should include a label with the appropriate text if a label is specified
    should include a placeholder if a placeholder is specified
    should include a label with the the required flag if a label is specified & required is set
    should not include a label with the the required flag if a label is
        specified & required is not set
    should not include a label with the the required flag if a label is not
        specified & required is set
*/
describe('in terms of basic markup, the NumericInput component', () => {

    it('should be a div.form-group', () => {
        const component = shallow(<NumericInput />);

        expect(component.is('div.form-group')).to.equal(true);
    });

    it('should include an input[type="text"]', () => {
        const component = shallow(<NumericInput />);

        expect(component.find('input').length).to.equal(1);
        expect(component.find('input[type="text"]').length).to.equal(1);
    });

    it('should include an input.form-control', () => {
        const component = shallow(<NumericInput />);

        expect(component.find('input').length).to.equal(1);
        expect(component.find('input.form-control').length).to.equal(1);
    });

    it('should include a label with the appropriate text if a label is specified', () => {
        const label = 'some label';

        const component = shallow(<NumericInput label={label} />);

        expect(component.find('Label').length).to.equal(1);
        expect(component.find('Label').props().label).to.equal(label);
    });

    it('should include a placeholder if a placeholder is specified', () => {
        const placeholder = 'a number goes here';

        const component = shallow(<NumericInput placeholder={placeholder} />);

        expect(component.find('input').length).to.equal(1);
        expect(component.find('input').props().placeholder).to.equal(placeholder);
    });

    it('should include a label with the the required flag if a label is ' +
        'specified & required is set', () => {
        const label = 'some label';
        const required = true;

        const component = shallow(<NumericInput required={required} label={label} />);

        expect(component.find('Label').length).to.equal(1);
        expect(component.find('Label').props().required).to.equal(required);
    });

    it('should not include a label with the the required flag if a label is ' +
        'specified & required is not set', () => {
        const label = 'some label';
        const required = false;

        const component = shallow(<NumericInput required={required} label={label} />);

        expect(component.find('Label').length).to.equal(1);
        expect(component.find('Label').props().required).to.equal(required);
    });

    it('should not include a label with the the required flag if a label is ' +
        'not specified & required is set', () => {
        const required = false;

        const component = shallow(<NumericInput required={required} />);

        expect(component.find('Label').length).to.equal(0);
    });
});
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import EntityListApp from 'tocco-entity-list/src/main'
import {
  design,
  Icon,
  Panel,
  Typography
} from 'tocco-ui'

const PanelHeaderContent = ({color, label}) =>
  <Typography.Span >
    <Icon
      icon={color ? 'square' : ['far', 'square']}
      aria-hidden="true"
      style={{...(color ? {color} : {})}}
      position={design.position.PREPEND}
    />  {label}
  </Typography.Span>

PanelHeaderContent.propTypes = {
  color: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired
}

class SearchPanel extends React.PureComponent {
  constructor() {
    super()
    this.state = {activeKey: -1}
  }

  handleSelect = name => selection => {
    this.props.updateRequestedCalendars(name, selection)
  }

  getSearchLists = calendarTypes =>
    calendarTypes.map(calendarType =>
      <Panel.Wrapper key={calendarType.name}>
        <Panel.Header>
          <PanelHeaderContent
            color={calendarType.color}
            label={calendarType.label}
          />
        </Panel.Header>
        <Panel.Body>
          <EntityListApp
            locale={this.props.locale}
            id={`search-panel-${calendarType.name}`}
            entityName={calendarType.targetEntity}
            formBase={calendarType.formBase}
            limit={25}
            showSearchForm={true}
            showCreateButton={false}
            selectable={true}
            selection={this.props.requestedCalendars ? this.props.requestedCalendars[calendarType.name] || [] : []}
            onSelectChange={this.handleSelect(calendarType.name)}
            simpleSearchFields="txtFulltext, searchFilter"
            disableSelectionController
          />
        </Panel.Body>
      </Panel.Wrapper>
    )

  render = () =>
    <Panel.Group>
      {this.getSearchLists(this.props.calendarTypes)}
    </Panel.Group>
}

SearchPanel.propTypes = {
  updateRequestedCalendars: PropTypes.func.isRequired,
  calendarTypes: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string.isRequired,
      formBase: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      targetEntity: PropTypes.string.isRequired,
      color: PropTypes.string
    }
    )),
  requestedCalendars: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
  locale: PropTypes.string
}

export default SearchPanel
<commit_msg>refactor(resource-scheduler): adjust to new list formName input<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import EntityListApp from 'tocco-entity-list/src/main'
import {
  design,
  Icon,
  Panel,
  Typography
} from 'tocco-ui'

const PanelHeaderContent = ({color, label}) =>
  <Typography.Span >
    <Icon
      icon={color ? 'square' : ['far', 'square']}
      aria-hidden="true"
      style={{...(color ? {color} : {})}}
      position={design.position.PREPEND}
    />  {label}
  </Typography.Span>

PanelHeaderContent.propTypes = {
  color: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired
}

class SearchPanel extends React.PureComponent {
  constructor() {
    super()
    this.state = {activeKey: -1}
  }

  handleSelect = name => selection => {
    this.props.updateRequestedCalendars(name, selection)
  }

  getSearchLists = calendarTypes =>
    calendarTypes.map(calendarType =>
      <Panel.Wrapper key={calendarType.name}>
        <Panel.Header>
          <PanelHeaderContent
            color={calendarType.color}
            label={calendarType.label}
          />
        </Panel.Header>
        <Panel.Body>
          <EntityListApp
            locale={this.props.locale}
            id={`search-panel-${calendarType.name}`}
            entityName={calendarType.targetEntity}
            formName={calendarType.formBase}
            limit={25}
            showSearchForm={true}
            showCreateButton={false}
            selectable={true}
            selection={this.props.requestedCalendars ? this.props.requestedCalendars[calendarType.name] || [] : []}
            onSelectChange={this.handleSelect(calendarType.name)}
            simpleSearchFields="txtFulltext, searchFilter"
            disableSelectionController
          />
        </Panel.Body>
      </Panel.Wrapper>
    )

  render = () =>
    <Panel.Group>
      {this.getSearchLists(this.props.calendarTypes)}
    </Panel.Group>
}

SearchPanel.propTypes = {
  updateRequestedCalendars: PropTypes.func.isRequired,
  calendarTypes: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string.isRequired,
      formBase: PropTypes.string.isRequired,
      name: PropTypes.string.isRequired,
      targetEntity: PropTypes.string.isRequired,
      color: PropTypes.string
    }
    )),
  requestedCalendars: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)),
  locale: PropTypes.string
}

export default SearchPanel
<|endoftext|>


<commit_before>const shellUtils = require('shell-utils');

function run() {
  // shellUtils.exec.execSync(`yarn run test-js`);
  // shellUtils.exec.execSync(`yarn run test-unit-android && yarn run test-unit-ios`);
  shellUtils.exec.exec(`yarn run test-e2e-android`);
  shellUtils.exec.exec(`yarn run test-e2e-ios`);
}

run();
<commit_msg>fixed script<commit_after>const shellUtils = require('shell-utils');

function run() {
  shellUtils.exec.execSync(`yarn run test-js`);
  shellUtils.exec.execSync(`yarn run test-unit-android && yarn run test-unit-ios`);
  shellUtils.exec.exec(`yarn run test-e2e-android`);
  shellUtils.exec.exec(`yarn run test-e2e-ios`);
}

run();
<|endoftext|>