<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box, { omitBoxProps, pickBoxProps } from '../box';
import DayPickerInput from 'react-day-picker/DayPickerInput';
import Icon from '../icon';
import NavigationBar from './NavigationBar';
import WeekDay from './WeekDay';
import { convertModifiersToClassnames } from './utils';
import { IconCalendarSmallOutline } from '@teamleader/ui-icons';
import HelpText from '../helptext';
import ErrorText from '../errorText';
import cx from 'classnames';
import omit from 'lodash.omit';
import theme from './theme.css';

class DatePickerInput extends PureComponent {
  state = {
    inputHasFocus: false,
    selectedDate: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.selectedDate !== undefined && props.selectedDate !== state.selectedDate) {
      return {
        selectedDate: props.selectedDate,
      };
    }

    return null;
  }

  handleBlur = () => {
    this.setState({ inputHasFocus: false }, () => this.props.onBlur && this.props.onBlur());
  };

  handleFocus = () => {
    this.setState({ inputHasFocus: true }, () => this.props.onFocus && this.props.onFocus());
  };

  handleInputDateChange = date => {
    this.setState({ selectedDate: date }, () => this.props.onChange(date));
  };

  renderDayPickerInput = () => {
    const { className, dayPickerProps, disabled, modifiers, readOnly, size, ...others } = this.props;
    const { selectedDate } = this.state;

    const dayPickerClassNames = cx(theme['date-picker'], theme[`is-${size}`], className);

    const propsWithoutBoxProps = omitBoxProps(others);
    const restProps = omit(propsWithoutBoxProps, ['helpText', 'onBlur', 'onChange', 'onFocus']);

    return (
      <DayPickerInput
        classNames={theme}
        dayPickerProps={{
          className: dayPickerClassNames,
          classNames: theme,
          modifiers: convertModifiersToClassnames(modifiers, theme),
          onDayClick: this.handleInputDateChange,
          selectedDays: selectedDate,
          navbarElement: <NavigationBar size={size} />,
          weekdayElement: <WeekDay size={size} />,
          ...dayPickerProps,
        }}
        onDayChange={this.handleInputDateChange}
        hideOnDayClick={false}
        inputProps={{
          disabled: disabled || readOnly,
          onBlur: this.handleBlur,
          onFocus: this.handleFocus,
        }}
        {...restProps}
      />
    );
  };

  renderIcon = () => {
    const { inverse } = this.props;

    return (
      <Icon
        className={theme['input-icon']}
        color={inverse ? 'teal' : 'neutral'}
        tint={inverse ? 'light' : 'darkest'}
        marginHorizontal={2}
      >
        <IconCalendarSmallOutline />
      </Icon>
    );
  };

  render() {
    const { bold, disabled, error, helpText, inverse, readOnly, size, ...others } = this.props;
    const { inputHasFocus } = this.state;

    const boxProps = pickBoxProps(others);

    const classNames = cx(theme['date-picker-input'], theme[`is-${size}`], {
      [theme['is-bold']]: bold,
      [theme['is-disabled']]: disabled,
      [theme['is-inverse']]: inverse,
      [theme['is-read-only']]: readOnly,
      [theme['has-error']]: error,
      [theme['has-focus']]: inputHasFocus,
    });

    return (
      <Box className={classNames} {...boxProps}>

          {this.renderIcon()}
          {this.renderDayPickerInput()}

        {error ? (
          <ErrorText inverse={inverse}>{error}</ErrorText>
        ) : (
          helpText && <HelpText inverse={inverse}>{helpText}</HelpText>
        )}
      </Box>
    );
  }
}

DatePickerInput.propTypes = {
  bold: PropTypes.bool,
  className: PropTypes.string,
  dayPickerProps: PropTypes.object,
  disabled: PropTypes.bool,
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  helpText: PropTypes.string,
  inverse: PropTypes.bool,
  modifiers: PropTypes.object,
  /** Callback function that is fired when blurring the input field. */
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  /** Callback function that is fired when focussing the input field. */
  onFocus: PropTypes.func,
  readOnly: PropTypes.bool,
  selectedDate: PropTypes.instanceOf(Date),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

DatePickerInput.defaultProps = {
  bold: false,
  disabled: false,
  inverse: false,
  readOnly: false,
  size: 'medium',
};

export default DatePickerInput;
<commit_msg>:fire: remove onDayClick handler and let DayPicker handle it<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box, { omitBoxProps, pickBoxProps } from '../box';
import DayPickerInput from 'react-day-picker/DayPickerInput';
import Icon from '../icon';
import NavigationBar from './NavigationBar';
import WeekDay from './WeekDay';
import { convertModifiersToClassnames } from './utils';
import { IconCalendarSmallOutline } from '@teamleader/ui-icons';
import HelpText from '../helptext';
import ErrorText from '../errorText';
import cx from 'classnames';
import omit from 'lodash.omit';
import theme from './theme.css';

class DatePickerInput extends PureComponent {
  state = {
    inputHasFocus: false,
    selectedDate: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (props.selectedDate !== undefined && props.selectedDate !== state.selectedDate) {
      return {
        selectedDate: props.selectedDate,
      };
    }

    return null;
  }

  handleBlur = () => {
    this.setState({ inputHasFocus: false }, () => this.props.onBlur && this.props.onBlur());
  };

  handleFocus = () => {
    this.setState({ inputHasFocus: true }, () => this.props.onFocus && this.props.onFocus());
  };

  handleInputDateChange = date => {
    this.setState({ selectedDate: date }, () => this.props.onChange(date));
  };

  renderDayPickerInput = () => {
    const { className, dayPickerProps, disabled, modifiers, readOnly, size, ...others } = this.props;
    const { selectedDate } = this.state;

    const dayPickerClassNames = cx(theme['date-picker'], theme[`is-${size}`], className);

    const propsWithoutBoxProps = omitBoxProps(others);
    const restProps = omit(propsWithoutBoxProps, ['helpText', 'onBlur', 'onChange', 'onFocus']);

    return (
      <DayPickerInput
        classNames={theme}
        dayPickerProps={{
          className: dayPickerClassNames,
          classNames: theme,
          modifiers: convertModifiersToClassnames(modifiers, theme),
          selectedDays: selectedDate,
          navbarElement: <NavigationBar size={size} />,
          weekdayElement: <WeekDay size={size} />,
          ...dayPickerProps,
        }}
        onDayChange={this.handleInputDateChange}
        hideOnDayClick={false}
        inputProps={{
          disabled: disabled || readOnly,
          onBlur: this.handleBlur,
          onFocus: this.handleFocus,
        }}
        {...restProps}
      />
    );
  };

  renderIcon = () => {
    const { inverse } = this.props;

    return (
      <Icon
        className={theme['input-icon']}
        color={inverse ? 'teal' : 'neutral'}
        tint={inverse ? 'light' : 'darkest'}
        marginHorizontal={2}
      >
        <IconCalendarSmallOutline />
      </Icon>
    );
  };

  render() {
    const { bold, disabled, error, helpText, inverse, readOnly, size, ...others } = this.props;
    const { inputHasFocus } = this.state;

    const boxProps = pickBoxProps(others);

    const classNames = cx(theme['date-picker-input'], theme[`is-${size}`], {
      [theme['is-bold']]: bold,
      [theme['is-disabled']]: disabled,
      [theme['is-inverse']]: inverse,
      [theme['is-read-only']]: readOnly,
      [theme['has-error']]: error,
      [theme['has-focus']]: inputHasFocus,
    });

    return (
      <Box className={classNames} {...boxProps}>

          {this.renderIcon()}
          {this.renderDayPickerInput()}

        {error ? (
          <ErrorText inverse={inverse}>{error}</ErrorText>
        ) : (
          helpText && <HelpText inverse={inverse}>{helpText}</HelpText>
        )}
      </Box>
    );
  }
}

DatePickerInput.propTypes = {
  bold: PropTypes.bool,
  className: PropTypes.string,
  dayPickerProps: PropTypes.object,
  disabled: PropTypes.bool,
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  helpText: PropTypes.string,
  inverse: PropTypes.bool,
  modifiers: PropTypes.object,
  /** Callback function that is fired when blurring the input field. */
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  /** Callback function that is fired when focussing the input field. */
  onFocus: PropTypes.func,
  readOnly: PropTypes.bool,
  selectedDate: PropTypes.instanceOf(Date),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

DatePickerInput.defaultProps = {
  bold: false,
  disabled: false,
  inverse: false,
  readOnly: false,
  size: 'medium',
};

export default DatePickerInput;
<|endoftext|>


<commit_before>const Rx = require('rx-lite')
const redis = require("redis");
const {PromiseUtils} = require('isomorphic-core');
const log = global.Logger || console;

PromiseUtils.promisifyAll(redis.RedisClient.prototype);
PromiseUtils.promisifyAll(redis.Multi.prototype);

class PubsubConnector {
  constructor() {
    this._broadcastClient = null;
    this._listenClient = null;
    this._listenClientSubs = {};
  }

  buildClient(accountId) {
    const client = redis.createClient(process.env.REDIS_URL || null);
    log.info({account_id: accountId}, "Connecting to Redis")
    client.on("error", log.error);
    client.on("end", () => {
      log.info({account_id: accountId}, "Redis disconnected")
      this._broadcastClient = null;
    })
    return client;
  }

  broadcastClient() {
    if (!this._broadcastClient) {
      this._broadcastClient = this.buildClient("broadcast");
    }
    return this._broadcastClient;
  }

  queueProcessMessage({messageId, accountId}) {
    if (!messageId) {
      throw new Error("queueProcessMessage: The message body processor expects a messageId")
    }
    if (!accountId) {
      throw new Error("queueProcessMessage: The message body processor expects a accountId")
    }
    this.broadcastClient().lpush(`message-processor-queue`, JSON.stringify({messageId, accountId}));
  }

  // Shared channel
  _observableForChannelOnSharedListener(channel) {
    if (!this._listenClient) {
      this._listenClient = this.buildClient();
      this._listenClientSubs = {};
    }

    return Rx.Observable.create((observer) => {
      this._listenClient.on("message", (msgChannel, message) => {
        if (msgChannel !== channel) { return }
        observer.onNext(message)
      });

      if (!this._listenClientSubs[channel]) {
        this._listenClientSubs[channel] = 1;
        this._listenClient.subscribe(channel);
      } else {
        this._listenClientSubs[channel] += 1;
      }
      return () => {
        this._listenClientSubs[channel] -= 1;
        if (this._listenClientSubs[channel] === 0) {
          this._listenClient.unsubscribe(channel);
        }
      }
    });
  }

  notifyAccount(accountId, {type, data}) {
    this.broadcastClient().publish(`account-${accountId}`, JSON.stringify({type, data}));
  }

  observeAccount(accountId) {
    return this._observableForChannelOnSharedListener(`account-${accountId}`);
  }

  notifyDelta(accountId, transactionJSON) {
    this.broadcastClient().publish(`deltas-${accountId}`, JSON.stringify(transactionJSON))
  }

  observeAllAccounts() {
    return Rx.Observable.create((observer) => {
      const sub = this.buildClient();
      sub.on("pmessage", (pattern, channel, message) =>
        observer.onNext(channel.replace('account-', ''), message));
      sub.psubscribe(`account-*`);
      return () => {
        sub.unsubscribe();
        sub.quit();
      }
    })
  }

  observeDeltas(accountId) {
    return Rx.Observable.create((observer) => {
      const sub = this.buildClient(accountId);
      sub.on("message", (channel, transactionJSONString) => {
        observer.onNext(JSON.parse(transactionJSONString))
      })
      sub.subscribe(`deltas-${accountId}`);
      return () => {
        log.info({account_id: accountId}, "Closing Redis")
        sub.unsubscribe();
        sub.quit();
      }
    })
  }
}

module.exports = new PubsubConnector()
<commit_msg>[cloud-core] use correct logger when logging Redis connections<commit_after>const Rx = require('rx-lite')
const redis = require("redis");
const {PromiseUtils} = require('isomorphic-core');

PromiseUtils.promisifyAll(redis.RedisClient.prototype);
PromiseUtils.promisifyAll(redis.Multi.prototype);

class PubsubConnector {
  constructor() {
    this._broadcastClient = null;
    this._listenClient = null;
    this._listenClientSubs = {};
  }

  buildClient(accountId) {
    const client = redis.createClient(process.env.REDIS_URL || null);
    global.Logger.log.info({account_id: accountId}, "Connecting to Redis")
    client.on("error", global.Logger.log.error);
    client.on("end", () => {
      global.Logger.log.info({account_id: accountId}, "Redis disconnected")
      this._broadcastClient = null;
    })
    return client;
  }

  broadcastClient() {
    if (!this._broadcastClient) {
      this._broadcastClient = this.buildClient("broadcast");
    }
    return this._broadcastClient;
  }

  queueProcessMessage({messageId, accountId}) {
    if (!messageId) {
      throw new Error("queueProcessMessage: The message body processor expects a messageId")
    }
    if (!accountId) {
      throw new Error("queueProcessMessage: The message body processor expects a accountId")
    }
    this.broadcastClient().lpush(`message-processor-queue`, JSON.stringify({messageId, accountId}));
  }

  // Shared channel
  _observableForChannelOnSharedListener(channel) {
    if (!this._listenClient) {
      this._listenClient = this.buildClient();
      this._listenClientSubs = {};
    }

    return Rx.Observable.create((observer) => {
      this._listenClient.on("message", (msgChannel, message) => {
        if (msgChannel !== channel) { return }
        observer.onNext(message)
      });

      if (!this._listenClientSubs[channel]) {
        this._listenClientSubs[channel] = 1;
        this._listenClient.subscribe(channel);
      } else {
        this._listenClientSubs[channel] += 1;
      }
      return () => {
        this._listenClientSubs[channel] -= 1;
        if (this._listenClientSubs[channel] === 0) {
          this._listenClient.unsubscribe(channel);
        }
      }
    });
  }

  notifyAccount(accountId, {type, data}) {
    this.broadcastClient().publish(`account-${accountId}`, JSON.stringify({type, data}));
  }

  observeAccount(accountId) {
    return this._observableForChannelOnSharedListener(`account-${accountId}`);
  }

  notifyDelta(accountId, transactionJSON) {
    this.broadcastClient().publish(`deltas-${accountId}`, JSON.stringify(transactionJSON))
  }

  observeAllAccounts() {
    return Rx.Observable.create((observer) => {
      const sub = this.buildClient();
      sub.on("pmessage", (pattern, channel, message) =>
        observer.onNext(channel.replace('account-', ''), message));
      sub.psubscribe(`account-*`);
      return () => {
        sub.unsubscribe();
        sub.quit();
      }
    })
  }

  observeDeltas(accountId) {
    return Rx.Observable.create((observer) => {
      const sub = this.buildClient(accountId);
      sub.on("message", (channel, transactionJSONString) => {
        observer.onNext(JSON.parse(transactionJSONString))
      })
      sub.subscribe(`deltas-${accountId}`);
      return () => {
        global.Logger.log.info({account_id: accountId}, "Closing Redis")
        sub.unsubscribe();
        sub.quit();
      }
    })
  }
}

module.exports = new PubsubConnector()
<|endoftext|>


<commit_before>// Name  : login.js
// Author: Cerek Hillen
//
// Description:
//   The API endpoint for logging a user in.

//////////////
// Includes //
var bcrypt = require('bcrypt'),

    database = require('../database.js'),
    common   = require('../common.js');

//////////
// Code //

// Attempting to POST to this endpoint.
function post(req, res) {
    common.allExists(req.body, ['email', 'password'], function (exists) {
        if (!exists) {
            res.json({
                error  : true,
                success: false,
                message: 'Malformed request.'
            });

            return;
        }

        database.schema.User.find({
            email: req.body.email
        }, function (err, users) {
            if (err) {
                res.json({
                    error  : true,
                    success: false,
                    message: 'Failed to retrieve users.'
                });

                return;
            }

            if (users.length === 0) {
                res.json({
                    error  : false,
                    success: false,
                    message: 'No account exists with that email.'
                });

                return;
            }

            var user = users[0];
            bcrypt.compare(req.body.password, user.password, function (err, same) {
                if (err) {
                    res.json({
                        error  : true,
                        success: false,
                        message: 'There was an error in hashing the password: ' + String(err)
                    });

                    return;
                }

                if (!same) {
                    res.json({
                        error  : false,
                        success: false,
                        message: 'Invalid password.'
                    });

                    return;
                }

                bcrypt.hash(user.username + user.password, 10, function (err, hash) {
                    if (err) {
                        res.json({
                            error  : true,
                            success: false,
                            message: 'Failed to generate auth key.'
                        });

                        return;
                    }

                    res.set('Set-Cookie', 'auth=' + JSON.stringify({
                        username: user.username,
                        auth: hash
                    }));

                    res.json({
                        error  : false,
                        success: true,
                        message: 'Logged in.'
                    });
                });
            });
        });
    });
}

/////////////
// Exports //
module.exports.path = '/login';
module.exports.post = post;
<commit_msg>Fixed the cookie not showing up properly.<commit_after>// Name  : login.js
// Author: Cerek Hillen
//
// Description:
//   The API endpoint for logging a user in.

//////////////
// Includes //
var bcrypt = require('bcrypt'),

    database = require('../database.js'),
    common   = require('../common.js');

//////////
// Code //

// Attempting to POST to this endpoint.
function post(req, res) {
    common.allExists(req.body, ['email', 'password'], function (exists) {
        if (!exists) {
            res.json({
                error  : true,
                success: false,
                message: 'Malformed request.'
            });

            return;
        }

        database.schema.User.find({
            email: req.body.email
        }, function (err, users) {
            if (err) {
                res.json({
                    error  : true,
                    success: false,
                    message: 'Failed to retrieve users.'
                });

                return;
            }

            if (users.length === 0) {
                res.json({
                    error  : false,
                    success: false,
                    message: 'No account exists with that email.'
                });

                return;
            }

            var user = users[0];
            bcrypt.compare(req.body.password, user.password, function (err, same) {
                if (err) {
                    res.json({
                        error  : true,
                        success: false,
                        message: 'There was an error in hashing the password: ' + String(err)
                    });

                    return;
                }

                if (!same) {
                    res.json({
                        error  : false,
                        success: false,
                        message: 'Invalid password.'
                    });

                    return;
                }

                bcrypt.hash(user.username + user.password, 10, function (err, hash) {
                    if (err) {
                        res.json({
                            error  : true,
                            success: false,
                            message: 'Failed to generate auth key.'
                        });

                        return;
                    }

                    res.set('Set-Cookie', 'auth=' + JSON.stringify({
                        username: user.username,
                        auth: hash
                    }) + ';path=/');

                    res.json({
                        error  : false,
                        success: true,
                        message: 'Logged in.'
                    });
                });
            });
        });
    });
}

/////////////
// Exports //
module.exports.path = '/login';
module.exports.post = post;
<|endoftext|>


<commit_before>var phoneItInDrivers = phoneItInDrivers || {};

phoneItInDrivers.Browser = (function ( document, UI ) {
  function Browser() {
    var fixtureEl, inputs, currentInputNum, priorInputNum, ui,
        my          = {} ,
        mentalModel = {} ;

    function teardown() {
      if ( fixtureEl ) {
        document.body.removeChild( fixtureEl );
      }
      fixtureEl = null;
    }
    this.teardown = teardown;

    function getUi() {
      return ui = ui || new UI();
    }

    function getFixtureEl() {
      if ( ! fixtureEl ) {
        fixtureEl = document.createElement('DIV');
        document.body.appendChild( fixtureEl );
      }
      return fixtureEl;
    }

    function createInputs() {
      var vitro = document.createElement('DIV')

      vitro.innerHTML =
        "<input type='text' />" +
        "<input type='text' />";
      inputs = [ vitro.firstChild, vitro.lastChild ];
      getFixtureEl().appendChild( inputs[ 0 ] );
      getFixtureEl().appendChild( inputs[ 1 ] );
      mentalModel.inputs = [
        { value: undefined, formattedVal: undefined } ,
        { value: undefined, formattedVal: undefined }
      ];
    }
    this.createInputs = createInputs;

    function getInputByNum( inputNum ) {
      return inputs[ inputNum - 1 ];
    }

    function putUnformattedPhoneNumValueIntoInput( inputNum ) {
      var unformatted = '5313456789'     ,
          formatted   = '(531) 345-6789' ;

      getInputByNum( inputNum ).value = unformatted;

      var inputMM = mentalModel.inputs[ inputNum - 1 ];
      inputMM.value        = unformatted ;
      inputMM.formattedVal = formatted   ;
    }
    this.putUnformattedPhoneNumValueIntoInput = putUnformattedPhoneNumValueIntoInput;

    function navigateToInput( inputNum ) {
      priorInputNum = currentInputNum;
      getInputByNum( inputNum ).focus();
      currentInputNum = inputNum;
    }
    this.navigateToInput = navigateToInput;

    function navigateFromInput() {
      var toInputNum = ( currentInputNum % inputs.length ) + 1;
      navigateToInput( toInputNum );
    }
    this.navigateFromInput = navigateFromInput;

    function enablePhoneHelpForInput( inputNum ) {
      var input = getInputByNum( inputNum );
      getUi().bindToInput( input );
    }
    this.enablePhoneHelpForInput = enablePhoneHelpForInput;

    function enterUnformattedPhoneNumber() {
      var unformatted = '3212345678'     ,
          formatted   = '(321) 234-5678' ;

      getInputByNum( currentInputNum ).value = unformatted;

      mmInput = mentalModel.inputs[ currentInputNum -1 ];
      mmInput.value        = unformatted ;
      mmInput.formattedVal = formatted   ;
    }
    this.enterUnformattedPhoneNumber = enterUnformattedPhoneNumber;

    function assertPhoneEntryHelpDisplayedForInput( inputNum ) {
      if ( ! inputNum ) { inputNum = currentInputNum; }

      helpEl = document.getElementById('phin-help');
      if ( ! helpEl ) {
        throw "Phone help element not found";
      }
      var input = getInputByNum( inputNum );
      if ( ! helpEl.previousSibling == input ) {
        throw "Phone help element not next sibling of input";
      }
      var formattedValue = mentalModel.inputs[ inputNum - 1 ].formattedVal ;
      var helpContent = helpEl.innerHTML;
      if ( helpContent.indexOf( formattedValue ) < 0 ){
        throw 'Phone help expected to contain "' + formattedValue + '", but was "' + helpContent + '".';
      }
      return true;
    }
    this.assertPhoneEntryHelpDisplayedForInput = assertPhoneEntryHelpDisplayedForInput;

    function assertNoPhoneEntryShownForPriorInput() {
      inputEl = getInputByNum( priorInputNum );
      potentialHelpEl = inputEl.nextSibling;
      if ( potentialHelpEl && potentialHelpEl.getAttribute('id') === 'phin-help' ) {
        throw "Expected input not to have formatted phone entry help, but does have it.";
      }
    }
    this.assertNoPhoneEntryShownForPriorInput = assertNoPhoneEntryShownForPriorInput;

    function assertFormattedValueInInput( inputNum ) {
      mmInput = mentalModel.inputs[ inputNum - 1 ];
      mmInput.value = mmInput.formattedVal;

      currentValue = getInputByNum( inputNum ).value;

      if ( ! ( currentValue === mmInput.value ) ) {
        throw 'Expected current input value to be "' + mmInput.value + '", but was "' + currentValue + '".';
      }
    }
    this.assertFormattedValueInInput = assertFormattedValueInInput;
  }

  return Browser
})( document, phoneItIn.UI );
<commit_msg>Factor out Input constructor from browser spec driver.<commit_after>var phoneItInDrivers = phoneItInDrivers || {};
phoneItInDrivers.browser = {};

phoneItInDrivers.Browser = (function ( browser, document, UI ) {
  function Browser() {
    var fixtureEl, inputs, currentInput, priorInput, ui,
        my = {};

    function teardown() {
      if ( fixtureEl ) {
        document.body.removeChild( fixtureEl );
      }
      fixtureEl = null;
    }
    this.teardown = teardown;

    function getUi() {
      return ui = ui || new UI();
    }

    function getFixtureEl() {
      if ( ! fixtureEl ) {
        fixtureEl = document.createElement('DIV');
        document.body.appendChild( fixtureEl );
      }
      return fixtureEl;
    }

    function createInputs() {
      inputs = [
        new browser.Input( getFixtureEl(), 1 ) ,
        new browser.Input( getFixtureEl(), 2 )
      ];
    }
    this.createInputs = createInputs;

    function getInputByNum( inputNum ) {
      return inputs[ inputNum - 1 ];
    }

    function findPhoneHelpElement() {
      return document.getElementById('phin-help');
    }

    function putUnformattedPhoneNumValueIntoInput( inputNum ) {
      var input = getInputByNum( inputNum );
      input.enterValue( '5313456789', '(531) 345-6789' );
    }
    this.putUnformattedPhoneNumValueIntoInput = putUnformattedPhoneNumValueIntoInput;

    function enterUnformattedPhoneNumber() {
      currentInput.enterValue( '3212345678', '(321) 234-5678' );
    }
    this.enterUnformattedPhoneNumber = enterUnformattedPhoneNumber;

    function navigateToInput( inputNum ) {
      var input = getInputByNum( inputNum );

      input.focus();
      priorInput = currentInput;
      currentInput = input;
    }
    this.navigateToInput = navigateToInput;

    function navigateFromInput() {
      var toInputNum = ( currentInput.getInputNum() % inputs.length ) + 1;
      navigateToInput( toInputNum );
    }
    this.navigateFromInput = navigateFromInput;

    function enablePhoneHelpForInput( inputNum ) {
      var input = getInputByNum( inputNum );
      getUi().bindToInput( input.getElement() );
    }
    this.enablePhoneHelpForInput = enablePhoneHelpForInput;

    function assertPhoneEntryHelpDisplayedForInput( inputNum ) {
      var contentMismatch,
          input = inputNum ? getInputByNum( inputNum ) : currentInput,
          helpEl = findPhoneHelpElement();

      if ( ! helpEl ) {
        throw "Phone help element not found";
      }
      if( ! input.hasAsNextSiblingElement( helpEl ) ) {
        throw "Phone help element not next sibling of input";
      }
      contentMismatch = input.actualExpectedHelpContentMismatch( helpEl );
      if ( contentMismatch ){
        throw 'Phone help expected to contain "' + contentMismatch.expectedToContain + '", ' +
              'but was "' + contentMismatch.actual + '".';
      }
    }
    this.assertPhoneEntryHelpDisplayedForInput = assertPhoneEntryHelpDisplayedForInput;

    function assertNoPhoneEntryShownForPriorInput() {
      var helpEl = findPhoneHelpElement();

      if( helpEl && priorInput.hasAsNextSiblingElement( helpEl ) ) {
        throw "Expected formatted phone entry help not to be shown for input, but it was shown";
      }
    }
    this.assertNoPhoneEntryShownForPriorInput = assertNoPhoneEntryShownForPriorInput;

    function assertFormattedValueInInput( inputNum ) {
      var mismatch,
          input = getInputByNum( inputNum );

      input.expectValueWasFormatted();

      mismatch = input.actualExpectedValueMismatch();
      if ( mismatch ) {
        throw 'Expected current input value to be "' + mismatch.expected + '", ' +
              'but was "' + mismatch.actual + '".';
      }
    }
    this.assertFormattedValueInInput = assertFormattedValueInInput;
  }

  return Browser
})( phoneItInDrivers.browser, document, phoneItIn.UI );

phoneItInDrivers.browser.Input = (function ( document ) {
  function Input( parentEl, inputNum ) {
    var mentalModel,
        element = document.createElement('INPUT');

    element.setAttribute( 'type', 'text' );
    parentEl.appendChild( element );

    mentalModel = {
      value          : '' ,
      formattedValue : ''
    };

    function getInputNum() { return inputNum; }
    this.getInputNum = getInputNum;

    function getElement() { return element; }
    this.getElement = getElement;

    function hasAsNextSiblingElement(candidateEl) {
      return element.nextSibling == candidateEl;
    }
    this.hasAsNextSiblingElement = hasAsNextSiblingElement;

    function getMentalModel() { return mentalModel; }
    this.getMentalModel = getMentalModel;

    function enterValue( value, formattedValue ) {
      element.value = value;
      mentalModel.value = value;
      mentalModel.formattedValue = formattedValue;
    }
    this.enterValue = enterValue;

    function focus() {
      element.focus();
    }
    this.focus = focus;

    function expectValueWasFormatted() {
      mentalModel.value = mentalModel.formattedValue;
    }
    this.expectValueWasFormatted = expectValueWasFormatted;

    function actualExpectedValueMismatch() {
      return element.value === mentalModel.value ?
        null :
        { actual: element.value, expected: mentalModel.value };
    }
    this.actualExpectedValueMismatch = actualExpectedValueMismatch;

    function actualExpectedHelpContentMismatch( helpEl ) {
      helpContent = helpEl.innerHTML;
      return helpContent.indexOf( mentalModel.formattedValue ) >= 0 ?
        null :
        { actual: helpContent, expectedToContain: mentalModel.formattedValue };
    }
    this.actualExpectedHelpContentMismatch = actualExpectedHelpContentMismatch;
  }

  return Input;
})( document );
<|endoftext|>


<commit_before>Taskar.Sections.Aside = {
  initialize: function(element){
    var url = element.getAttribute("data-update-path");
    Ajax.Responders.register({
      onComplete: function(request){
        var method = request.options.method;
        if (method == "post" || method == "put" || method == "delete"){
          new Ajax.Request(url, { method: "get" })
        }
      }
    });
  },
  updateContent: function(element, newHtml){
    if (element.innerHTML != newHtml){
      element.morph("opacity:0", function(e){
        element.update(newHtml || "").morph("opacity:1");
      });
    }
  },
  updateResponsibilities: function(count){
    this.updateContent($("user_card").down("a"), count);
  },
  updateParticipant: function(element, options){
    element = $(element);
    element.setAttribute("title", options.title || "");
    this.updateContent(element.down('.status'), options.status);
  },
  updateEvents: function(count){
    var badget = $('notify_badge');
    if (!count){
      badget.hide();
    } else {
      badget.update(count).appear();
    }
  }
};<commit_msg>update badget only if needed<commit_after>Taskar.Sections.Aside = {
  initialize: function(element){
    var url = element.getAttribute("data-update-path");
    Ajax.Responders.register({
      onComplete: function(request){
        var method = request.options.method;
        if (method == "post" || method == "put" || method == "delete"){
          new Ajax.Request(url, { method: "get" })
        }
      }
    });
  },
  updateContent: function(element, newHtml){
    if (element.innerHTML != newHtml){
      element.morph("opacity:0", function(e){
        element.update(newHtml || "").morph("opacity:1");
      });
    }
  },
  updateResponsibilities: function(count){
    this.updateContent($("user_card").down("a"), count);
  },
  updateParticipant: function(element, options){
    element = $(element);
    element.setAttribute("title", options.title || "");
    this.updateContent(element.down('.status'), options.status);
  },
  updateEvents: function(count){
    var badget = $('notify_badge');
    if (badget.innerHTML != count){
      if (!count){
        badget.hide();
      } else {
        badget.update(count).appear();
      }
    }
  }
};<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '',
    // Watches files for changes and runs tasks based on the changed files
    watch: {
      js: {
        files: ['dev/*.js'],
        tasks: [],
        options: {
          livereload: '<' + '%= connect.options.livereload %>'
        }
      },
      image: {
        files: ['dev/sprites/*.png'],
        tasks: ['sprite'],
        options: {
          livereload: '<' + '%= connect.options.livereload %>'
        }
      },
      livereload: {
        options: {
          livereload: '<' + '%= connect.options.livereload %>'
        },
        files: [
          'dev/*.html',
          'dev/*.css',
          'dev/*.js',
          'dev/{,*/}*.{png,jpg,jpeg,gif,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('dev')
            ];
          }
        }
      }
    },

    uglify: {
      core: {
        options: {
          mangle: false
        },
        files: {
          'pub/app.js': ['dev/*.js']
        }
      }
    },
    cssmin: {
      core: {
        files: {
          'pub/style.css': ['dev/*.css']
        }
      }
    },

    // Task configuration.
    clean: {
      dist: {
        src: ['pub/**']
      }
    },
    concat: {
      css: {
        files: {
          'pub/style.css': ['dev/*.css', '!dev/style.css']
        }
      }
    },
    imagemin: { // Task
      dist: { // Target
        options: { // Target options
          optimizationLevel: 3
        },
        files: [{
          expand: true,
          cwd: 'dev/',
          src: ['*.png', '*.jpg'],
          dest: 'pub/'
        }]
      }
    },
    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      options: {
        dest: 'pub/'
      },
      html: 'pub/index.html'
    },

    // Performs rewrites based on rev and the useminPrepare configuration
    usemin: {
      options: {
        assetsDirs: [
          'pub/'
        ]
      },
      html: ['pub/{,*/}*.html'],
      css: ['pub/{,*/}*.css']
    },
    htmlmin: {
      dist: {
        options: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          conservativeCollapse: true,
          removeAttributeQuotes: true,
          removeCommentsFromCDATA: true,
          removeEmptyAttributes: false,
          removeOptionalTags: false,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeComments: true,
        },
        files: [{
          expand: true,
          cwd: 'pub/',
          src: '{,*/}*.html',
          dest: 'pub/'
        }]
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          cwd: 'dev/',
          src: ['**', '!sprites/**'],
          dest: 'pub/'
        }]
      }
    },
    sprite:{
      all: {
        src: 'dev/sprites/*.png',
        dest: 'dev/spr.png',
        destCss: 'dev/sprites.css',
        padding: 2,
        cssOpts: {
          cssSelector: function (item) {
            return '.' + item.name;
          }
        }
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-imagemin');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-htmlmin');
  grunt.loadNpmTasks('grunt-usemin');

  // Load in `grunt-spritesmith`
  grunt.loadNpmTasks('grunt-spritesmith');

  // Default task.
  grunt.registerTask('default', ['clean', 'copy', 'concat', 'imagemin']);
  grunt.registerTask('build', ['clean', 'sprite', 'copy', 'cssmin', 'uglify', 'imagemin', 'useminPrepare', 'usemin', 'htmlmin']);

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.task.run(['clean', 'connect:livereload', 'watch']);
  });
};<commit_msg>run task `sprite` on first `grunt serve`<commit_after>'use strict';

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '',
    // Watches files for changes and runs tasks based on the changed files
    watch: {
      js: {
        files: ['dev/*.js'],
        tasks: [],
        options: {
          livereload: '<' + '%= connect.options.livereload %>'
        }
      },
      image: {
        files: ['dev/sprites/*.png'],
        tasks: ['sprite'],
        options: {
          livereload: '<' + '%= connect.options.livereload %>'
        }
      },
      livereload: {
        options: {
          livereload: '<' + '%= connect.options.livereload %>'
        },
        files: [
          'dev/*.html',
          'dev/*.css',
          'dev/*.js',
          'dev/{,*/}*.{png,jpg,jpeg,gif,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('dev')
            ];
          }
        }
      }
    },

    uglify: {
      core: {
        options: {
          mangle: false
        },
        files: {
          'pub/app.js': ['dev/*.js']
        }
      }
    },
    cssmin: {
      core: {
        files: {
          'pub/style.css': ['dev/*.css']
        }
      }
    },

    // Task configuration.
    clean: {
      dist: {
        src: ['pub/**']
      }
    },
    concat: {
      css: {
        files: {
          'pub/style.css': ['dev/*.css', '!dev/style.css']
        }
      }
    },
    imagemin: { // Task
      dist: { // Target
        options: { // Target options
          optimizationLevel: 3
        },
        files: [{
          expand: true,
          cwd: 'dev/',
          src: ['*.png', '*.jpg'],
          dest: 'pub/'
        }]
      }
    },
    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      options: {
        dest: 'pub/'
      },
      html: 'pub/index.html'
    },

    // Performs rewrites based on rev and the useminPrepare configuration
    usemin: {
      options: {
        assetsDirs: [
          'pub/'
        ]
      },
      html: ['pub/{,*/}*.html'],
      css: ['pub/{,*/}*.css']
    },
    htmlmin: {
      dist: {
        options: {
          collapseBooleanAttributes: true,
          collapseWhitespace: true,
          conservativeCollapse: true,
          removeAttributeQuotes: true,
          removeCommentsFromCDATA: true,
          removeEmptyAttributes: false,
          removeOptionalTags: false,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeComments: true,
        },
        files: [{
          expand: true,
          cwd: 'pub/',
          src: '{,*/}*.html',
          dest: 'pub/'
        }]
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          cwd: 'dev/',
          src: ['**', '!sprites/**'],
          dest: 'pub/'
        }]
      }
    },
    sprite:{
      all: {
        src: 'dev/sprites/*.png',
        dest: 'dev/spr.png',
        destCss: 'dev/sprites.css',
        padding: 2,
        cssOpts: {
          cssSelector: function (item) {
            return '.' + item.name;
          }
        }
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-imagemin');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-htmlmin');
  grunt.loadNpmTasks('grunt-usemin');

  // Load in `grunt-spritesmith`
  grunt.loadNpmTasks('grunt-spritesmith');

  // Default task.
  grunt.registerTask('default', ['clean', 'copy', 'concat', 'imagemin']);
  grunt.registerTask('build', ['clean', 'sprite', 'copy', 'cssmin', 'uglify', 'imagemin', 'useminPrepare', 'usemin', 'htmlmin']);

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.task.run(['clean', 'sprite', 'connect:livereload', 'watch']);
  });
};<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import DayPicker from 'react-day-picker';
import NavigationBar from './NavigationBar';
import WeekDay from './WeekDay';
import { convertModifiersToClassnames, hasRange, isSelectingFirstDay } from './utils';
import cx from 'classnames';
import theme from './theme.css';

class DatePickerRange extends PureComponent {
  state = {
    selectedStartDate: null,
    selectedEndDate: null,
    mouseEnteredEndDate: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (
      props.selectedRange !== undefined &&
      (props.selectedRange.selectedStartDate !== state.selectedStartDate ||
        props.selectedRange.selectedEndDate !== state.selectedEndDate)
    ) {
      return {
        selectedStartDate: props.selectedRange.selectedStartDate,
        selectedEndDate: props.selectedRange.selectedEndDate,
        mouseEnteredEndDate: props.selectedRange.selectedEndDate,
      };
    }

    return null;
  }

  handleDayClick = day => {
    const { selectedStartDate, selectedEndDate } = this.state;

    if (isSelectingFirstDay(selectedStartDate, selectedEndDate, day)) {
      this.setState(
        {
          selectedStartDate: day,
          selectedEndDate: null,
          mouseEnteredEndDate: null,
        },
        () => this.props.onChange({ selectedStartDate: day, selectedEndDate: null }),
      );
    } else {
      this.setState(
        {
          selectedEndDate: day,
          mouseEnteredEndDate: day,
        },
        () => this.props.onChange({ selectedStartDate, selectedEndDate: day }),
      );
    }
  };

  handleDayMouseEnter = day => {
    const { selectedStartDate, selectedEndDate } = this.state;

    if (!isSelectingFirstDay(selectedStartDate, selectedEndDate, day)) {
      this.setState({
        mouseEnteredEndDate: day,
      });
    }
  };

  render() {
    const { className, size, ...others } = this.props;
    const { selectedStartDate, mouseEnteredEndDate } = this.state;
    const modifiers = { from: selectedStartDate, to: mouseEnteredEndDate };
    const selectedDays = [selectedStartDate, { from: selectedStartDate, to: mouseEnteredEndDate }];

    const classNames = cx(
      theme['date-picker'],
      theme[`is-${size}`],
      {
        [theme['has-range']]: hasRange(selectedDays),
      },
      className,
    );

    return (
      <DayPicker
        {...others}
        className={classNames}
        classNames={theme}
        modifiers={convertModifiersToClassnames(modifiers, theme)}
        navbarElement={<NavigationBar size={size} />}
        onDayClick={this.handleDayClick}
        onDayMouseEnter={this.handleDayMouseEnter}
        selectedDays={selectedDays}
        weekdayElement={<WeekDay size={size} />}
      />
    );
  }
}

DatePickerRange.propTypes = {
  className: PropTypes.string,
  onChange: PropTypes.func,
  selectedRange: PropTypes.object,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

DatePickerRange.defaultProps = {
  size: 'medium',
};

export default DatePickerRange;
<commit_msg>We don't need to check if the selection is a range. We know that since the components name is DatePickerRange<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import DayPicker from 'react-day-picker';
import NavigationBar from './NavigationBar';
import WeekDay from './WeekDay';
import { convertModifiersToClassnames, isSelectingFirstDay } from './utils';
import cx from 'classnames';
import theme from './theme.css';

class DatePickerRange extends PureComponent {
  state = {
    selectedStartDate: null,
    selectedEndDate: null,
    mouseEnteredEndDate: null,
  };

  static getDerivedStateFromProps(props, state) {
    if (
      props.selectedRange !== undefined &&
      (props.selectedRange.selectedStartDate !== state.selectedStartDate ||
        props.selectedRange.selectedEndDate !== state.selectedEndDate)
    ) {
      return {
        selectedStartDate: props.selectedRange.selectedStartDate,
        selectedEndDate: props.selectedRange.selectedEndDate,
        mouseEnteredEndDate: props.selectedRange.selectedEndDate,
      };
    }

    return null;
  }

  handleDayClick = day => {
    const { selectedStartDate, selectedEndDate } = this.state;

    if (isSelectingFirstDay(selectedStartDate, selectedEndDate, day)) {
      this.setState(
        {
          selectedStartDate: day,
          selectedEndDate: null,
          mouseEnteredEndDate: null,
        },
        () => this.props.onChange({ selectedStartDate: day, selectedEndDate: null }),
      );
    } else {
      this.setState(
        {
          selectedEndDate: day,
          mouseEnteredEndDate: day,
        },
        () => this.props.onChange({ selectedStartDate, selectedEndDate: day }),
      );
    }
  };

  handleDayMouseEnter = day => {
    const { selectedStartDate, selectedEndDate } = this.state;

    if (!isSelectingFirstDay(selectedStartDate, selectedEndDate, day)) {
      this.setState({
        mouseEnteredEndDate: day,
      });
    }
  };

  render() {
    const { className, size, ...others } = this.props;
    const { selectedStartDate, mouseEnteredEndDate } = this.state;
    const modifiers = { from: selectedStartDate, to: mouseEnteredEndDate };
    const selectedDays = [selectedStartDate, { from: selectedStartDate, to: mouseEnteredEndDate }];

    const classNames = cx(
      theme['date-picker'],
      theme['has-range'],
      theme[`is-${size}`],
      className,
    );

    return (
      <DayPicker
        {...others}
        className={classNames}
        classNames={theme}
        modifiers={convertModifiersToClassnames(modifiers, theme)}
        navbarElement={<NavigationBar size={size} />}
        onDayClick={this.handleDayClick}
        onDayMouseEnter={this.handleDayMouseEnter}
        selectedDays={selectedDays}
        weekdayElement={<WeekDay size={size} />}
      />
    );
  }
}

DatePickerRange.propTypes = {
  className: PropTypes.string,
  onChange: PropTypes.func,
  selectedRange: PropTypes.object,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
};

DatePickerRange.defaultProps = {
  size: 'medium',
};

export default DatePickerRange;
<|endoftext|>


<commit_before>/**
 * This file contains the readily availbile functions which interact with the
 * ITSLanguage role API.
 */

import {authorisedRequest} from './communication';

// The URL for the role handler(s).
const url = '/roles';

/**
 * Create a new role.
 *
 * @param {Object} role - The role to create.
 * @param {string} [role.id] - The category identifier. If none is given, one is generated.
 * @param {Array} role.permissions - Array of permissions this role is authorized for.
 *
 * @returns {Promise} - The user creation promise.
 */
export function create(role) {
  return authorisedRequest('POST', url, role);
}

/**
 * Get a single role by its ID.
 *
 * @param {string} id - The ID of the desired role.
 *
 * @returns {Promise} - The promise for the role.
 */
export function getById(id) {
  return authorisedRequest('GET', `${url}/${id}`);
}

/**
 * Get a all roles.
 *
 * By default all roles are fetched though it is allowed to pass
 * filters as a `URLSearchParams` object.
 *
 * @param {URLSearchParams} [filters] - The filters to apply to the category
 *                                      list.
 *
 * @throws {Promise.<string>} - If the given optional filters are not an
 *                              instance of `URLSearchParams`.
 *
 * @returns {Promise} - The promise for the roles.
 */
export function getAll(filters) {
  let urlWithFilters = url;

  if (filters) {
    if (!(filters instanceof URLSearchParams)) {
      return Promise.reject('The filters should be a `URLSearchParams` object.');
    }

    urlWithFilters += `?${filters.toString()}`;
  }

  return authorisedRequest('GET', urlWithFilters);
}
<commit_msg>docs: update the roles api docs<commit_after>/**
 * This file contains the readily availbile functions which interact with the
 * ITSLanguage role API.
 *
 * Roles are named groups of permissions. A role is typically assigned to a user
 * to grant a user permissions.
 *
 * @see https://itslanguage.github.io/itslanguage-docs/api/roles/index.html
 */

import {authorisedRequest} from './communication';

// The URL for the role handler(s).
const url = '/roles';

/**
 * Create a new role.
 *
 * @param {Object} role - The role to create.
 * @param {string} [role.id] - The category identifier. If none is given, one is generated.
 * @param {Array} role.permissions - Array of permissions this role is authorized for.
 *
 * @returns {Promise} - The user creation promise.
 */
export function create(role) {
  return authorisedRequest('POST', url, role);
}

/**
 * Get a single role by its ID.
 *
 * @param {string} id - The ID of the desired role.
 *
 * @returns {Promise} - The promise for the role.
 */
export function getById(id) {
  return authorisedRequest('GET', `${url}/${id}`);
}

/**
 * Get a all roles.
 *
 * By default all roles are fetched though it is allowed to pass
 * filters as a `URLSearchParams` object.
 *
 * @param {URLSearchParams} [filters] - The filters to apply to the category
 *                                      list.
 *
 * @throws {Promise.<string>} - If the given optional filters are not an
 *                              instance of `URLSearchParams`.
 *
 * @returns {Promise} - The promise for the roles.
 */
export function getAll(filters) {
  let urlWithFilters = url;

  if (filters) {
    if (!(filters instanceof URLSearchParams)) {
      return Promise.reject('The filters should be a `URLSearchParams` object.');
    }

    urlWithFilters += `?${filters.toString()}`;
  }

  return authorisedRequest('GET', urlWithFilters);
}
<|endoftext|>


<commit_before>module.exports = function (grunt) {
	'use strict';

	var options = {
		pkg: require('./package'), // <%%=pkg.name%>

		<% if (statix == true) {
			%>site: grunt.file.readYAML('statix/src/data/site.yml'),<%
		} %>

		// Global Grunt vars. Edit this file to change vars
		config : require('./_grunt-configs/config.js')
	};

	// Load grunt tasks automatically
	require('load-grunt-tasks')(grunt, {pattern: ["grunt-*", "chotto"<%
		if (statix === true) {
			%>, "assemble"<%
		}
	%>]});

	// Load grunt configurations automatically
	var configs = require('load-grunt-configs')(grunt, options);

	// Define the configuration for all the tasks
	grunt.initConfig(configs);

	/**
	 * Available tasks:
	 * grunt            : Alias for 'serve' task, below
	 * grunt serve      : watch js, images & scss and run a local server
	 * grunt watch      : run sass:kickoff, uglify and livereload
	 * grunt dev        : run uglify, sass:kickoff & autoprefixer:kickoff
	 * grunt deploy     : run jshint, uglify, sass:kickoff and csso
	 * grunt styleguide : watch js & scss, run a local server for editing the styleguide<%
	if (grunticon === true) {%>
	 * grunt icons      : generate the icons. uses svgmin and grunticon<% } %>
	 * grunt checks     : run jshint & scsslint
	 */

	/**
	 * GRUNT * Alias for 'serve' task, below
	 */
	grunt.registerTask('default', ['serve']);

	/**
	 * GRUNT SERVE * A task for a static server with a watch
	 * run browserSync and watch
	 */
	grunt.registerTask('serve', [<%
		if (statix === true) {%>
		'clean:all',<% } %><%
		if (shims === true) {%>
		'shimly',<% } %>
		'compileJS',
		'compileCSS',
		'clean:tempCSS',
		'images',<%
		if (statix === true) {%>
		'copy',
		'assemble',<%
		} else {
		%>'copy:modernizr',<%
		} %>
		'browserSync:serve',
		'watch'
	]);

	/**
	 * GRUNT DEV * A task for development
	 * run uglify, sass:kickoff & autoprefixer:kickoff
	 */
	grunt.registerTask('dev', [<%
		if (statix === true) {%>
		'clean:all',<% } %><%
		if (shims === true) {%>
		'shimly',<% } %>
		'compileJS',
		'compileCSS',
		'clean:tempCSS',
		'images',<%
		if (statix === true) {%>
		'copy',
		'assemble'<%
		} else {
		%>'copy:modernizr'<%
		} %>
	]);

	/**
	 * GRUNT DEPLOY * A task for your production environment
	 * run jshint, uglify and sass:production
	 */
	grunt.registerTask('deploy', [<%
		if (statix === true) {%>
		'clean:all',<% } %><%
		if (shims === true) {%>
		'shimly',<% } %>
		'compileJS',
		'compileCSS',
		'csso',
		'clean:tempCSS',
		'images',<%
		if (statix === true) {%>
		'copy',
		'assemble'<%
		} else {
		%>'copy:modernizr'<%
		} %>
	]);

<%
	if (styleguide === true) {
%>
	/**
	 * GRUNT STYLEGUIDE * A task to view the styleguide
	 */
	grunt.registerTask('styleguide', [<%
		if (statix === true) {%>
		'clean:all',<% } %><%
		if (shims === true) {%>
		'shimly',<% } %>
		'compileJS',
		'compileCSS',
		'clean:tempCSS',
		'images',<%
		if (statix === true) {%>
		'copy',
		'assemble',<%
		} else {
		%>'copy:modernizr',<%
		} %>
		'browserSync:styleguide',
		'watch'
	]);<%
} %>

	/**
	 * GRUNT IMAGES * A task to compress all non-grunticon images
	 */<%
	if (grunticon === true) {%>
	grunt.registerTask('images', [
		'imagemin:images',
		'icons'
	]);

	/**
	 * GRUNT ICONS * A task to create all icons using grunticon
	 * run clean, svgmin and grunticon
	 */
	grunt.registerTask('icons', [
		'clean:icons',
		'imagemin:grunticon',
		'grunticon'
	]);<%
} else { %>
	grunt.registerTask('images', [
		'imagemin:images'
	]);<%
} %>

	/**
	 * GRUNT CHECKS * Check code for errors
	 * run jshint
	 */
	grunt.registerTask('checks', [
		'jshint:project',
		'scsslint',
		'validation'
	]);

	/**
	 * Utility tasks
	 */
	// Compile JS
<% if (browserify === true) {%>
	grunt.registerTask('compileJS', [
		'browserify:dev'
	]);
<% } else { %>
	grunt.registerTask('compileJS', [
		'chotto:js',
		'uglify',
	]);
<% } %>

	// Compile CSS
	grunt.registerTask('compileCSS', [
		'sass',
		'autoprefixer'
	]);
};
<commit_msg>update gruntfile<commit_after>/**
 * Grunt tasks:
 * - grunt                   : The default task. Alias for `grunt serve` task below
 * - grunt serve             : watch files and run a static server
 * - grunt watcher           : watch files without static server
 * - grunt compile           : compile scss, js & compress images
 * - grunt compile --release : same as above, but compress CSS as well
 * - grunt icons             : generate the icons using grunticon
 * - grunt images            : compress all non-grunticon images & then run `grunt icons`
 */

var opn = require('opn')

module.exports = function (grunt) {
	'use strict';

	var options = {
		pkg: require('./package'), // <%%=pkg.name%>

		<% if (statix) {
			%>site: grunt.file.readYAML('statix/src/data/site.yml'),<%
		} %>

		// Global Grunt vars. Edit this file to change vars
		config : require('./_grunt-configs/config.js')
	};

	// Load grunt tasks automatically
	require('load-grunt-tasks')(grunt, {pattern: ["grunt-*"<%
		if (!browserify) {
			%>, "chotto"<%
		}
		%><%
		if (statix) {
			%>, "assemble"<%
		}
	%>]});

	// Load grunt configurations automatically
	var configs = require('load-grunt-configs')(grunt, options);

	// Define the configuration for all the tasks
	grunt.initConfig(configs);

	/**
	 * The tasks
	 */

	// grunt
	grunt.registerTask('default', ['serve']);

	// grunt serve
	grunt.registerTask('serve', [
		'compile',
		'browserSync:serve',
		'watch'
	])

	// grunt watcher
	grunt.registerTask('watcher', [
		'compile',
		'watch'
	]);

	// grunt compile
	grunt.registerTask('compile', [<%
		if (browserify) {%>
		'browserify',<%
		} else { %>
		'chotto:js',
		'uglify',<%
		} %>
		'postscss',
		'images'<%
		if (statix) {%>
		'copy:statix',
		'assemble'<%
		} %><%
		if (shims) {%>,
		'shimly'<%
		} %><%
		if (modernizr) {%>,
		'copy:modernizr'<%
		} %>
	]);

	// grunt start
	grunt.registerTask('start', function() {
		opn('http://trykickoff.com/learn/checklist.html');
	});<%
	if (styleguide) { %>

	// grunt styleguide
	grunt.registerTask('styleguide', [
		'compile',
		'browserSync:styleguide',
		'watch'
	]);<%
	} %>

	// grunt images
	grunt.registerTask('images', [
		'newer:imagemin:images'<%
		if (grunticon) {%>,
		'icons'<% } %>
	]);<%
	if (grunticon) {%>

	// grunt icons
	grunt.registerTask('icons', [
		'clean:icons',
		'newer:imagemin:grunticon',
		'grunticon'
	]);<% } %>

	// grunt travis
	grunt.registerTask('travis', [
		'postscss'
	]);

};
<|endoftext|>


<commit_before>'use strict';

const specHelper = require('../../support/specHelper'),
    stripeHandler = require('../../../lib/stripeHandler'),
    models = specHelper.models,
    Invoice = models.Invoice,
    sinon = specHelper.sinon,
    Q = specHelper.Q,
    logger = specHelper.logger,
    moment = require('moment');

var invoiceService = require('../../../services/invoiceService');

describe('invoiceService', () => {
    let createInvoiceStub, updateInvoiceStub,
        newInvoiceloggerStub, findInvoiceStub,
        updateInvoiceloggerStub, newInvoice,
        expectedInvoice, createInvoicePromise,
        updateInvoicePromise, findInvoicePromise,
        createEmptyInvoiceloggerStub, memberEmail, reference,
        createdEmptyInvoice, invoiceId, errorLoggerStub;

    beforeEach(() => {
        createInvoiceStub = sinon.stub(models.Invoice, 'create');
        updateInvoiceStub = sinon.stub(models.Invoice, 'update');
        findInvoiceStub = sinon.stub(models.Invoice, 'findOne');
        newInvoiceloggerStub = sinon.stub(logger, 'logNewInvoiceEvent');
        updateInvoiceloggerStub = sinon.stub(logger, 'logUpdateInvoiceEvent');
        createEmptyInvoiceloggerStub = sinon.stub(logger, 'logCreateEmptyInvoiceEvent');
        errorLoggerStub = sinon.stub(logger, 'logError');

        invoiceId = 1;

        newInvoice = {
            totalAmount: 60,
            paymentType: 'deposit',
            paymentDate: moment().format('L'),
            paymentStatus: 'Pending',
            invoiceId: invoiceId
        };

        expectedInvoice = {
            invoiceId: invoiceId,
            reference: 'FUL1'
        };

        createdEmptyInvoice = {dataValues: {id: 1}};

        memberEmail = 'sherlock@holmes.co.uk';
        reference = 'FUL1234';

        createInvoicePromise = Q.defer();
        createInvoiceStub.returns(createInvoicePromise.promise);

        updateInvoicePromise = Q.defer();
        updateInvoiceStub.returns(updateInvoicePromise.promise);

        findInvoicePromise = Q.defer();
        findInvoiceStub.returns(findInvoicePromise.promise);
    });

    afterEach(() => {
        models.Invoice.create.restore();
        models.Invoice.update.restore();
        models.Invoice.findOne.restore();
        newInvoiceloggerStub.restore();
        updateInvoiceloggerStub.restore();
        createEmptyInvoiceloggerStub.restore();
        errorLoggerStub.restore();
    });

    describe('create empty invoice', () => {
        let membershipType,
            updatedInovice, emptyInvoice;

        beforeEach(() => {
            emptyInvoice = {
                memberEmail: memberEmail,
                totalAmountInCents: 0,
                paymentDate: moment().format('L'),
                paymentType: '',
                reference: ''
            };

            membershipType = 'full';
            updatedInovice = {dataValues: expectedInvoice};
        });

        it ('with member email and membershipType, then update the reference', (done) => {
            createInvoicePromise.resolve(createdEmptyInvoice);
            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.resolve(updatedInovice);

            invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then((createdInvoice) => {
                    expect(createdInvoice.id).toEqual(expectedInvoice.invoiceId);
                }).then(done, done.fail);
        });

        it('logs the create empty invoice event', (done) => {
            createInvoicePromise.resolve(createdEmptyInvoice);
            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.resolve(updatedInovice);

            invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .finally(() => {
                    expect(logger.logCreateEmptyInvoiceEvent).toHaveBeenCalledWith(createdEmptyInvoice);
                    expect(logger.logUpdateInvoiceEvent).toHaveBeenCalledWith(1, {reference: 'FUL1'});
                }).then(done, done.fail);
        });

        it('logs the error when create empty invoice failed', (done) => {
            createInvoicePromise.resolve(createdEmptyInvoice);
            findInvoicePromise.resolve({});

            let promise = invoiceService.createEmptyInvoice(memberEmail, membershipType);

            promise.catch((error) => {
                expect(logger.logError).toHaveBeenCalled();
                expect(error.message).toEqual('An error has occurred internally.');
            }).then(done, done.fail);
        });

        describe('reject the promise when', () => {
            it('create empty invoice failed', (done) => {
                createInvoicePromise.reject('Seriously, we still don\'t have any damn bananas.');

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });

            it('find invoice failed', (done) => {
                createInvoicePromise.resolve(createdEmptyInvoice);
                findInvoicePromise.reject('Seriously, we still don\'t have any damn bananas.');

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });

            it('invoice not found', (done) => {
                createInvoicePromise.resolve(createdEmptyInvoice);
                findInvoicePromise.resolve({});

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });

            it('update invoice failed', (done) => {
                let errorMessage = 'Seriously, we still don\'t have any damn bananas.';
                createInvoicePromise.resolve(createdEmptyInvoice);
                findInvoicePromise.resolve(invoiceId);
                updateInvoicePromise.reject(errorMessage);

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });
        });
    });

    describe('pay for invoice', () => {
        describe('Credit Card/Debit Card Payment', () => {
            let stripeHandlerStub, stripeChargePromise,
            stripeToken, totalAmount,
            loggerStub, failedLoggerStub;

            beforeEach(() => {
                newInvoice.paymentType = 'stripe';
                newInvoice.paymentStatus = 'PAID';
                newInvoice.transactionId = 'trans_1';
                newInvoice.stripeToken = 'token';

                stripeToken='47';
                totalAmount = 123;

                stripeHandlerStub = sinon.stub(stripeHandler, 'chargeCard');
                stripeChargePromise = Q.defer();
                stripeHandlerStub.returns(stripeChargePromise.promise);

                loggerStub = sinon.stub(logger, 'logNewChargeEvent');
                failedLoggerStub = sinon.stub(logger, 'logNewFailedCharge');
            });

            afterEach(() => {
                stripeHandler.chargeCard.restore();
                loggerStub.restore();
                failedLoggerStub.restore();
            });

            it('should call charge card handler to charge the card', (done) => {
                stripeChargePromise.resolve();
                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                invoiceService.payForInvoice(newInvoice)
                    .finally(() => {
                        expect(stripeHandler.chargeCard).toHaveBeenCalledWith(newInvoice.stripeToken, newInvoice.totalAmount);
                        done();
                    });
            });

            it('After charge, logger should log', (done) => {
                stripeChargePromise.resolve();
                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                let promise = invoiceService.payForInvoice(newInvoice);

                promise.finally(() => {
                    expect(logger.logNewChargeEvent).toHaveBeenCalledWith(newInvoice.stripeToken);
                    expect(logger.logNewFailedCharge).not.toHaveBeenCalled();
                    done();
                });
            });

            it('update stripe reference with passed in values', (done) => {
                let invoice = {
                    totalAmountInCents: 6000,
                    paymentDate: moment().format('L'),
                    paymentType: 'stripe',
                    paymentStatus: 'PAID',
                    transactionId: 'trans_1'
                };

                stripeChargePromise.resolve({id:'trans_1'});
                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                invoiceService.payForInvoice(newInvoice)
                    .then((updatedInvoice) => {
                        expect(updatedInvoice.dataValues.id).toEqual(expectedInvoice.id);
                        expect(updatedInvoice.dataValues.reference).toEqual(expectedInvoice.reference);

                        expect(Invoice.update).toHaveBeenCalledWith(invoice, {where: {id: 1}});
                    }).then(done, done.fail);
            });

            it('If charge card fails, logger should log failed event', (done) => {
                let errorMessage = 'Charge card failed with Stripe!';
                stripeChargePromise.reject(errorMessage);

                invoiceService.payForInvoice(newInvoice)
                .then(() => {
                    done.fail('payForInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    expect(logger.logNewFailedCharge).toHaveBeenCalledWith(newInvoice.stripeToken, errorMessage);
                }).then(done, done.fail);
            });

            it('If charge card fails, should reject promise with charg card error', (done) => {
                stripeChargePromise.reject('Charge card failed with Stripe!');

                invoiceService.payForInvoice(newInvoice)
                .then(() => {
                    done.fail('payForInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error.name).toEqual('ChargeCardError');
                    expect(error.message).toEqual('Failed to charge card!');
                }).then(done, done.fail);
            });
        });

        describe('Direct debit, cheque, and no contribute payment', () => {
            it('update the exisiting invoice', (done) => {
                let invoice = {
                    totalAmountInCents: 6000,
                    paymentDate: moment().format('L'),
                    paymentType: 'deposit',
                    paymentStatus: 'Pending'
                };

                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                invoiceService.payForInvoice(newInvoice)
                    .then((updatedInvoice) => {
                        expect(updatedInvoice.dataValues.id).toEqual(expectedInvoice.id);
                        expect(updatedInvoice.dataValues.reference).toEqual(expectedInvoice.reference);

                        expect(Invoice.update).toHaveBeenCalledWith(invoice, {where: {id: 1}});
                    }).then(done, done.fail);
            });
        });

        it('logs update invoice event', (done) => {
            let invoice = {
                totalAmountInCents: 6000,
                paymentDate: moment().format('L'),
                paymentType: 'deposit',
                paymentStatus: 'Pending'
            };

            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.resolve({dataValues: expectedInvoice});

            invoiceService.payForInvoice(newInvoice)
                .finally(() => {
                    expect(logger.logUpdateInvoiceEvent).toHaveBeenCalledWith(1, invoice);
                }).then(done, done.fail);
        });

        it('rejects the promise when update invoice failed, and log the error', (done) => {
            let errorMessage = 'Seriously, we still don\'t have any damn bananas.';
            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.reject(errorMessage);

            invoiceService.payForInvoice(newInvoice)
            .then(() => {
                done.fail('payForInvoice should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(error).toEqual(errorMessage);
            }).then(done, done.fail);
        });

        it('rejects the promise when find invoice failed, and log the error', (done) => {
            findInvoicePromise.resolve({});

            invoiceService.payForInvoice(newInvoice)
            .then(() => {
                done.fail('payForInvoice should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(error.message).toEqual('Invoice not found for Id: 1');
            }).then(done, done.fail);
        });
    });

    describe('paypalChargeSuccess', () => {
        let updateLoggerStub, failedUpdateLoggerStub;

        beforeEach( () => {
            updateLoggerStub = sinon.stub(logger, 'logNewPaypalUpdate');
            failedUpdateLoggerStub = sinon.stub(logger, 'logNewFailedPaypalUpdate');
        });

        afterEach( () => {
            updateLoggerStub.restore();
            failedUpdateLoggerStub.restore();
        });

        it('should not call the error logger when finds matching invoice in db' , (done) => {
            updateInvoicePromise.resolve([1]);

            let promise = invoiceService.paypalChargeSuccess(23, 1);

            promise.finally(() => {
                expect(updateLoggerStub).toHaveBeenCalled();
                expect(failedUpdateLoggerStub).not.toHaveBeenCalled();
                expect(promise.isResolved()).toBe(true);
            }).then(done, done.fail);
        });

        it('should call the error logger when no matching invoice id in database' , (done) => {
            updateInvoicePromise.resolve([0]);

            invoiceService.paypalChargeSuccess(23, 1)
            .then(() => {
                done.fail('paypalChargeSuccess should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(updateLoggerStub).toHaveBeenCalled();
                expect(failedUpdateLoggerStub).toHaveBeenCalled();
                expect(error).not.toBeNull();
            }).then(done, done.fail);

        });

        it('should call the error logger when no multiple matching invoice id in database' , (done) => {
            updateInvoicePromise.resolve([2]);

            invoiceService.paypalChargeSuccess(23, 1)
            .then(() => {
                done.fail('paypalChargeSuccess should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(updateLoggerStub).toHaveBeenCalled();
                expect(failedUpdateLoggerStub).toHaveBeenCalled();
                expect(error).not.toBeNull();
            }).then(done, done.fail);
        });
      });

    describe('unconfirmedPaymentList', () => {
        let invoiceFindAllStub, findAllPromise, unconfirmedPaymentValue, expectedOutput;

        beforeEach(() => {
            invoiceFindAllStub = sinon.stub(models.Invoice, 'findAll');
            findAllPromise = Q.defer();
            invoiceFindAllStub.returns(findAllPromise.promise);
            unconfirmedPaymentValue = [
                {
                    dataValues: {
                        reference: 'INT34',
                        paymentType: 'deposit',
                        totalAmountInCents: '2000',
                        paymentStatus: 'Pending',
                        member: {
                            dataValues: {
                                firstName: 'Gotta catch em all',
                                lastName: 'Pokemans Pokewomans Pokepeople'
                            }
                        }
                    }
                }];
            expectedOutput = [{
                firstName: 'Gotta catch em all',
                lastName: 'Pokemans Pokewomans Pokepeople',
                reference: 'INT34',
                paymentType: 'deposit',
                totalAmountInCents: '2000',
                paymentStatus: 'Pending'
            }];
        });

        afterEach(() => {
            invoiceFindAllStub.restore();
        });

        it('Should retrieve the unconfirmed payments', (done) => {
            findAllPromise.resolve(unconfirmedPaymentValue);

            let promise = invoiceService.unconfirmedPaymentList();

            promise.then((value) => {
                expect(invoiceFindAllStub).toHaveBeenCalled();
                expect(value).toEqual(expectedOutput);
            }).then(done, done.fail)
                .catch(done.fail);
        });

        it('Should throw an error if findAll fails', (done) => {
            findAllPromise.reject('Could not connect to database');

            let promise = invoiceService.unconfirmedPaymentList();

            promise.then(() => {
                done.fail('Should not go into then');
            }).catch((err) => {
                expect(err).toEqual('An error has occurred while fetching unconfirmed members');
                done();
            });
        });
    });
});
<commit_msg>Added acceptPayment invoice specs<commit_after>'use strict';

const specHelper = require('../../support/specHelper'),
    stripeHandler = require('../../../lib/stripeHandler'),
    models = specHelper.models,
    Invoice = models.Invoice,
    sinon = specHelper.sinon,
    Q = specHelper.Q,
    logger = specHelper.logger,
    moment = require('moment');

var invoiceService = require('../../../services/invoiceService');

describe('invoiceService', () => {
    let createInvoiceStub, updateInvoiceStub,
        newInvoiceloggerStub, findInvoiceStub,
        updateInvoiceloggerStub, newInvoice,
        expectedInvoice, createInvoicePromise,
        updateInvoicePromise, findInvoicePromise,
        createEmptyInvoiceloggerStub, memberEmail, reference,
        createdEmptyInvoice, invoiceId, errorLoggerStub;

    beforeEach(() => {
        createInvoiceStub = sinon.stub(models.Invoice, 'create');
        updateInvoiceStub = sinon.stub(models.Invoice, 'update');
        findInvoiceStub = sinon.stub(models.Invoice, 'findOne');
        newInvoiceloggerStub = sinon.stub(logger, 'logNewInvoiceEvent');
        updateInvoiceloggerStub = sinon.stub(logger, 'logUpdateInvoiceEvent');
        createEmptyInvoiceloggerStub = sinon.stub(logger, 'logCreateEmptyInvoiceEvent');
        errorLoggerStub = sinon.stub(logger, 'logError');

        invoiceId = 1;

        newInvoice = {
            totalAmount: 60,
            paymentType: 'deposit',
            paymentDate: moment().format('L'),
            paymentStatus: 'Pending',
            invoiceId: invoiceId
        };

        expectedInvoice = {
            invoiceId: invoiceId,
            reference: 'FUL1'
        };

        createdEmptyInvoice = {dataValues: {id: 1}};

        memberEmail = 'sherlock@holmes.co.uk';
        reference = 'FUL1234';

        createInvoicePromise = Q.defer();
        createInvoiceStub.returns(createInvoicePromise.promise);

        updateInvoicePromise = Q.defer();
        updateInvoiceStub.returns(updateInvoicePromise.promise);

        findInvoicePromise = Q.defer();
        findInvoiceStub.returns(findInvoicePromise.promise);
    });

    afterEach(() => {
        models.Invoice.create.restore();
        models.Invoice.update.restore();
        models.Invoice.findOne.restore();
        newInvoiceloggerStub.restore();
        updateInvoiceloggerStub.restore();
        createEmptyInvoiceloggerStub.restore();
        errorLoggerStub.restore();
    });

    describe('create empty invoice', () => {
        let membershipType,
            updatedInovice, emptyInvoice;

        beforeEach(() => {
            emptyInvoice = {
                memberEmail: memberEmail,
                totalAmountInCents: 0,
                paymentDate: moment().format('L'),
                paymentType: '',
                reference: ''
            };

            membershipType = 'full';
            updatedInovice = {dataValues: expectedInvoice};
        });

        it ('with member email and membershipType, then update the reference', (done) => {
            createInvoicePromise.resolve(createdEmptyInvoice);
            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.resolve(updatedInovice);

            invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then((createdInvoice) => {
                    expect(createdInvoice.id).toEqual(expectedInvoice.invoiceId);
                }).then(done, done.fail);
        });

        it('logs the create empty invoice event', (done) => {
            createInvoicePromise.resolve(createdEmptyInvoice);
            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.resolve(updatedInovice);

            invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .finally(() => {
                    expect(logger.logCreateEmptyInvoiceEvent).toHaveBeenCalledWith(createdEmptyInvoice);
                    expect(logger.logUpdateInvoiceEvent).toHaveBeenCalledWith(1, {reference: 'FUL1'});
                }).then(done, done.fail);
        });

        it('logs the error when create empty invoice failed', (done) => {
            createInvoicePromise.resolve(createdEmptyInvoice);
            findInvoicePromise.resolve({});

            let promise = invoiceService.createEmptyInvoice(memberEmail, membershipType);

            promise.catch((error) => {
                expect(logger.logError).toHaveBeenCalled();
                expect(error.message).toEqual('An error has occurred internally.');
            }).then(done, done.fail);
        });

        describe('reject the promise when', () => {
            it('create empty invoice failed', (done) => {
                createInvoicePromise.reject('Seriously, we still don\'t have any damn bananas.');

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });

            it('find invoice failed', (done) => {
                createInvoicePromise.resolve(createdEmptyInvoice);
                findInvoicePromise.reject('Seriously, we still don\'t have any damn bananas.');

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });

            it('invoice not found', (done) => {
                createInvoicePromise.resolve(createdEmptyInvoice);
                findInvoicePromise.resolve({});

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });

            it('update invoice failed', (done) => {
                let errorMessage = 'Seriously, we still don\'t have any damn bananas.';
                createInvoicePromise.resolve(createdEmptyInvoice);
                findInvoicePromise.resolve(invoiceId);
                updateInvoicePromise.reject(errorMessage);

                invoiceService.createEmptyInvoice(memberEmail, membershipType)
                .then(() => {
                    done.fail('createEmptyInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    done();
                });
            });
        });
    });

    describe('pay for invoice', () => {
        describe('Credit Card/Debit Card Payment', () => {
            let stripeHandlerStub, stripeChargePromise,
            stripeToken, totalAmount,
            loggerStub, failedLoggerStub;

            beforeEach(() => {
                newInvoice.paymentType = 'stripe';
                newInvoice.paymentStatus = 'PAID';
                newInvoice.transactionId = 'trans_1';
                newInvoice.stripeToken = 'token';

                stripeToken='47';
                totalAmount = 123;

                stripeHandlerStub = sinon.stub(stripeHandler, 'chargeCard');
                stripeChargePromise = Q.defer();
                stripeHandlerStub.returns(stripeChargePromise.promise);

                loggerStub = sinon.stub(logger, 'logNewChargeEvent');
                failedLoggerStub = sinon.stub(logger, 'logNewFailedCharge');
            });

            afterEach(() => {
                stripeHandler.chargeCard.restore();
                loggerStub.restore();
                failedLoggerStub.restore();
            });

            it('should call charge card handler to charge the card', (done) => {
                stripeChargePromise.resolve();
                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                invoiceService.payForInvoice(newInvoice)
                    .finally(() => {
                        expect(stripeHandler.chargeCard).toHaveBeenCalledWith(newInvoice.stripeToken, newInvoice.totalAmount);
                        done();
                    });
            });

            it('After charge, logger should log', (done) => {
                stripeChargePromise.resolve();
                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                let promise = invoiceService.payForInvoice(newInvoice);

                promise.finally(() => {
                    expect(logger.logNewChargeEvent).toHaveBeenCalledWith(newInvoice.stripeToken);
                    expect(logger.logNewFailedCharge).not.toHaveBeenCalled();
                    done();
                });
            });

            it('update stripe reference with passed in values', (done) => {
                let invoice = {
                    totalAmountInCents: 6000,
                    paymentDate: moment().format('L'),
                    paymentType: 'stripe',
                    paymentStatus: 'PAID',
                    transactionId: 'trans_1'
                };

                stripeChargePromise.resolve({id:'trans_1'});
                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                invoiceService.payForInvoice(newInvoice)
                    .then((updatedInvoice) => {
                        expect(updatedInvoice.dataValues.id).toEqual(expectedInvoice.id);
                        expect(updatedInvoice.dataValues.reference).toEqual(expectedInvoice.reference);

                        expect(Invoice.update).toHaveBeenCalledWith(invoice, {where: {id: 1}});
                    }).then(done, done.fail);
            });

            it('If charge card fails, logger should log failed event', (done) => {
                let errorMessage = 'Charge card failed with Stripe!';
                stripeChargePromise.reject(errorMessage);

                invoiceService.payForInvoice(newInvoice)
                .then(() => {
                    done.fail('payForInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error).not.toBeNull();
                    expect(logger.logNewFailedCharge).toHaveBeenCalledWith(newInvoice.stripeToken, errorMessage);
                }).then(done, done.fail);
            });

            it('If charge card fails, should reject promise with charg card error', (done) => {
                stripeChargePromise.reject('Charge card failed with Stripe!');

                invoiceService.payForInvoice(newInvoice)
                .then(() => {
                    done.fail('payForInvoice should have failed, not succeeded, not this time.');
                })
                .catch((error) => {
                    expect(error.name).toEqual('ChargeCardError');
                    expect(error.message).toEqual('Failed to charge card!');
                }).then(done, done.fail);
            });
        });

        describe('Direct debit, cheque, and no contribute payment', () => {
            it('update the exisiting invoice', (done) => {
                let invoice = {
                    totalAmountInCents: 6000,
                    paymentDate: moment().format('L'),
                    paymentType: 'deposit',
                    paymentStatus: 'Pending'
                };

                findInvoicePromise.resolve(createdEmptyInvoice);
                updateInvoicePromise.resolve({dataValues: expectedInvoice});

                invoiceService.payForInvoice(newInvoice)
                    .then((updatedInvoice) => {
                        expect(updatedInvoice.dataValues.id).toEqual(expectedInvoice.id);
                        expect(updatedInvoice.dataValues.reference).toEqual(expectedInvoice.reference);

                        expect(Invoice.update).toHaveBeenCalledWith(invoice, {where: {id: 1}});
                    }).then(done, done.fail);
            });
        });

        it('logs update invoice event', (done) => {
            let invoice = {
                totalAmountInCents: 6000,
                paymentDate: moment().format('L'),
                paymentType: 'deposit',
                paymentStatus: 'Pending'
            };

            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.resolve({dataValues: expectedInvoice});

            invoiceService.payForInvoice(newInvoice)
                .finally(() => {
                    expect(logger.logUpdateInvoiceEvent).toHaveBeenCalledWith(1, invoice);
                }).then(done, done.fail);
        });

        it('rejects the promise when update invoice failed, and log the error', (done) => {
            let errorMessage = 'Seriously, we still don\'t have any damn bananas.';
            findInvoicePromise.resolve(createdEmptyInvoice);
            updateInvoicePromise.reject(errorMessage);

            invoiceService.payForInvoice(newInvoice)
            .then(() => {
                done.fail('payForInvoice should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(error).toEqual(errorMessage);
            }).then(done, done.fail);
        });

        it('rejects the promise when find invoice failed, and log the error', (done) => {
            findInvoicePromise.resolve({});

            invoiceService.payForInvoice(newInvoice)
            .then(() => {
                done.fail('payForInvoice should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(error.message).toEqual('Invoice not found for Id: 1');
            }).then(done, done.fail);
        });
    });

    describe('paypalChargeSuccess', () => {
        let updateLoggerStub, failedUpdateLoggerStub;

        beforeEach( () => {
            updateLoggerStub = sinon.stub(logger, 'logNewPaypalUpdate');
            failedUpdateLoggerStub = sinon.stub(logger, 'logNewFailedPaypalUpdate');
        });

        afterEach( () => {
            updateLoggerStub.restore();
            failedUpdateLoggerStub.restore();
        });

        it('should not call the error logger when finds matching invoice in db' , (done) => {
            updateInvoicePromise.resolve([1]);

            let promise = invoiceService.paypalChargeSuccess(23, 1);

            promise.finally(() => {
                expect(updateLoggerStub).toHaveBeenCalled();
                expect(failedUpdateLoggerStub).not.toHaveBeenCalled();
                expect(promise.isResolved()).toBe(true);
            }).then(done, done.fail);
        });

        it('should call the error logger when no matching invoice id in database' , (done) => {
            updateInvoicePromise.resolve([0]);

            invoiceService.paypalChargeSuccess(23, 1)
            .then(() => {
                done.fail('paypalChargeSuccess should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(updateLoggerStub).toHaveBeenCalled();
                expect(failedUpdateLoggerStub).toHaveBeenCalled();
                expect(error).not.toBeNull();
            }).then(done, done.fail);

        });

        it('should call the error logger when no multiple matching invoice id in database' , (done) => {
            updateInvoicePromise.resolve([2]);

            invoiceService.paypalChargeSuccess(23, 1)
            .then(() => {
                done.fail('paypalChargeSuccess should have failed, not succeeded, not this time.');
            })
            .catch((error) => {
                expect(updateLoggerStub).toHaveBeenCalled();
                expect(failedUpdateLoggerStub).toHaveBeenCalled();
                expect(error).not.toBeNull();
            }).then(done, done.fail);
        });
      });

    describe('unconfirmedPaymentList', () => {
        let invoiceFindAllStub, findAllPromise, unconfirmedPaymentValue, expectedOutput;

        beforeEach(() => {
            invoiceFindAllStub = sinon.stub(models.Invoice, 'findAll');
            findAllPromise = Q.defer();
            invoiceFindAllStub.returns(findAllPromise.promise);
            unconfirmedPaymentValue = [
                {
                    dataValues: {
                        reference: 'INT34',
                        paymentType: 'deposit',
                        totalAmountInCents: '2000',
                        paymentStatus: 'Pending',
                        member: {
                            dataValues: {
                                firstName: 'Gotta catch em all',
                                lastName: 'Pokemans Pokewomans Pokepeople'
                            }
                        }
                    }
                }];
            expectedOutput = [{
                firstName: 'Gotta catch em all',
                lastName: 'Pokemans Pokewomans Pokepeople',
                reference: 'INT34',
                paymentType: 'deposit',
                totalAmountInCents: '2000',
                paymentStatus: 'Pending'
            }];
        });

        afterEach(() => {
            invoiceFindAllStub.restore();
        });

        it('Should retrieve the unconfirmed payments', (done) => {
            findAllPromise.resolve(unconfirmedPaymentValue);

            let promise = invoiceService.unconfirmedPaymentList();

            promise.then((value) => {
                expect(invoiceFindAllStub).toHaveBeenCalled();
                expect(value).toEqual(expectedOutput);
            }).then(done, done.fail)
                .catch(done.fail);
        });

        it('Should throw an error if findAll fails', (done) => {
            findAllPromise.reject('Could not connect to database');

            let promise = invoiceService.unconfirmedPaymentList();

            promise.then(() => {
                done.fail('Should not go into then');
            }).catch((err) => {
                expect(err).toEqual('An error has occurred while fetching unconfirmed members');
                done();
            });
        });
    });

    describe('acceptPayment', () => {
        let reference ='INT8';

        it('Should retrieve the unconfirmed payments', (done) => {
            updateInvoicePromise.resolve([1]);

            let promise = invoiceService.acceptPayment(reference);

            promise.then(() => {
                expect(updateInvoiceStub).toHaveBeenCalled();
            }).then(done, done.fail)
                .catch(done.fail);
        });

        it('Should throw an error if update fails', (done) => {
            updateInvoicePromise.reject('This should not be shown to user');

            let promise = invoiceService.acceptPayment(reference);

            promise.then(() => {
                done.fail('Should not go into then');
            }).catch((err) => {
                expect(err).toEqual('This should not be shown to user');
                done();
            });
        });

        it('Should throw an error if no rows updated', (done) => {
            updateInvoicePromise.resolve([0]);

            let promise = invoiceService.acceptPayment(reference);

            promise.then(() => {
                done.fail('Should not go into then');
            }).catch((err) => {
                expect(err).toEqual('Failed to accept INT8 in the database');
                done();
            });
        });
    });
});
<|endoftext|>