<commit_before>// @flow

import type { ThreadInfo } from 'lib/types/thread-types';
import type { EntryStore } from 'lib/types/entry-types';
import type { LoadingStatus } from 'lib/types/loading-types';
import type { CurrentUserInfo, UserInfo } from 'lib/types/user-types';
import type { MessageStore } from 'lib/types/message-types';
import type { NavInfo, Action } from './navigation-setup';

import React from 'react';
import invariant from 'invariant';
import { REHYDRATE } from 'redux-persist/constants';
import thunk from 'redux-thunk';
import { AsyncStorage } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { autoRehydrate, persistStore } from 'redux-persist';
import PropTypes from 'prop-types';
import { NavigationActions } from 'react-navigation';

import baseReducer from 'lib/reducers/master-reducer';
import { newSessionID } from 'lib/selectors/session-selectors';
import { MessageListRouteName } from './chat/message-list.react';
import { getThreadIDFromParams } from './utils/navigation-utils';

import {
  handleURLActionType,
  navigateToAppActionType,
  defaultNavInfo,
  reduceNavInfo,
} from './navigation-setup';

export type AppState = {|
  navInfo: NavInfo,
  currentUserInfo: ?CurrentUserInfo,
  sessionID: string,
  entryStore: EntryStore,
  lastUserInteraction: {[section: string]: number},
  threadInfos: {[id: string]: ThreadInfo},
  userInfos: {[id: string]: UserInfo},
  messageStore: MessageStore,
  drafts: {[key: string]: string},
  currentAsOf: number,
  loadingStatuses: {[key: string]: {[idx: number]: LoadingStatus}},
  cookie: ?string,
  rehydrateConcluded: bool,
|};

const defaultState = ({
  navInfo: defaultNavInfo,
  currentUserInfo: null,
  sessionID: newSessionID(),
  entryStore: {
    entryInfos: {},
    daysToEntries: {},
    lastUserInteractionCalendar: 0,
  },
  lastUserInteraction: { sessionReset: Date.now() },
  threadInfos: {},
  userInfos: {},
  messageStore: {
    messages: {},
    threads: {},
  },
  drafts: {},
  currentAsOf: 0,
  loadingStatuses: {},
  cookie: null,
  rehydrateConcluded: false,
}: AppState);

const blacklist = __DEV__
  ? [
      'sessionID',
      'lastUserInteraction',
      'loadingStatuses',
      'rehydrateConcluded',
    ]
  : [
      'sessionID',
      'lastUserInteraction',
      'loadingStatuses',
      'rehydrateConcluded',
      'navInfo',
    ];

function reducer(state: AppState, action: *) {
  const navInfo = reduceNavInfo(state && state.navInfo, action);
  if (navInfo && navInfo !== state.navInfo) {
    state = {
      navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: state.threadInfos,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: state.rehydrateConcluded,
    };
  }
  if (action.type === REHYDRATE) {
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: state.threadInfos,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: true,
    };
  }
  // These action type are handled by reduceNavInfo above
  if (
    action.type === handleURLActionType ||
      action.type === navigateToAppActionType ||
      action.type === NavigationActions.INIT ||
      action.type === NavigationActions.NAVIGATE ||
      action.type === NavigationActions.BACK ||
      action.type === NavigationActions.SET_PARAMS ||
      action.type === NavigationActions.RESET
  ) {
    return state;
  }
  if (
    action.type === NavigationActions.NAVIGATE &&
    action.routeName === MessageListRouteName
  ) {
    const threadID = getThreadIDFromParams(action);
    return {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: state.threadInfos,
      userInfos: state.userInfos,
      messageStore: {
        messages: state.messageStore.messages,
        threads: {
          ...state.messageStore.threads,
          [threadID]: {
            ...state.messageStore.threads[threadID],
            lastNavigatedTo: Date.now(),
          },
        },
      },
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: state.rehydrateConcluded,
    };
  }
  return baseReducer(state, action);
}

const store = createStore(
  reducer,
  defaultState,
  composeWithDevTools(
    applyMiddleware(thunk),
    autoRehydrate(),
  ),
);
const persistor = persistStore(store, { storage: AsyncStorage, blacklist });

export {
  store,
  persistor,
};
<commit_msg>Make sure a currently focused thread is never unread on native<commit_after>// @flow

import type { ThreadInfo } from 'lib/types/thread-types';
import type { EntryStore } from 'lib/types/entry-types';
import type { LoadingStatus } from 'lib/types/loading-types';
import type { CurrentUserInfo, UserInfo } from 'lib/types/user-types';
import type { MessageStore } from 'lib/types/message-types';
import type { NavInfo, Action } from './navigation-setup';

import React from 'react';
import invariant from 'invariant';
import { REHYDRATE } from 'redux-persist/constants';
import thunk from 'redux-thunk';
import { AsyncStorage } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { autoRehydrate, persistStore } from 'redux-persist';
import PropTypes from 'prop-types';
import { NavigationActions } from 'react-navigation';

import baseReducer from 'lib/reducers/master-reducer';
import { newSessionID } from 'lib/selectors/session-selectors';

import { MessageListRouteName } from './chat/message-list.react';
import { getThreadIDFromParams } from './utils/navigation-utils';
import { activeThreadSelector } from './selectors/nav-selectors';
import {
  handleURLActionType,
  navigateToAppActionType,
  defaultNavInfo,
  reduceNavInfo,
} from './navigation-setup';

export type AppState = {|
  navInfo: NavInfo,
  currentUserInfo: ?CurrentUserInfo,
  sessionID: string,
  entryStore: EntryStore,
  lastUserInteraction: {[section: string]: number},
  threadInfos: {[id: string]: ThreadInfo},
  userInfos: {[id: string]: UserInfo},
  messageStore: MessageStore,
  drafts: {[key: string]: string},
  currentAsOf: number,
  loadingStatuses: {[key: string]: {[idx: number]: LoadingStatus}},
  cookie: ?string,
  rehydrateConcluded: bool,
|};

const defaultState = ({
  navInfo: defaultNavInfo,
  currentUserInfo: null,
  sessionID: newSessionID(),
  entryStore: {
    entryInfos: {},
    daysToEntries: {},
    lastUserInteractionCalendar: 0,
  },
  lastUserInteraction: { sessionReset: Date.now() },
  threadInfos: {},
  userInfos: {},
  messageStore: {
    messages: {},
    threads: {},
  },
  drafts: {},
  currentAsOf: 0,
  loadingStatuses: {},
  cookie: null,
  rehydrateConcluded: false,
}: AppState);

const blacklist = __DEV__
  ? [
      'sessionID',
      'lastUserInteraction',
      'loadingStatuses',
      'rehydrateConcluded',
    ]
  : [
      'sessionID',
      'lastUserInteraction',
      'loadingStatuses',
      'rehydrateConcluded',
      'navInfo',
    ];

function reducer(state: AppState, action: *) {
  const navInfo = reduceNavInfo(state && state.navInfo, action);
  if (navInfo && navInfo !== state.navInfo) {
    state = {
      navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: state.threadInfos,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: state.rehydrateConcluded,
    };
  }
  if (action.type === REHYDRATE) {
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: state.threadInfos,
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: true,
    };
  }
  // These action type are handled by reduceNavInfo above
  if (
    action.type === handleURLActionType ||
      action.type === navigateToAppActionType ||
      action.type === NavigationActions.INIT ||
      action.type === NavigationActions.NAVIGATE ||
      action.type === NavigationActions.BACK ||
      action.type === NavigationActions.SET_PARAMS ||
      action.type === NavigationActions.RESET
  ) {
    return validateUnreadStatus(state);
  }
  if (
    action.type === NavigationActions.NAVIGATE &&
    action.routeName === MessageListRouteName
  ) {
    const threadID = getThreadIDFromParams(action);
    return validateUnreadStatus({
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: state.threadInfos,
      userInfos: state.userInfos,
      messageStore: {
        messages: state.messageStore.messages,
        threads: {
          ...state.messageStore.threads,
          [threadID]: {
            ...state.messageStore.threads[threadID],
            lastNavigatedTo: Date.now(),
          },
        },
      },
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: state.rehydrateConcluded,
    });
  }
  return validateUnreadStatus(baseReducer(state, action));
}

// Makes sure a currently focused thread is never unread
function validateUnreadStatus(state: AppState): AppState {
  const activeThread = activeThreadSelector(state);
  if (activeThread && state.threadInfos[activeThread].currentUser.unread) {
    state = {
      navInfo: state.navInfo,
      currentUserInfo: state.currentUserInfo,
      sessionID: state.sessionID,
      entryStore: state.entryStore,
      lastUserInteraction: state.lastUserInteraction,
      threadInfos: {
        ...state.threadInfos,
        [activeThread]: {
          ...state.threadInfos[activeThread],
          currentUser: {
            ...state.threadInfos[activeThread].currentUser,
            unread: false,
          },
        },
      },
      userInfos: state.userInfos,
      messageStore: state.messageStore,
      drafts: state.drafts,
      currentAsOf: state.currentAsOf,
      loadingStatuses: state.loadingStatuses,
      cookie: state.cookie,
      rehydrateConcluded: state.rehydrateConcluded,
    };
  }
  return state;
}

const store = createStore(
  reducer,
  defaultState,
  composeWithDevTools(
    applyMiddleware(thunk),
    autoRehydrate(),
  ),
);
const persistor = persistStore(store, { storage: AsyncStorage, blacklist });

export {
  store,
  persistor,
};
<|endoftext|>


<commit_before>define([
    "jquery",
    "pat-utils"
], function($, utils) {
    var pluggable = {

        extend: function (attrs) {
            return utils.extend(this, attrs);
        },

        registerPlugin: function (name, callback) {
            this.plugins[name] = callback;
        },

        initializePlugins: function () {
            var args = arguments;
            _.each(_.keys(this.plugins), $.proxy(function (k) {
                $.proxy(this.plugins[k], this)(this, args);
            }, this));
            return args;
        }
    };
    return pluggable;
});
<commit_msg>Bugfix. Give the plugins the chance the modify the arguments before returning them<commit_after>define([
    "jquery",
    "pat-utils"
], function($, utils) {
    var pluggable = {

        extend: function (attrs) {
            return utils.extend(this, attrs);
        },

        registerPlugin: function (name, callback) {
            this.plugins[name] = callback;
        },

        initializePlugins: function () {
            var i, keys = _.keys(this.plugins);
            var args = arguments;
            for (i=0; i<keys.length; i++) {
                $.proxy(this.plugins[keys[i]], this).apply(this, args);
            }
            return args;
        }
    };
    return pluggable;
});
<|endoftext|>


<commit_before>UI(UI.dom).get('isElement');
UI.dom.element = function(obj){
  if(typeof obj === 'string'){
    var obj = {
      type:obj,
      $:document.createElement(obj),
    };
  }else if(UI.dom.isElement(obj)){
    obj = {
      $:obj,
    };
  }else if(!obj.$){
    obj.$ = document.createElement(obj.type);
  }
  if(obj.parent){
    UI.dom.element.prototype.appendTo.call(obj,obj.parent);
  }
  return obj;
};
UI.dom.element.prototype.appendTo = function(parent){
  console.log(this);
  console.log(parent);
  this.parent = UI.dom.element(parent);
  this.parent.$.appendChild(this.$);
  console.log(this.parent);
  return this;
};
UI.dom.element.prototype.__proto__ = UI.dom.prototype;<commit_msg>Update element.js<commit_after>UI(UI.dom).get('isElement');
UI.dom.element = function(obj){
  if(typeof obj === 'string'){
    var obj = {
      type:obj,
      $:document.createElement(obj),
    };
  }else if(UI.dom.isElement(obj)){
    obj = {
      $:obj,
    };
  }else if(!obj.$){
    obj.$ = document.createElement(obj.type);
  }
  if(obj.parent){
    if(obj.parent.then){
      obj.parent.then(function(parent){
        UI.dom.element.prototype.appendTo.call(obj,parent);
      });
    }else{
      UI.dom.element.prototype.appendTo.call(obj,obj.parent);
    }
  }
  return obj;
};
UI.dom.element.prototype.appendTo = function(parent){
  this.parent = parent;
  this.parent.$.appendChild(this.$);
  return this;
};
UI.dom.element.prototype.__proto__ = UI.dom.prototype;
<|endoftext|>


<commit_before>var MSXML = (typeof DOMParser === 'undefined' || typeof XSLTProcessor === 'undefined');
var xslt = {
	/**
	* @param {string} xsl
	*/
	init: function(xsl)
	{
		var stylesheet = xslt.loadXML(xsl);
		if (MSXML)
		{
			var generator = new ActiveXObject('MSXML2.XSLTemplate.6.0');
			generator['stylesheet'] = stylesheet;
			xslt.proc = generator['createProcessor']();
		}
		else
		{
			xslt.proc = new XSLTProcessor;
			xslt.proc['importStylesheet'](stylesheet);
		}
	},

	/**
	* @param  {string} xml
	* @return {!Document}
	*/
	loadXML: function(xml)
	{
		var dom;
		if (MSXML)
		{
			dom = new ActiveXObject('MSXML2.FreeThreadedDOMDocument.6.0');
			dom['async'] = false;
			dom['validateOnParse'] = false;
			dom['loadXML'](xml);
		}
		else
		{
			dom = (new DOMParser).parseFromString(xml, 'text/xml');
		}

		if (!dom)
		{
			throw 'Cannot parse ' + xml;
		}

		return dom;
	},

	/**
	* @param {string} paramName  Parameter name
	* @param {string} paramValue Parameter's value
	*/
	setParameter: function(paramName, paramValue)
	{
		if (MSXML)
		{
			xslt.proc['addParameter'](paramName, paramValue, '');
		}
		else
		{
			xslt.proc['setParameter'](null, paramName, paramValue);
		}
	},

	/**
	* @param  {string}    xml
	* @param  {!Document} targetDoc
	* @return {!DocumentFragment}
	*/
	transformToFragment: function(xml, targetDoc)
	{
		if (MSXML)
		{
			var div = targetDoc.createElement('div'),
				fragment = targetDoc.createDocumentFragment();

			xslt.proc['input'] = xslt.loadXML(xml);
			xslt.proc['transform']();
			div.innerHTML = xslt.proc['output'];
			while (div.firstChild)
			{
				fragment.appendChild(div.firstChild);
			}

			return fragment;
		}

		return xslt.proc['transformToFragment'](xslt.loadXML(xml), targetDoc);
	}
};
xslt.init(xsl);

var functionCache = {};

/**
* Parse a given text and render it into given HTML element
*
* @param  {string} text
* @param  {!HTMLElement} target
* @return {!Node}
*/
function preview(text, target)
{
	var targetDoc = target.ownerDocument;
	if (!targetDoc)
	{
		throw 'Target does not have a ownerDocument';
	}

	var resultFragment = xslt.transformToFragment(parse(text).replace(/<[eis]>.*?<\/[eis]>/g, ''), targetDoc),
		lastUpdated    = target;

	// Apply post-processing
	if (HINT.onRender)
	{
		executeEvents(resultFragment, 'onrender');
	}

	/**
	* Execute an event's code on a given node
	*
	* @param {!Element} node
	* @param {string}   eventName
	*/
	function executeEvent(node, eventName)
	{
		/** @type {string} */
		var code = node.getAttribute('data-s9e-livepreview-' + eventName);
		if (!functionCache[code])
		{
			functionCache[code] = new Function(code);
		}

		functionCache[code]['call'](node);
	}

	/**
	* Locate and execute an event on given document fragment
	*
	* @param {!DocumentFragment} fragment
	* @param {string}            eventName
	*/
	function executeEvents(fragment, eventName)
	{
		var nodes = fragment.querySelectorAll('[data-s9e-livepreview-' + eventName + ']'),
			i     = nodes.length;
		while (--i >= 0)
		{
			executeEvent(nodes[i], eventName);
		}
	}

	/**
	* Update the content of given node oldParent to match node newParent
	*
	* @param {!Node} oldParent
	* @param {!Node} newParent
	*/
	function refreshElementContent(oldParent, newParent)
	{
		var oldNodes = oldParent.childNodes,
			newNodes = newParent.childNodes,
			oldCnt   = oldNodes.length,
			newCnt   = newNodes.length,
			oldNode,
			newNode,
			left     = 0,
			right    = 0;

		// Skip the leftmost matching nodes
		while (left < oldCnt && left < newCnt)
		{
			oldNode = oldNodes[left];
			newNode = newNodes[left];
			if (!refreshNode(oldNode, newNode))
			{
				break;
			}

			++left;
		}

		// Skip the rightmost matching nodes
		var maxRight = Math.min(oldCnt - left, newCnt - left);
		while (right < maxRight)
		{
			oldNode = oldNodes[oldCnt - (right + 1)];
			newNode = newNodes[newCnt - (right + 1)];
			if (!refreshNode(oldNode, newNode))
			{
				break;
			}

			++right;
		}

		// Remove the old dirty nodes in the middle of the tree
		var i = oldCnt - right;
		while (--i >= left)
		{
			oldParent.removeChild(oldNodes[i]);
			lastUpdated = oldParent;
		}

		// Test whether there are any nodes in the new tree between the matching nodes at the left
		// and the matching nodes at the right
		var rightBoundary = newCnt - right;
		if (left >= rightBoundary)
		{
			return;
		}

		// Clone the new nodes
		var newNodesFragment = targetDoc.createDocumentFragment();
		i = left;
		do
		{
			lastUpdated = newNodesFragment.appendChild(newNodes[i]);
		}
		while (i < --rightBoundary);

		// If we haven't skipped any nodes to the right, we can just append the fragment
		if (!right)
		{
			oldParent.appendChild(newNodesFragment);
		}
		else
		{
			oldParent.insertBefore(newNodesFragment, oldParent.childNodes[left]);
		}
	}

	/**
	* Update given node oldNode to make it match newNode
	*
	* @param {!Node} oldNode
	* @param {!Node} newNode
	* @return {boolean} Whether the node can be skipped
	*/
	function refreshNode(oldNode, newNode)
	{
		if (oldNode.nodeName !== newNode.nodeName || oldNode.nodeType !== newNode.nodeType)
		{
			return false;
		}

		if (oldNode instanceof HTMLElement && newNode instanceof HTMLElement)
		{
			if (!oldNode.isEqualNode(newNode))
			{
				syncElementAttributes(oldNode, newNode);
				refreshElementContent(oldNode, newNode);
			}
		}
		// Node.TEXT_NODE || Node.COMMENT_NODE
		else if (oldNode.nodeType === 3 || oldNode.nodeType === 8)
		{
			if (oldNode.nodeValue !== newNode.nodeValue)
			{
				oldNode.nodeValue = newNode.nodeValue;
				lastUpdated = oldNode;
			}
		}

		return true;
	}

	/**
	* Make the set of attributes of given element oldEl match newEl's
	*
	* @param {!HTMLElement} oldEl
	* @param {!HTMLElement} newEl
	*/
	function syncElementAttributes(oldEl, newEl)
	{
		var oldAttributes = oldEl['attributes'],
			newAttributes = newEl['attributes'],
			oldCnt        = oldAttributes.length,
			newCnt        = newAttributes.length,
			i             = oldCnt,
			ignoreAttrs   = ' ' + oldEl.getAttribute('data-s9e-livepreview-ignore-attrs') + ' ';

		while (--i >= 0)
		{
			var oldAttr      = oldAttributes[i],
				namespaceURI = oldAttr['namespaceURI'],
				attrName     = oldAttr['name'];

			if (HINT.ignoreAttrs && ignoreAttrs.indexOf(' ' + attrName + ' ') > -1)
			{
				continue;
			}
			if (!newEl.hasAttributeNS(namespaceURI, attrName))
			{
				oldEl.removeAttributeNS(namespaceURI, attrName);
				lastUpdated = oldEl;
			}
		}

		i = newCnt;
		while (--i >= 0)
		{
			var newAttr      = newAttributes[i],
				namespaceURI = newAttr['namespaceURI'],
				attrName     = newAttr['name'],
				attrValue    = newAttr['value'];

			if (HINT.ignoreAttrs && ignoreAttrs.indexOf(' ' + attrName + ' ') > -1)
			{
				continue;
			}
			if (attrValue !== oldEl.getAttributeNS(namespaceURI, attrName))
			{
				oldEl.setAttributeNS(namespaceURI, attrName, attrValue);
				lastUpdated = oldEl;
			}
		}
	}

	refreshElementContent(target, resultFragment);

	return lastUpdated;
}

/**
* Set the value of a stylesheet parameter
*
* @param {string} paramName  Parameter name
* @param {string} paramValue Parameter's value
*/
function setParameter(paramName, paramValue)
{
	xslt.setParameter(paramName, paramValue);
}<commit_msg>Updated render.js [ci skip]<commit_after>var MSXML = (typeof DOMParser === 'undefined' || typeof XSLTProcessor === 'undefined');
var xslt = {
	/**
	* @param {string} xsl
	*/
	init: function(xsl)
	{
		var stylesheet = xslt.loadXML(xsl);
		if (MSXML)
		{
			var generator = new ActiveXObject('MSXML2.XSLTemplate.6.0');
			generator['stylesheet'] = stylesheet;
			xslt.proc = generator['createProcessor']();
		}
		else
		{
			xslt.proc = new XSLTProcessor;
			xslt.proc['importStylesheet'](stylesheet);
		}
	},

	/**
	* @param  {string} xml
	* @return {!Document}
	*/
	loadXML: function(xml)
	{
		var dom;
		if (MSXML)
		{
			dom = new ActiveXObject('MSXML2.FreeThreadedDOMDocument.6.0');
			dom['async'] = false;
			dom['validateOnParse'] = false;
			dom['loadXML'](xml);
		}
		else
		{
			dom = (new DOMParser).parseFromString(xml, 'text/xml');
		}

		if (!dom)
		{
			throw 'Cannot parse ' + xml;
		}

		return dom;
	},

	/**
	* @param {string} paramName  Parameter name
	* @param {string} paramValue Parameter's value
	*/
	setParameter: function(paramName, paramValue)
	{
		if (MSXML)
		{
			xslt.proc['addParameter'](paramName, paramValue, '');
		}
		else
		{
			xslt.proc['setParameter'](null, paramName, paramValue);
		}
	},

	/**
	* @param  {string}    xml
	* @param  {!Document} targetDoc
	* @return {!DocumentFragment}
	*/
	transformToFragment: function(xml, targetDoc)
	{
		if (MSXML)
		{
			var div = targetDoc.createElement('div'),
				fragment = targetDoc.createDocumentFragment();

			xslt.proc['input'] = xslt.loadXML(xml);
			xslt.proc['transform']();
			div.innerHTML = xslt.proc['output'];
			while (div.firstChild)
			{
				fragment.appendChild(div.firstChild);
			}

			return fragment;
		}

		return xslt.proc['transformToFragment'](xslt.loadXML(xml), targetDoc);
	}
};
xslt.init(xsl);

var functionCache = {};

/**
* Parse a given text and render it into given HTML element
*
* @param  {string} text
* @param  {!HTMLElement} target
* @return {!Node}
*/
function preview(text, target)
{
	var targetDoc = target.ownerDocument;
	if (!targetDoc)
	{
		throw 'Target does not have a ownerDocument';
	}

	var resultFragment = xslt.transformToFragment(parse(text).replace(/<[eis]>[^<]*<\/[eis]>/g, ''), targetDoc),
		lastUpdated    = target;

	// Apply post-processing
	if (HINT.onRender)
	{
		executeEvents(resultFragment, 'onrender');
	}

	/**
	* Execute an event's code on a given node
	*
	* @param {!Element} node
	* @param {string}   eventName
	*/
	function executeEvent(node, eventName)
	{
		/** @type {string} */
		var code = node.getAttribute('data-s9e-livepreview-' + eventName);
		if (!functionCache[code])
		{
			functionCache[code] = new Function(code);
		}

		functionCache[code]['call'](node);
	}

	/**
	* Locate and execute an event on given document fragment
	*
	* @param {!DocumentFragment} fragment
	* @param {string}            eventName
	*/
	function executeEvents(fragment, eventName)
	{
		var nodes = fragment.querySelectorAll('[data-s9e-livepreview-' + eventName + ']'),
			i     = nodes.length;
		while (--i >= 0)
		{
			executeEvent(nodes[i], eventName);
		}
	}

	/**
	* Update the content of given node oldParent to match node newParent
	*
	* @param {!Node} oldParent
	* @param {!Node} newParent
	*/
	function refreshElementContent(oldParent, newParent)
	{
		var oldNodes = oldParent.childNodes,
			newNodes = newParent.childNodes,
			oldCnt   = oldNodes.length,
			newCnt   = newNodes.length,
			oldNode,
			newNode,
			left     = 0,
			right    = 0;

		// Skip the leftmost matching nodes
		while (left < oldCnt && left < newCnt)
		{
			oldNode = oldNodes[left];
			newNode = newNodes[left];
			if (!refreshNode(oldNode, newNode))
			{
				break;
			}

			++left;
		}

		// Skip the rightmost matching nodes
		var maxRight = Math.min(oldCnt - left, newCnt - left);
		while (right < maxRight)
		{
			oldNode = oldNodes[oldCnt - (right + 1)];
			newNode = newNodes[newCnt - (right + 1)];
			if (!refreshNode(oldNode, newNode))
			{
				break;
			}

			++right;
		}

		// Remove the old dirty nodes in the middle of the tree
		var i = oldCnt - right;
		while (--i >= left)
		{
			oldParent.removeChild(oldNodes[i]);
			lastUpdated = oldParent;
		}

		// Test whether there are any nodes in the new tree between the matching nodes at the left
		// and the matching nodes at the right
		var rightBoundary = newCnt - right;
		if (left >= rightBoundary)
		{
			return;
		}

		// Clone the new nodes
		var newNodesFragment = targetDoc.createDocumentFragment();
		i = left;
		do
		{
			lastUpdated = newNodesFragment.appendChild(newNodes[i]);
		}
		while (i < --rightBoundary);

		// If we haven't skipped any nodes to the right, we can just append the fragment
		if (!right)
		{
			oldParent.appendChild(newNodesFragment);
		}
		else
		{
			oldParent.insertBefore(newNodesFragment, oldParent.childNodes[left]);
		}
	}

	/**
	* Update given node oldNode to make it match newNode
	*
	* @param {!Node} oldNode
	* @param {!Node} newNode
	* @return {boolean} Whether the node can be skipped
	*/
	function refreshNode(oldNode, newNode)
	{
		if (oldNode.nodeName !== newNode.nodeName || oldNode.nodeType !== newNode.nodeType)
		{
			return false;
		}

		if (oldNode instanceof HTMLElement && newNode instanceof HTMLElement)
		{
			if (!oldNode.isEqualNode(newNode))
			{
				syncElementAttributes(oldNode, newNode);
				refreshElementContent(oldNode, newNode);
			}
		}
		// Node.TEXT_NODE || Node.COMMENT_NODE
		else if (oldNode.nodeType === 3 || oldNode.nodeType === 8)
		{
			if (oldNode.nodeValue !== newNode.nodeValue)
			{
				oldNode.nodeValue = newNode.nodeValue;
				lastUpdated = oldNode;
			}
		}

		return true;
	}

	/**
	* Make the set of attributes of given element oldEl match newEl's
	*
	* @param {!HTMLElement} oldEl
	* @param {!HTMLElement} newEl
	*/
	function syncElementAttributes(oldEl, newEl)
	{
		var oldAttributes = oldEl['attributes'],
			newAttributes = newEl['attributes'],
			oldCnt        = oldAttributes.length,
			newCnt        = newAttributes.length,
			i             = oldCnt,
			ignoreAttrs   = ' ' + oldEl.getAttribute('data-s9e-livepreview-ignore-attrs') + ' ';

		while (--i >= 0)
		{
			var oldAttr      = oldAttributes[i],
				namespaceURI = oldAttr['namespaceURI'],
				attrName     = oldAttr['name'];

			if (HINT.ignoreAttrs && ignoreAttrs.indexOf(' ' + attrName + ' ') > -1)
			{
				continue;
			}
			if (!newEl.hasAttributeNS(namespaceURI, attrName))
			{
				oldEl.removeAttributeNS(namespaceURI, attrName);
				lastUpdated = oldEl;
			}
		}

		i = newCnt;
		while (--i >= 0)
		{
			var newAttr      = newAttributes[i],
				namespaceURI = newAttr['namespaceURI'],
				attrName     = newAttr['name'],
				attrValue    = newAttr['value'];

			if (HINT.ignoreAttrs && ignoreAttrs.indexOf(' ' + attrName + ' ') > -1)
			{
				continue;
			}
			if (attrValue !== oldEl.getAttributeNS(namespaceURI, attrName))
			{
				oldEl.setAttributeNS(namespaceURI, attrName, attrValue);
				lastUpdated = oldEl;
			}
		}
	}

	refreshElementContent(target, resultFragment);

	return lastUpdated;
}

/**
* Set the value of a stylesheet parameter
*
* @param {string} paramName  Parameter name
* @param {string} paramValue Parameter's value
*/
function setParameter(paramName, paramValue)
{
	xslt.setParameter(paramName, paramValue);
}<|endoftext|>


<commit_before>function ddg_spice_songkick_geteventid(api_result) {
  "use strict";
  // Taken from
  //   http://www.songkick.com/developer/location-search
  // {"resultsPage":
  //   {"results":
  //     {"location":[
  //       {"city":{"displayName":"London",
  //         "country":{"displayName":"UK"},
  //         "lng":-0.128,"lat":51.5078},
  //         "metroArea":{"uri":"http://www.songkick.com/metro_areas/24426-uk-london",
  //           "displayName":"London",
  //           "country":{"displayName":"UK"},
  //           "id":24426,
  //           "lng":-0.128,"lat":51.5078}
  //       },
  //       {"city":{"displayName":"London",
  //         "country":{"displayName":"US"},
  //         "lng":null,"lat":null,
  //         "state":{"displayName":"KY"}},
  //         "metroArea":{"uri":"http://www.songkick.com/metro_areas/24580",
  //           "displayName":"Lexington",
  //           "country":{"displayName":"US"},
  //           "id":24580,
  //           "lng":-84.4947,"lat":38.0297,
  //           "state":{"displayName":"KY"}}
  //       }
  //     ]},
  //   "totalEntries":2,"perPage":10,"page":1,"status":"ok"}
  // }
  // At this point, we have the metro_area_id, so we can get the list of
  // concerts.
  if (!api_result || !api_result.resultsPage || !api_result.resultsPage.results) {
    return;
  }
  if (api_result.resultsPage.results.totalEntries == 0 || api_result.resultsPage.results.length == 0) {
    return;
  }
  if (!api_result.resultsPage.results.location || api_result.resultsPage.results.location.length == 0) {
    return;
  }

  // Prevent jQuery from appending "_={timestamp}" in our url.
  $.ajaxSetup({
    cache: true
  });

  var considered_location = api_result.resultsPage.results.location[0];

  var skip_words = ['concert', 'concerts'];
  if (!DDG.isRelevant(considered_location.city.displayName, skip_words) &&
      !DDG.isRelevant(considered_location.city.country.displayName, skip_words) &&
      !DDG.isRelevant(considered_location.metroArea.displayName, skip_words)) {
    // If the query is just 'concert' or 'concerts', we use the location API to
    // get the user's location, so we can't rely on isRelevant to tell us if the
    // location matches the query or not. If the query isn't in skip_words, we
    // return;
    if (['concert', 'concerts', 'concerts in the area'].indexOf(DDG.get_query()) < 0) {
      return;
    }
  }
  var metro_area_id = considered_location.metroArea.id;
  var metadata = {
    metro_area_uri          : considered_location.metroArea.uri,
    metro_area_display_name : considered_location.city.displayName
  };
  ddg_spice_songkick_geteventid.metadata = metadata;

  $.getScript("/js/spice/songkick/events/" + metro_area_id);
}

function ddg_spice_songkick_events(events_data) {
  "use strict";

  var max_results = 10;
  var show_results = 3;
  var twenty_four_to_twelve_hour_time = function(t) {
    if (t === null) {
      return t;
    }
    var a = t.split(/:/);
    a[0] = parseInt(a[0]);
    if (a[0] > 12) {
      a = [a[0] - 12, a[1], 'PM'];
    } else {
      a = [a[0], a[1], ((a[0] == 12) ? 'PM' : 'AM')];
    }
    a[0] += '';
    return a[0] + ':' + a[1] + ' ' + a[2];
  };
  // Taken from
  //   http://www.songkick.com/developer/upcoming-events-for-metro-area
  // {"resultsPage:" {
  //   "results": { "event": [
  //     {"id":11129128,
  //       "type":"Concert",
  //       "uri":"http://www.songkick.com/concerts/11129128-wild-flag-at-fillmore?utm_source=PARTNER_ID&utm_medium=partner",
  //       "displayName":"Wild Flag at The Fillmore (April 18, 2012)",
  //       "start":{"time":"20:00:00",
  //         "date":"2012-04-18",
  //         "datetime":"2012-04-18T20:00:00-0800"},
  //       "performance":[{"artist":{"uri":"http://www.songkick.com/artists/29835-wild-flag?utm_source=PARTNER_ID&utm_medium=partner",
  //         "displayName":"Wild Flag","id":29835,"identifier":[]},
  //         "displayName":"Wild Flag",
  //         "billingIndex":1,
  //         "id":21579303,
  //         "billing":"headline"}],
  //       "location":{"city":"San Francisco, CA, US","lng":-122.4332937,"lat":37.7842398},
  //       "venue":{"id":6239,
  //         "displayName":"The Fillmore",
  //         "uri":"http://www.songkick.com/venues/6239-fillmore?utm_source=PARTNER_ID&utm_medium=partner",
  //         "lng":-122.4332937, "lat":37.7842398,
  //         "metroArea":{"uri":"http://www.songkick.com/metro_areas/26330-us-sf-bay-area?utm_source=PARTNER_ID&utm_medium=partner",
  //           "displayName":"SF Bay Area",
  //           "country":{"displayName":"US"},
  //           "id":26330,
  //           "state":{"displayName":"CA"}}},
  //       "status":"ok",
  //       "popularity":0.012763
  //     }, ....
  //   ]},
  //   "totalEntries":24,
  //   "perPage":50,
  //   "page":1,
  //   "status":"ok"}
  // }
  if (!events_data || !events_data.resultsPage || !events_data.resultsPage.results || !events_data.resultsPage.results.event) {
    return;
  }
  if (events_data.resultsPage.results.event.length == 0) {
    return;
  }

  // Filter out unknown venues and events with no artists.
  // Also redefine the objects into something cleaner.
  var events = events_data.resultsPage.results.event;
  var results = [];
  for(var i = 0; i < events.length; i++) {
    if(events[i].performance.length > 0 && events[i].venue.displayName !== "Unknown venue") {
      results.push({
        uri: events[i].uri,
        artist: events[i].performance[0].displayName,
	venue: events[i].venue.displayName,
        start: {
          date: events[i].start.date,
          time: twenty_four_to_twelve_hour_time(events[i].start.time)
        }
      });
    }
  }

  Spice.render({
    data             : events_data,
    header1          : 'Events in ' + ddg_spice_songkick_geteventid.metadata.metro_area_display_name + ' (Songkick)',
    source_url       : encodeURI(ddg_spice_songkick_geteventid.metadata.metro_area_uri),
    source_name      : 'Songkick',
    spice_name       : 'songkick',
    template_frame   : 'list',
    template_options: {
      items         : results,
      template_item : 'songkick_geteventid',
      show          : show_results,
      max           : max_results
    },
    force_big_header : true,
    force_no_fold    : true
  });
}

Handlebars.registerHelper("dateString", function(s) {
  var date = DDG.getDateFromString(s),
      months = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
  return months[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
});
<commit_msg>Songkick: Remove country checking.<commit_after>function ddg_spice_songkick_geteventid(api_result) {
  "use strict";
  // Taken from
  //   http://www.songkick.com/developer/location-search
  // {"resultsPage":
  //   {"results":
  //     {"location":[
  //       {"city":{"displayName":"London",
  //         "country":{"displayName":"UK"},
  //         "lng":-0.128,"lat":51.5078},
  //         "metroArea":{"uri":"http://www.songkick.com/metro_areas/24426-uk-london",
  //           "displayName":"London",
  //           "country":{"displayName":"UK"},
  //           "id":24426,
  //           "lng":-0.128,"lat":51.5078}
  //       },
  //       {"city":{"displayName":"London",
  //         "country":{"displayName":"US"},
  //         "lng":null,"lat":null,
  //         "state":{"displayName":"KY"}},
  //         "metroArea":{"uri":"http://www.songkick.com/metro_areas/24580",
  //           "displayName":"Lexington",
  //           "country":{"displayName":"US"},
  //           "id":24580,
  //           "lng":-84.4947,"lat":38.0297,
  //           "state":{"displayName":"KY"}}
  //       }
  //     ]},
  //   "totalEntries":2,"perPage":10,"page":1,"status":"ok"}
  // }
  // At this point, we have the metro_area_id, so we can get the list of
  // concerts.
  if (!api_result || !api_result.resultsPage || !api_result.resultsPage.results) {
    return;
  }
  if (api_result.resultsPage.results.totalEntries == 0 || api_result.resultsPage.results.length == 0) {
    return;
  }
  if (!api_result.resultsPage.results.location || api_result.resultsPage.results.location.length == 0) {
    return;
  }

  // Prevent jQuery from appending "_={timestamp}" in our url.
  $.ajaxSetup({
    cache: true
  });

  var considered_location = api_result.resultsPage.results.location[0];

  var skip_words = ['concert', 'concerts'];
  if (!DDG.isRelevant(considered_location.city.displayName, skip_words) &&
      !DDG.isRelevant(considered_location.metroArea.displayName, skip_words)) {
    // If the query is just 'concert' or 'concerts', we use the location API to
    // get the user's location, so we can't rely on isRelevant to tell us if the
    // location matches the query or not. If the query isn't in skip_words, we
    // return;
    if (['concert', 'concerts', 'concerts in the area'].indexOf(DDG.get_query()) < 0) {
      return;
    }
  }
  var metro_area_id = considered_location.metroArea.id;
  var metadata = {
    metro_area_uri          : considered_location.metroArea.uri,
    metro_area_display_name : considered_location.city.displayName
  };
  ddg_spice_songkick_geteventid.metadata = metadata;

  $.getScript("/js/spice/songkick/events/" + metro_area_id);
}

function ddg_spice_songkick_events(events_data) {
  "use strict";

  var max_results = 10;
  var show_results = 3;
  var twenty_four_to_twelve_hour_time = function(t) {
    if (t === null) {
      return t;
    }
    var a = t.split(/:/);
    a[0] = parseInt(a[0]);
    if (a[0] > 12) {
      a = [a[0] - 12, a[1], 'PM'];
    } else {
      a = [a[0], a[1], ((a[0] == 12) ? 'PM' : 'AM')];
    }
    a[0] += '';
    return a[0] + ':' + a[1] + ' ' + a[2];
  };
  // Taken from
  //   http://www.songkick.com/developer/upcoming-events-for-metro-area
  // {"resultsPage:" {
  //   "results": { "event": [
  //     {"id":11129128,
  //       "type":"Concert",
  //       "uri":"http://www.songkick.com/concerts/11129128-wild-flag-at-fillmore?utm_source=PARTNER_ID&utm_medium=partner",
  //       "displayName":"Wild Flag at The Fillmore (April 18, 2012)",
  //       "start":{"time":"20:00:00",
  //         "date":"2012-04-18",
  //         "datetime":"2012-04-18T20:00:00-0800"},
  //       "performance":[{"artist":{"uri":"http://www.songkick.com/artists/29835-wild-flag?utm_source=PARTNER_ID&utm_medium=partner",
  //         "displayName":"Wild Flag","id":29835,"identifier":[]},
  //         "displayName":"Wild Flag",
  //         "billingIndex":1,
  //         "id":21579303,
  //         "billing":"headline"}],
  //       "location":{"city":"San Francisco, CA, US","lng":-122.4332937,"lat":37.7842398},
  //       "venue":{"id":6239,
  //         "displayName":"The Fillmore",
  //         "uri":"http://www.songkick.com/venues/6239-fillmore?utm_source=PARTNER_ID&utm_medium=partner",
  //         "lng":-122.4332937, "lat":37.7842398,
  //         "metroArea":{"uri":"http://www.songkick.com/metro_areas/26330-us-sf-bay-area?utm_source=PARTNER_ID&utm_medium=partner",
  //           "displayName":"SF Bay Area",
  //           "country":{"displayName":"US"},
  //           "id":26330,
  //           "state":{"displayName":"CA"}}},
  //       "status":"ok",
  //       "popularity":0.012763
  //     }, ....
  //   ]},
  //   "totalEntries":24,
  //   "perPage":50,
  //   "page":1,
  //   "status":"ok"}
  // }
  if (!events_data || !events_data.resultsPage || !events_data.resultsPage.results || !events_data.resultsPage.results.event) {
    return;
  }
  if (events_data.resultsPage.results.event.length == 0) {
    return;
  }

  // Filter out unknown venues and events with no artists.
  // Also redefine the objects into something cleaner.
  var events = events_data.resultsPage.results.event;
  var results = [];
  for(var i = 0; i < events.length; i++) {
    if(events[i].performance.length > 0 && events[i].venue.displayName !== "Unknown venue") {
      results.push({
        uri: events[i].uri,
        artist: events[i].performance[0].displayName,
	venue: events[i].venue.displayName,
        start: {
          date: events[i].start.date,
          time: twenty_four_to_twelve_hour_time(events[i].start.time)
        }
      });
    }
  }

  Spice.render({
    data             : events_data,
    header1          : 'Events in ' + ddg_spice_songkick_geteventid.metadata.metro_area_display_name + ' (Songkick)',
    source_url       : encodeURI(ddg_spice_songkick_geteventid.metadata.metro_area_uri),
    source_name      : 'Songkick',
    spice_name       : 'songkick',
    template_frame   : 'list',
    template_options: {
      items         : results,
      template_item : 'songkick_geteventid',
      show          : show_results,
      max           : max_results
    },
    force_big_header : true,
    force_no_fold    : true
  });
}

Handlebars.registerHelper("dateString", function(s) {
  var date = DDG.getDateFromString(s),
      months = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
  return months[date.getMonth()] + " " + date.getDate() + ", " + date.getFullYear();
});
<|endoftext|>


<commit_before>(function (env) {
    "use strict";
    env.ddg_spice_transit_switzerland = function(api_result) {

        if (!api_result || api_result.error || !api_result.to || !api_result.from || !api_result.connections) {
            return Spice.failed('switzerland');
        }

        var to = api_result.to.name || '',
            from = api_result.from.name || '';

        Spice.add({
            id: "switzerland",
            name: "Swiss Trains",
            data: api_result.connections,
            meta: {
                heading: from + " to " + to,
                sourceName: "transport.opendata.ch",
                sourceUrl: 'http://transport.opendata.ch/examples/connections.php?from=' + from + '&to=' + to
            },
            templates: {
                group: 'base',
                detail: false,
                item_detail: false,
                options: {
                    content: Spice.transit_switzerland.train_item,
                    moreAt: true
                }
            },
            normalize: function(item){
                return {
                    arrival_time: format_time(item.to.arrival),
                    departure_time: format_time(item.from.departure),
                    name: item.sections[0].journey.name,
                    platform: (item.from.platform) ? item.from.platform : 'unknown',
                    status: (item.from.delay) ? 'Delayed' : 'On time',
                    status_class: (item.from.delay) ? 'switzerland__delayed' : '',
                    transfers: format_transfers(item.transfers),
                    url: 'http://transport.opendata.ch/examples/connections.php?from=' + from + '&to=' + to
                }
            }
        });
    };

    // Extract time only from datetime, e.g. "2015-03-04T13:32:00+0100"
    function format_time(departure) {
        var dep_array = departure.split('T');
        var time = dep_array[1];
        return time.substr(0, 5);
    }

    // Make the transfers output format more human-readable
    function format_transfers(transfers) {
        var plural = (transfers === 1) ? '' : 's';
        transfers = (!transfers) ? 'No' : transfers;
        return transfers + ' transfer' + plural;
    }
}(this));<commit_msg>Transit/Switzerland: Improved handling of unknown platforms (fixes #1835)<commit_after>(function (env) {
    "use strict";
    env.ddg_spice_transit_switzerland = function(api_result) {

        if (!api_result || api_result.error || !api_result.to || !api_result.from || !api_result.connections) {
            return Spice.failed('switzerland');
        }

        var to = api_result.to.name || '',
            from = api_result.from.name || '';

        Spice.add({
            id: "switzerland",
            name: "Swiss Trains",
            data: api_result.connections,
            meta: {
                heading: from + " to " + to,
                sourceName: "transport.opendata.ch",
                sourceUrl: 'http://transport.opendata.ch/examples/connections.php?from=' + from + '&to=' + to
            },
            templates: {
                group: 'base',
                detail: false,
                item_detail: false,
                options: {
                    content: Spice.transit_switzerland.train_item,
                    moreAt: true
                }
            },
            normalize: function(item){
                return {
                    arrival_time: format_time(item.to.arrival),
                    departure_time: format_time(item.from.departure),
                    name: item.sections[0].journey.name,
                    platform: item.from.platform || 'unknown',
                    status: (item.from.delay) ? 'Delayed' : 'On time',
                    status_class: (item.from.delay) ? 'switzerland__delayed' : '',
                    transfers: format_transfers(item.transfers),
                    url: 'http://transport.opendata.ch/examples/connections.php?from=' + from + '&to=' + to
                }
            }
        });
    };

    // Extract time only from datetime, e.g. "2015-03-04T13:32:00+0100"
    function format_time(departure) {
        var dep_array = departure.split('T');
        var time = dep_array[1];
        return time.substr(0, 5);
    }

    // Make the transfers output format more human-readable
    function format_transfers(transfers) {
        var plural = (transfers === 1) ? '' : 's';
        transfers = (!transfers) ? 'No' : transfers;
        return transfers + ' transfer' + plural;
    }
}(this));<|endoftext|>


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

const pull = require('pull-stream')
const toPull = require('stream-to-pull-stream')
const { createServerStream } = require('../..')

const appKey = Buffer.from(process.argv[2], 'hex')
const authorize = (pubKey, cb) => cb(null, true) // all clients are allowed to connect
const bob = { // the keypair of the server
  publicKey: Buffer.from(process.argv[4], 'hex'),
  secretKey: Buffer.from(process.argv[3], 'hex')
}
const timeout = 30 // I hope this is milliseconds?

const ServerStream = createServerStream(bob, authorize, appKey, timeout) // duplex handshake stream
const stream = ServerStream((err, plainStream) => {
  console.log(err, plainStream)
})

pull(
  toPull.source(process.stdin),
  stream,
  toPull.sink(process.stdout)
)

//
// const { verifyMsg1, createMsg2, verifyMsg3, createMsg4, serverOutcome } = require('../index.js')
// const serverState = {
//   shsStep: 1, // which message are you receiving next
//   network_identifier: Buffer.from(process.argv[2], 'hex'),
//   server_longterm_sk: Buffer.from(process.argv[3], 'hex'),
//   server_longterm_pk: Buffer.from(process.argv[4], 'hex'),
//   server_ephemeral_sk: Buffer.from([176, 248, 210, 185, 226, 76, 162, 153, 239, 144, 57, 206, 218, 97, 2, 215, 155, 5, 223, 189, 22, 28, 137, 85, 228, 233, 93, 79, 217, 203, 63, 125]),
//   server_ephemeral_pk: Buffer.from([166, 12, 63, 218, 235, 136, 61, 99, 232, 142, 165, 147, 88, 93, 79, 177, 23, 148, 129, 57, 179, 24, 192, 174, 90, 62, 40, 83, 51, 9, 97, 82])
// }

// process.stdin.on('readable', () => {
//   switch (serverState.shsStep) {
//     case 1: {
//       const msg1 = process.stdin.read()

//       if (!verifyMsg1(serverState, msg1)) process.exit(1)

//       process.stdout.write(createMsg2(serverState))
//       serverState.shsStep = 3
//       break
//     }
//     case 3: {
//       const msg3 = process.stdin.read()

//       if (!verifyMsg3(serverState, msg3)) {
//         process.exit(3)
//       }

//       const outcome = serverOutcome(serverState)
//       process.stdout.write(Buffer.concat([
//         createMsg4(serverState),
//         outcome.encryption_key,
//         outcome.encryption_nonce,
//         outcome.decryption_key,
//         outcome.decryption_nonce
//       ]))
//       break
//     }
//     default:
//   }
// })
<commit_msg>add debugging to testsuite server test<commit_after>#!/usr/bin/env node

const pull = require('pull-stream')
const toPull = require('stream-to-pull-stream')
const { createServerStream } = require('../..')

const appKey = Buffer.from(process.argv[2], 'hex')
const bob = { // the keypair of the server
  secretKey: Buffer.from(process.argv[3], 'hex'),
  publicKey: Buffer.from(process.argv[4], 'hex')
}
const authorize = (pubKey, cb) => cb(null, true) // all clients are allowed to connect
const timeout = 30 // I hope this is milliseconds?

const ServerStream = createServerStream(bob, authorize, appKey, timeout) // duplex handshake stream
var d = Date.now()
const stream = ServerStream((err, plainStream) => {
  if (err) process.stderr.write(`X ${d} ERROR ${err}\n`)
  else process.stderr.write(`0 ${d} DONE\n`)

  process.kill(process.pid)
  // is this sufficient to kill this process? are the streams closed?
})

process.on('SIGTERM', () => {
  process.stderr.write(`X ${d} received SIGTERM\n`) // triggered by process.kill above
})

pull(
  toPull.source(process.stdin),
  pull.through(data => process.stderr.write(`> ${d}\n`)),
  stream,
  pull.through(data => process.stderr.write(`< ${d}\n`)),
  toPull.sink(process.stdout)
)
<|endoftext|>


<commit_before>'use strict';

var chai = require('chai');
var sinon = require('sinon');
var sinonChai = require('sinon-chai');

var BULLET_MODULE_PATH = '../../src/bullet';

var expect = chai.expect;
var bulletSingleton = require(BULLET_MODULE_PATH);
var BulletClass = bulletSingleton.constructor;

chai.use(sinonChai);

describe('Bullet', function () {

    describe('Method Existence', function () {

        before(function () {

            this.bullet = new BulletClass();
        });

        it('should have a private method named "_getMappings"', function () {
            expect(this.bullet._getMappings).to.be.a('function');
        });

        it('should have a public method named "on"', function () {
            expect(this.bullet.on).to.be.a('function');
        });

        it('should have a public method named "off"', function () {
            expect(this.bullet.off).to.be.a('function');
        });

        it('should have a public method named "trigger"', function () {
            expect(this.bullet.trigger).to.be.a('function');
        });

        it('should have a public method named "once"', function () {
            expect(this.bullet.once).to.be.a('function');
        });

        it('should have a public method named "addEvent"', function () {
            expect(this.bullet.addEvent).to.be.a('function');
        });

        it('should have a public method named "removeEvent"', function () {
            expect(this.bullet.removeEvent).to.be.a('function');
        });
    });

    describe('Method Implementation', function () {

        beforeEach(function () {

            this.bullet = new BulletClass();
            this.testEventName = 'hello there';
            this.testCallback = function testCallback () {};
        });

        describe('_getMappings()', function () {

            it('should return the public state of the internal "_mappings" object', function () {

                var mappings = this.bullet._getMappings();
                expect(mappings).to.deep.equal({});

                // Add a property to the returned mappings object.
                mappings.foo = 'bar';

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                // Bullet's internal mappings should not have been modified.
                expect(mappings).to.deep.equal({});
            });
        });

        describe('on()', function () {

            it('should create a mapping for the specified event name', function () {

                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('undefined');

                // Add an event.
                this.bullet.on(this.testEventName, this.testCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks).to.be.an('object');

                var testCallbackString = this.testCallback.toString();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('object');
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if only one parameter is passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with only one param.
                this.bullet.on(this.testEventName);

                // The map should be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if no parameters are passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with no params.
                this.bullet.on();

                // The map should still be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });
        });

        describe('off()', function () {

            before(function () {

                this.someOtherEventName = 'toto';
                this.someOtherCallback = function someOtherCallback () {};
            });

            it('should remove a mapping only for the specified event name and function', function () {

                // Create multiple event mappings so that we can test the removal of a single mapping.
                this.bullet.on(this.testEventName, this.testCallback);
                this.bullet.on(this.testEventName, this.someOtherCallback);

                var testCallbackString = this.testCallback.toString();
                var someOtherCallbackString = this.someOtherCallback.toString();

                // Get the events map.
                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove the mapping to this.testCallback
                this.bullet.off(this.testEventName, this.testCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('undefined');
                expect(mappings[this.testEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove the mapping to this.someOtherCallback
                this.bullet.off(this.testEventName, this.someOtherCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                // The map should be empty, now that all event mappings have been removed.
                expect(mappings).to.deep.equal({});
            });

            it('should remove all mappings for the specified event name', function () {

                // Create multiple event mappings so that we can test the removal of all mappings
                // for a specific event name.
                this.bullet.on(this.testEventName, this.testCallback);
                this.bullet.on(this.testEventName, this.someOtherCallback);
                this.bullet.on(this.someOtherEventName, this.testCallback);
                this.bullet.on(this.someOtherEventName, this.someOtherCallback);

                var testCallbackString = this.testCallback.toString();
                var someOtherCallbackString = this.someOtherCallback.toString();

                // Get the events map.
                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks[someOtherCallbackString]).to.be.an('object');
                expect(mappings[this.someOtherEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.someOtherEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove all mappings for this.testEventName
                this.bullet.off(this.testEventName);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('undefined');
                expect(mappings[this.someOtherEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.someOtherEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove all mappings for this.someOtherEventName
                this.bullet.off(this.someOtherEventName);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.someOtherEventName]).to.be.an('undefined');
            });
        });

        describe('trigger()', function () {

            afterEach(function () {

                sinon.restore(this.testCallback);
            });

            it('should trigger a mapped callback', function () {

                sinon.spy(this, 'testCallback');

                // Create an event mapping so that we can test that it gets triggered below.
                this.bullet.on(this.testEventName, this.testCallback);

                // Trigger the event.
                this.bullet.trigger(this.testEventName);

                expect(this.testCallback.calledOnce).to.equal(true);
            });

            it('should trigger a mapped callback with data', function () {

                var testData = {hello : 'sunshine'};

                sinon.spy(this, 'testCallback');

                // Create an event mapping so that we can test that it gets triggered below.
                this.bullet.on(this.testEventName, this.testCallback);

                // Trigger the event with data.
                this.bullet.trigger(this.testEventName, testData);

                expect(this.testCallback).to.have.been.calledWith(testData);

            });
        });

        describe('once()', function () {

            it('should create a one-time mapping for the specified event name', function () {

                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('undefined');

                // Add a one-time event.
                this.bullet.once(this.testEventName, this.testCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks).to.be.an('object');

                // Trigger the event.
                this.bullet.trigger(this.testEventName);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                // The event mapping should have been deleted, as it was triggered once.
                expect(mappings[this.testEventName]).to.be.an('undefined');
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if only one parameter is passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with only one param.
                this.bullet.once(this.testEventName);

                // The map should be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if no parameters are passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with no params.
                this.bullet.once();

                // The map should still be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });
        });

        describe('getEvents()', function () {

            it('should return the current state of the internal "_strictEvents" object', function () {

                var events = this.bullet.getEvents();

                // The map should start empty
                expect(events).to.deep.equal({});

                // Add a property to the returned events object.
                events.foo = 'bar';

                // Get the updated events map.
                events = this.bullet.getEvents();

                // Bullet's internal events object should not have been modified.
                expect(events).to.deep.equal({});
            });
        });

        describe('addEvent()', function () {

            it('should add an event to the internal "_strictEvents" object', function () {

                var events = this.bullet.getEvents();

                // The map should start empty
                expect(events).to.deep.equal({});

                // Add an event.
                this.bullet.addEvent('foo');

                // Get the updated events map.
                events = this.bullet.getEvents();

                // Bullet's internal _events should not have been modified.
                expect(events).to.deep.equal({foo : 'foo'});

                // Add another event.
                this.bullet.addEvent('bar');

                // Get the updated events map.
                events = this.bullet.getEvents();

                // Bullet's internal _events should not have been modified.
                expect(events).to.deep.equal({foo : 'foo', bar : 'bar'});
            });

            it('should throw a TypeError if the passed parameter is not a string', function () {

                var self = this;

                function callAddEvent() {
                    self.bullet.addEvent({hello : 'hi'});
                }

                expect(callAddEvent).to.throw(TypeError);
            });

            it('should throw an Error if the passed string parameter length is 0', function () {

                var self = this;

                function callAddEvent() {
                    self.bullet.addEvent('');
                }

                expect(callAddEvent).to.throw(Error);
            });
        });

        describe('removeEvent()', function () {

            it('should remove an event from the internal "_strictEvents" object', function () {

                var events = this.bullet.getEvents();

                // Add multiple events so that we can test the removal of a single event.
                this.bullet.addEvent('foo');
                this.bullet.addEvent('bar');
                this.bullet.addEvent('baz');

                // Get the updated events map.
                events = this.bullet.getEvents();

                expect(events).to.deep.equal({foo : 'foo', bar : 'bar', baz : 'baz'});

                // Remove one of the events.
                this.bullet.removeEvent('bar');

                // Get the updated events map.
                events = this.bullet.getEvents();

                expect(events).to.deep.equal({foo : 'foo', baz : 'baz'});

                // Remove the remaining events.
                this.bullet.removeEvent('foo');
                this.bullet.removeEvent('baz');

                // Get the updated events map.
                events = this.bullet.getEvents();

                // The map should start empty
                expect(events).to.deep.equal({});
            });

            it('should throw a TypeError if the passed parameter is not a string', function () {

                var self = this;

                function callRemoveEvent() {
                    self.bullet.removeEvent({hello : 'hi'});
                }

                expect(callRemoveEvent).to.throw(TypeError);
            });

            it('should throw an Error if the passed string parameter length is 0', function () {

                var self = this;

                function callRemoveEvent() {
                    self.bullet.removeEvent('');
                }

                expect(callRemoveEvent).to.throw(Error);
            });
        });
    });
});<commit_msg>Add existence check test for 'getEvents' method<commit_after>'use strict';

var chai = require('chai');
var sinon = require('sinon');
var sinonChai = require('sinon-chai');

var BULLET_MODULE_PATH = '../../src/bullet';

var expect = chai.expect;
var bulletSingleton = require(BULLET_MODULE_PATH);
var BulletClass = bulletSingleton.constructor;

chai.use(sinonChai);

describe('Bullet', function () {

    describe('Method Existence', function () {

        before(function () {

            this.bullet = new BulletClass();
        });

        it('should have a private method named "_getMappings"', function () {
            expect(this.bullet._getMappings).to.be.a('function');
        });

        it('should have a public method named "on"', function () {
            expect(this.bullet.on).to.be.a('function');
        });

        it('should have a public method named "off"', function () {
            expect(this.bullet.off).to.be.a('function');
        });

        it('should have a public method named "trigger"', function () {
            expect(this.bullet.trigger).to.be.a('function');
        });

        it('should have a public method named "once"', function () {
            expect(this.bullet.once).to.be.a('function');
        });

        it('should have a public method named "getEvents"', function () {
            expect(this.bullet.getEvents).to.be.a('function');
        });

        it('should have a public method named "addEvent"', function () {
            expect(this.bullet.addEvent).to.be.a('function');
        });

        it('should have a public method named "removeEvent"', function () {
            expect(this.bullet.removeEvent).to.be.a('function');
        });
    });

    describe('Method Implementation', function () {

        beforeEach(function () {

            this.bullet = new BulletClass();
            this.testEventName = 'hello there';
            this.testCallback = function testCallback () {};
        });

        describe('_getMappings()', function () {

            it('should return the public state of the internal "_mappings" object', function () {

                var mappings = this.bullet._getMappings();
                expect(mappings).to.deep.equal({});

                // Add a property to the returned mappings object.
                mappings.foo = 'bar';

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                // Bullet's internal mappings should not have been modified.
                expect(mappings).to.deep.equal({});
            });
        });

        describe('on()', function () {

            it('should create a mapping for the specified event name', function () {

                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('undefined');

                // Add an event.
                this.bullet.on(this.testEventName, this.testCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks).to.be.an('object');

                var testCallbackString = this.testCallback.toString();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('object');
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if only one parameter is passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with only one param.
                this.bullet.on(this.testEventName);

                // The map should be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if no parameters are passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with no params.
                this.bullet.on();

                // The map should still be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });
        });

        describe('off()', function () {

            before(function () {

                this.someOtherEventName = 'toto';
                this.someOtherCallback = function someOtherCallback () {};
            });

            it('should remove a mapping only for the specified event name and function', function () {

                // Create multiple event mappings so that we can test the removal of a single mapping.
                this.bullet.on(this.testEventName, this.testCallback);
                this.bullet.on(this.testEventName, this.someOtherCallback);

                var testCallbackString = this.testCallback.toString();
                var someOtherCallbackString = this.someOtherCallback.toString();

                // Get the events map.
                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove the mapping to this.testCallback
                this.bullet.off(this.testEventName, this.testCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('undefined');
                expect(mappings[this.testEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove the mapping to this.someOtherCallback
                this.bullet.off(this.testEventName, this.someOtherCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                // The map should be empty, now that all event mappings have been removed.
                expect(mappings).to.deep.equal({});
            });

            it('should remove all mappings for the specified event name', function () {

                // Create multiple event mappings so that we can test the removal of all mappings
                // for a specific event name.
                this.bullet.on(this.testEventName, this.testCallback);
                this.bullet.on(this.testEventName, this.someOtherCallback);
                this.bullet.on(this.someOtherEventName, this.testCallback);
                this.bullet.on(this.someOtherEventName, this.someOtherCallback);

                var testCallbackString = this.testCallback.toString();
                var someOtherCallbackString = this.someOtherCallback.toString();

                // Get the events map.
                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks[someOtherCallbackString]).to.be.an('object');
                expect(mappings[this.someOtherEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.someOtherEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove all mappings for this.testEventName
                this.bullet.off(this.testEventName);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('undefined');
                expect(mappings[this.someOtherEventName].callbacks[testCallbackString]).to.be.an('object');
                expect(mappings[this.someOtherEventName].callbacks[someOtherCallbackString]).to.be.an('object');

                // Remove all mappings for this.someOtherEventName
                this.bullet.off(this.someOtherEventName);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.someOtherEventName]).to.be.an('undefined');
            });
        });

        describe('trigger()', function () {

            afterEach(function () {

                sinon.restore(this.testCallback);
            });

            it('should trigger a mapped callback', function () {

                sinon.spy(this, 'testCallback');

                // Create an event mapping so that we can test that it gets triggered below.
                this.bullet.on(this.testEventName, this.testCallback);

                // Trigger the event.
                this.bullet.trigger(this.testEventName);

                expect(this.testCallback.calledOnce).to.equal(true);
            });

            it('should trigger a mapped callback with data', function () {

                var testData = {hello : 'sunshine'};

                sinon.spy(this, 'testCallback');

                // Create an event mapping so that we can test that it gets triggered below.
                this.bullet.on(this.testEventName, this.testCallback);

                // Trigger the event with data.
                this.bullet.trigger(this.testEventName, testData);

                expect(this.testCallback).to.have.been.calledWith(testData);

            });
        });

        describe('once()', function () {

            it('should create a one-time mapping for the specified event name', function () {

                var mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('undefined');

                // Add a one-time event.
                this.bullet.once(this.testEventName, this.testCallback);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                expect(mappings[this.testEventName]).to.be.an('object');
                expect(mappings[this.testEventName].callbacks).to.be.an('object');

                // Trigger the event.
                this.bullet.trigger(this.testEventName);

                // Get the updated events map.
                mappings = this.bullet._getMappings();

                // The event mapping should have been deleted, as it was triggered once.
                expect(mappings[this.testEventName]).to.be.an('undefined');
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if only one parameter is passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with only one param.
                this.bullet.once(this.testEventName);

                // The map should be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });

            // TODO : It should really throw an Error here. The 'on' method needs updating.
            it('should not create a mapping if no parameters are passed', function () {

                // The map should start empty
                expect(this.bullet._getMappings()).to.deep.equal({});

                // Attempt to add an event with no params.
                this.bullet.once();

                // The map should still be empty
                expect(this.bullet._getMappings()).to.deep.equal({});
            });
        });

        describe('getEvents()', function () {

            it('should return the current state of the internal "_strictEvents" object', function () {

                var events = this.bullet.getEvents();

                // The map should start empty
                expect(events).to.deep.equal({});

                // Add a property to the returned events object.
                events.foo = 'bar';

                // Get the updated events map.
                events = this.bullet.getEvents();

                // Bullet's internal events object should not have been modified.
                expect(events).to.deep.equal({});
            });
        });

        describe('addEvent()', function () {

            it('should add an event to the internal "_strictEvents" object', function () {

                var events = this.bullet.getEvents();

                // The map should start empty
                expect(events).to.deep.equal({});

                // Add an event.
                this.bullet.addEvent('foo');

                // Get the updated events map.
                events = this.bullet.getEvents();

                // Bullet's internal _events should not have been modified.
                expect(events).to.deep.equal({foo : 'foo'});

                // Add another event.
                this.bullet.addEvent('bar');

                // Get the updated events map.
                events = this.bullet.getEvents();

                // Bullet's internal _events should not have been modified.
                expect(events).to.deep.equal({foo : 'foo', bar : 'bar'});
            });

            it('should throw a TypeError if the passed parameter is not a string', function () {

                var self = this;

                function callAddEvent() {
                    self.bullet.addEvent({hello : 'hi'});
                }

                expect(callAddEvent).to.throw(TypeError);
            });

            it('should throw an Error if the passed string parameter length is 0', function () {

                var self = this;

                function callAddEvent() {
                    self.bullet.addEvent('');
                }

                expect(callAddEvent).to.throw(Error);
            });
        });

        describe('removeEvent()', function () {

            it('should remove an event from the internal "_strictEvents" object', function () {

                var events = this.bullet.getEvents();

                // Add multiple events so that we can test the removal of a single event.
                this.bullet.addEvent('foo');
                this.bullet.addEvent('bar');
                this.bullet.addEvent('baz');

                // Get the updated events map.
                events = this.bullet.getEvents();

                expect(events).to.deep.equal({foo : 'foo', bar : 'bar', baz : 'baz'});

                // Remove one of the events.
                this.bullet.removeEvent('bar');

                // Get the updated events map.
                events = this.bullet.getEvents();

                expect(events).to.deep.equal({foo : 'foo', baz : 'baz'});

                // Remove the remaining events.
                this.bullet.removeEvent('foo');
                this.bullet.removeEvent('baz');

                // Get the updated events map.
                events = this.bullet.getEvents();

                // The map should start empty
                expect(events).to.deep.equal({});
            });

            it('should throw a TypeError if the passed parameter is not a string', function () {

                var self = this;

                function callRemoveEvent() {
                    self.bullet.removeEvent({hello : 'hi'});
                }

                expect(callRemoveEvent).to.throw(TypeError);
            });

            it('should throw an Error if the passed string parameter length is 0', function () {

                var self = this;

                function callRemoveEvent() {
                    self.bullet.removeEvent('');
                }

                expect(callRemoveEvent).to.throw(Error);
            });
        });
    });
});<|endoftext|>