<commit_before>(function(define) {'use strict';
define(function(require) {

// Module that contains methods for displaying and formatting variables/datasets
return function(loader) {
   var utils;
   utils    = require('../utils');

   // build array of arrays of objects (array of rows to be displayed)
   // each object has a `name` and a `value` property.
   // `ncol` is a positive integer
   loader.addInstanceMethod('Variable', 'layOut', function layOut(ncol) {
      var rows, currentRow, i, stringVar, names;
      names = this.names();
      stringVar = this.asString();
      ncol = Math.floor(ncol);
      if (!(ncol >= 1)) { ncol = 1; }
      rows = [];
      currentRow = [];
      for (i = 1; i <= this.length(); i += 1) {
         currentRow.push({
            index: i,
            value: utils.getDefault(stringVar.get(i), ''),
            name:  utils.isMissing(names) ? '' : names.get(i)
         });
         if (currentRow.length >= ncol) {
            rows.push(currentRow);
            currentRow = [];
         }
      }
      // if we have an unfinished row, include it before returning
      if (currentRow.length > 0) { rows.push(currentRow); }
      return rows;
   });

   // Returns a string (an HTML expression) for displaying the variable.
   // `options` can include:
   //  - ncol: a positive integer, defaults to 1
   //  - withNames: a boolean value, defaults to false
   //  - value: an object with properties `tag` and `class` for specifying the
   //           html tag and the class attribute.
   //  - name:  an object... same as value
   //  - row:   an object... same as value
   //  All three of these objects are optional, and their individual parts are
   //  optional as well.  Defaults to `<td>` or `<tr>` for the tag and to
   //  `var-value`, `var-name`, `var-row` for the class.
   loader.addInstanceMethod('Variable', 'toHTML', function toHTML(options) {
      var arr, defaults, makeRow;
      function wrap(params) {
         return function(str, others) {
            return '<' + params.tag + ' class="' + params.class + '" ' +
                  (others || '') + '>' + str +
                  '</' + params.tag + '>';
         };
      }
      defaults = {
         ncol: 1,
         withNames: false,
         value: { tag: 'td', class: 'var-value' },
         name:  { tag: 'td', class: 'var-name' },
         row:   { tag: 'tr', class: 'var-row' }
      };
      options = utils.mixin({}, options, defaults);
      options.value = utils.mixin(options.value, defaults.value);
      options.name  = utils.mixin(options.name, defaults.name);
      options.row   = utils.mixin(options.row, defaults.row);
      makeRow = options.withNames ?
         function(row) {
            // entries include names and values
            return row.map(function(obj) {
               return wrap(options.name)(obj.name  , 'data-relIndex="' + obj.index + '"') +
                      wrap(options.value)(obj.value, 'data-relIndex="' + obj.index + '"');
            }).join('');
         } :
         function(row) {
            return row.map(function(obj) {
               return wrap(options.value)(obj.value, 'data-relIndex="' + obj.index + '"');
            }).join('');
         };
      arr = this.layOut(options.ncol);
      return arr.map(function(row) {
         // row is an array of objects with properties `value` and `name`
         return makeRow(row);
      }).map(wrap(options.row)).join('\n');
   });

   // Returns a string variable for displaying the given variable with
   // specified numerical formatting. `options` is an object and may
   // include the following properties:
   //  - type:  has a string value 'scientific' or 'fixed
   //  - decimals:  indicates fixed number of decimal digits to the right of
   //       the decimal point; has a non-negative integer value; defaults to 4 in
   //       the case of scientific notation, and to 2 otherwise
   loader.addInstanceMethod('Variable', 'format', function format(options) {
      var v, maxDigits;
      v = this.asScalar().names(this.names());
      function nDigits(x) {
         return x === 0 ? 1 : Math.abs(Math.log(Math.abs(x)) / Math.LN10);
      }
      options = utils.mixin({}, options);
      if (!options.type) {
         maxDigits = Math.floor(v.map(nDigits).max());
         options.type = maxDigits > 4 ? 'scientific' : 'fixed';
      }
      if (!(options.decimals >= 0)) {
         options.decimals = options.type === 'scientific' ? 4 : 2;
      }
      return v.map(utils.format[options.type](options.decimals), false, 'string');
   });

// boilerplate below here
};

});

}(typeof define === 'function' && define.amd ? define : function(factory) {
   'use strict';
   module.exports = factory(require);
}));
<commit_msg>Update docs for format.js<commit_after>(function(define) {'use strict';
define(function(require) {

// Module that contains methods for displaying and formatting variables/datasets
return function(loader) {
   var utils;
   utils    = require('../utils');

   /**
    * Lay out the variable's values in rows. Return an array with one entry for
    * each row. Each row entry is an array of objects representing the values.
    * The objects have the form `{ index: i, value: val, name: s }`.
    *
    * The parameter `ncol` specifies how many entries will be in each row (default
    * is 1). If the variable length is not an exact multiple of `ncol`, then the
    * last row will contain fewer entries.
    *
    * Mostly intended as an internal method for use in `Variable#toHTML`.
    */
   loader.addInstanceMethod('Variable', 'layOut', function layOut(ncol) {
      var rows, currentRow, i, stringVar, names;
      names = this.names();
      stringVar = this.asString();
      ncol = Math.floor(ncol);
      if (!(ncol >= 1)) { ncol = 1; }
      rows = [];
      currentRow = [];
      for (i = 1; i <= this.length(); i += 1) {
         currentRow.push({
            index: i,
            value: utils.getDefault(stringVar.get(i), ''),
            name:  utils.isMissing(names) ? '' : names.get(i)
         });
         if (currentRow.length >= ncol) {
            rows.push(currentRow);
            currentRow = [];
         }
      }
      // if we have an unfinished row, include it before returning
      if (currentRow.length > 0) { rows.push(currentRow); }
      return rows;
   });

   /**
    * Return an HTML string displaying the variable. Each value is wrapped in
    * a tag, and that tag may be preceded by a tagged name. Each row is further
    * wrapped in a tag. The default format can be used as the contents of a
    * `table` tag.
    *
    * The `options` object may include:
    *  - `ncol`: The number of "columns" (default is 1).
    *  - `withNames`: Whether names should be included (default is `false`).
    *  - `value`: An object with properties `tag` and `class` for specifying the
    *      html tag and the class attributes to be used for the values.
    *  - `name`:  A similar object to be used for the names.
    *  - `row`:   A similar object to be used for wrapping around each row.
    *  All three of these objects are optional, and their individual parts are
    *  optional as well. The defaults are `<td>` or `<tr>` for the tag and
    *  `var-value`, `var-name` or `var-row` for the class.
    */
   loader.addInstanceMethod('Variable', 'toHTML', function toHTML(options) {
      var arr, defaults, makeRow;
      function wrap(params) {
         return function(str, others) {
            return '<' + params.tag + ' class="' + params.class + '" ' +
                  (others || '') + '>' + str +
                  '</' + params.tag + '>';
         };
      }
      defaults = {
         ncol: 1,
         withNames: false,
         value: { tag: 'td', class: 'var-value' },
         name:  { tag: 'td', class: 'var-name' },
         row:   { tag: 'tr', class: 'var-row' }
      };
      options = utils.mixin({}, options, defaults);
      options.value = utils.mixin(options.value, defaults.value);
      options.name  = utils.mixin(options.name, defaults.name);
      options.row   = utils.mixin(options.row, defaults.row);
      makeRow = options.withNames ?
         function(row) {
            // entries include names and values
            return row.map(function(obj) {
               return wrap(options.name)(obj.name  , 'data-relIndex="' + obj.index + '"') +
                      wrap(options.value)(obj.value, 'data-relIndex="' + obj.index + '"');
            }).join('');
         } :
         function(row) {
            return row.map(function(obj) {
               return wrap(options.value)(obj.value, 'data-relIndex="' + obj.index + '"');
            }).join('');
         };
      arr = this.layOut(options.ncol);
      return arr.map(function(row) {
         // row is an array of objects with properties `value` and `name`
         return makeRow(row);
      }).map(wrap(options.row)).join('\n');
   });

   /**
    * Return a string variable for displaying the given variable with a
    * specified numerical formatting. The `options` object may include:
    *  - `type`: A string value, either 'scientific' or 'fixed'.
    *  - `decimals`: The number of decimal digits to be displayed to the right of
    * the decimal point. Defaults to 4 for 'scientific' format and to 2 for 'fixed'
    * format.
    */
   loader.addInstanceMethod('Variable', 'format', function format(options) {
      var v, maxDigits;
      v = this.asScalar().names(this.names());
      function nDigits(x) {
         return x === 0 ? 1 : Math.abs(Math.log(Math.abs(x)) / Math.LN10);
      }
      options = utils.mixin({}, options);
      if (!options.type) {
         maxDigits = Math.floor(v.map(nDigits).max());
         options.type = maxDigits > 4 ? 'scientific' : 'fixed';
      }
      if (!(options.decimals >= 0)) {
         options.decimals = options.type === 'scientific' ? 4 : 2;
      }
      return v.map(utils.format[options.type](options.decimals), false, 'string');
   });

// boilerplate below here
};

});

}(typeof define === 'function' && define.amd ? define : function(factory) {
   'use strict';
   module.exports = factory(require);
}));
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import * as userActions from '../actions/UserActions'
import * as profileActions from '../actions/ProfileActions'
import * as Storage from '../utils/Storage'

import MainContent from '../components/MainContent'
import UserStatus from '../components/UserStatus'
import StatusManager from '../components/StatusManager'
import DataList from '../components/DataList'
import UserStatusItem from '../components/UserStatusItem'
import PendingBlock from '../components/PendingBlock'

class Profile extends Component {
  constructor(props) {
    super(props)
    this.handleSetStatus = this.handleSetStatus.bind(this)
    this.handleDeleteStatus = this.handleDeleteStatus.bind(this)
    this.handleAddStatus = this.handleAddStatus.bind(this)
    this.handleAddStatusType = this.handleAddStatusType.bind(this)
    this.handleFollowClick = this.handleFollowClick.bind(this)
    this.handleUnfollowClick = this.handleUnfollowClick.bind(this)
    this.handleConfirmDeleteStatusModal = this.handleConfirmDeleteStatusModal.bind(this)
    this.handleHideDeleteStatusModal = this.handleHideDeleteStatusModal.bind(this)
  }

  handleSetStatus(id) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.setUserStatus(id, token.token_type, token.access_token))
  }

  handleConfirmDeleteStatusModal(id, text) {
    this.props.dispatch(profileActions.confirmDeleteStatus(id, text))
  }

  handleHideDeleteStatusModal() {
    this.props.dispatch(profileActions.hideConfirmDeleteStatus())
  }

  handleDeleteStatus(id) {
    this.handleHideDeleteStatusModal()
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.deleteUserStatus(id, token.token_type, token.access_token))
  }

  handleAddStatus(text) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.addUserStatus(text, token.token_type, token.access_token))
  }

  handleFollowClick(username) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.userFollow(username, token.token_type, token.access_token))
  }

  handleUnfollowClick(username) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.userUnfollow(username, token.token_type, token.access_token))
  }

  handleAddStatusType(text) {
    this.props.dispatch(profileActions.addStatusFieldOnChange(text))
  }

  hasUserChanged(oldProps, newProps) {
    return (oldProps.params.username != newProps.params.username)
  }

  hasStatusChanged(oldProps, newProps) {
    return (newProps.userStatus.isStale && !oldProps.userStatus.isStale)
  }

  isUserProfile() {
    return (this.props.user.details &&
            this.props.user.details.username === this.props.params.username)
  }

  isProfileValid() {
    return (this.props.profile.details &&
            this.props.profile.details.statuses instanceof Array)
  }

  isUserFollowing() {
    if (! this.props.user.details ||
        ! this.props.follows.data ||
        ! this.props.follows.data.followers) {
      return false
    }

    var currentUser = this.props.user.details.username
    return (this.props.follows.data.followers.some((follower) => {
              return (follower.username === currentUser)
            }))
  }

  isUserSignedIn() {
    return (this.props.user.details && this.props.user.details.username !== undefined)
  }

  fetchProfile(username) {
    this.props.dispatch(profileActions.fetchProfile(username))
  }

  fetchProfileFollow(username, token) {
    if (this.isUserProfile()) {
      this.props.dispatch(profileActions.fetchUserFollow(token.token_type, token.access_token))
    } else {
      this.props.dispatch(profileActions.fetchProfileFollow(username))
    }
  }

  getUserDetails(token) {
    this.props.dispatch(userActions.getUserDetails(token.token_type, token.access_token))
    this.props.dispatch(userActions.authCheckUser(token.token_type, token.access_token))
  }

  getUserStatuses(token) {
    this.props.dispatch(userActions.getUserStatus(token.token_type, token.access_token))
    this.props.dispatch(profileActions.addStatusFieldOnChange(""))
  }

  refreshState() {
    this.fetchProfile(this.props.params.username)

    var token = Storage.loadAccessToken()
    this.getUserDetails(token)
    this.getUserStatuses(token)
    this.fetchProfileFollow(this.props.params.username, token)
  }

  componentDidMount() {
    this.refreshState()
  }

  componentDidUpdate(prevProps) {
    if (this.hasUserChanged(prevProps, this.props) ||
        this.hasStatusChanged(prevProps, this.props)) {
      this.refreshState()
    }
  }

  renderUserStatus(withUpdate) {
    var className = "profile-block section-block " + (withUpdate ? "col-md-8" : "col-md-12")
    return (

        <UserStatus
          username={this.props.params.username}
          statusHistory={this.props.profile.details.statuses}
          fullHistory={true}
          isFollowing={this.isUserFollowing()}
          isUser={this.isUserProfile()}
          isAuthenticated={this.isUserSignedIn()}
          onFollowClick={this.handleFollowClick}
          onUnfollowClick={this.handleUnfollowClick} />

    )
  }

  renderUpdateStatus(allowed) {
    if (allowed && this.props.userStatus.details.statuses) {
      return (

          <StatusManager
            statuses={this.props.userStatus.details.statuses}
            elementStatus={this.props.addStatusField}
            onSetStatusClick={this.handleSetStatus}
            onConfirmDeleteStatusClick={this.handleConfirmDeleteStatusModal}
            onDeleteStatusClick={this.handleDeleteStatus}
            onAddStatusClick={this.handleAddStatus}
            onAddStatusType={this.handleAddStatusType}
            deleteModalData={this.props.statusDeleteModal}
            closeDeleteConfirmModal={this.handleHideDeleteStatusModal}
            />

      )
    }
    return
  }

  renderFollowBlock(type) {
    if (!this.props.follows.data || this.props.follows.isFetching) {
      return (
        <PendingBlock height="360px" fontSize="32px" />
      )
    } else {
      var sortedData = (this.props.data ? this.props.data[type] : [])

      if (sortedData.length > 0) {
        sortedData = this.props.follows.data[type].sort((a, b) => {
          return a.since - b.since
        })
      }
      return (
        <DataList title={type}
                  data={sortedData}
                  contentHeight={200}
                  itemElement={UserStatusItem}
                  noDataMessage="No users found" />
      )
    }
  }

  renderProfile() {
    if (this.isProfileValid()) {
      return (

            {this.renderUserStatus(this.isUserProfile())}
            {this.renderUpdateStatus(this.isUserProfile())}

              {this.renderFollowBlock("following")}

              {this.renderFollowBlock("followers")}

      )
    } else {
      return (

Cannot find user

      )
    }
  }

  renderPage() {
    if (this.props.profile.isFetching) {
      return (
        <PendingBlock height="363px" fontSize="32px" />
      )
    } else {
      return (
        this.renderProfile()
      )
    }
  }

  render() {
    return (
      <MainContent>
        {this.renderPage()}
      </MainContent>
    )
  }
}

Profile.propTypes = {
  user: PropTypes.object.isRequired
}

function mapStateToProps(state) {
  return {
    user: state.userReducer.userDetails,
    profile: state.profileReducer.profileDetails,
    userStatus: state.userReducer.userStatus,
    addStatusField: state.profileReducer.addStatusField,
    follows: state.profileReducer.profileFollowDetails,
    statusDeleteModal: state.profileReducer.confirmDeleteModal
  }
}

export default connect(mapStateToProps)(Profile)
<commit_msg>chore(Profile): ensure username is always dealt with in lowercase<commit_after>import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import * as userActions from '../actions/UserActions'
import * as profileActions from '../actions/ProfileActions'
import * as Storage from '../utils/Storage'

import MainContent from '../components/MainContent'
import UserStatus from '../components/UserStatus'
import StatusManager from '../components/StatusManager'
import DataList from '../components/DataList'
import UserStatusItem from '../components/UserStatusItem'
import PendingBlock from '../components/PendingBlock'

class Profile extends Component {
  constructor(props) {
    super(props)
    this.handleSetStatus = this.handleSetStatus.bind(this)
    this.handleDeleteStatus = this.handleDeleteStatus.bind(this)
    this.handleAddStatus = this.handleAddStatus.bind(this)
    this.handleAddStatusType = this.handleAddStatusType.bind(this)
    this.handleFollowClick = this.handleFollowClick.bind(this)
    this.handleUnfollowClick = this.handleUnfollowClick.bind(this)
    this.handleConfirmDeleteStatusModal = this.handleConfirmDeleteStatusModal.bind(this)
    this.handleHideDeleteStatusModal = this.handleHideDeleteStatusModal.bind(this)
  }

  handleSetStatus(id) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.setUserStatus(id, token.token_type, token.access_token))
  }

  handleConfirmDeleteStatusModal(id, text) {
    this.props.dispatch(profileActions.confirmDeleteStatus(id, text))
  }

  handleHideDeleteStatusModal() {
    this.props.dispatch(profileActions.hideConfirmDeleteStatus())
  }

  handleDeleteStatus(id) {
    this.handleHideDeleteStatusModal()
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.deleteUserStatus(id, token.token_type, token.access_token))
  }

  handleAddStatus(text) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.addUserStatus(text, token.token_type, token.access_token))
  }

  handleFollowClick(username) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.userFollow(username, token.token_type, token.access_token))
  }

  handleUnfollowClick(username) {
    var token = Storage.loadAccessToken()
    this.props.dispatch(userActions.userUnfollow(username, token.token_type, token.access_token))
  }

  handleAddStatusType(text) {
    this.props.dispatch(profileActions.addStatusFieldOnChange(text))
  }

  hasUserChanged(oldProps, newProps) {
    return (oldProps.params.username.toLowerCase() != newProps.params.username.toLowerCase())
  }

  hasStatusChanged(oldProps, newProps) {
    return (newProps.userStatus.isStale && !oldProps.userStatus.isStale)
  }

  isUserProfile() {
    return (this.props.user.details && this.props.user.details.username &&
            this.props.user.details.username.toLowerCase() === this.props.params.username.toLowerCase())
  }

  isProfileValid() {
    return (this.props.profile.details &&
            this.props.profile.details.statuses instanceof Array)
  }

  isUserFollowing() {
    if (! this.props.user.details ||
        ! this.props.follows.data ||
        ! this.props.follows.data.followers) {
      return false
    }

    var currentUser = (this.props.user.details.username ? this.props.user.details.username.toLowerCase() : "")
    return (this.props.follows.data.followers.some((follower) => {
              return (follower.username.toLowerCase() === currentUser)
            }))
  }

  isUserSignedIn() {
    return (this.props.user.details && this.props.user.details.username !== undefined)
  }

  fetchProfile(username) {
    this.props.dispatch(profileActions.fetchProfile(username))
  }

  fetchProfileFollow(username, token) {
    if (this.isUserProfile()) {
      this.props.dispatch(profileActions.fetchUserFollow(token.token_type, token.access_token))
    } else {
      this.props.dispatch(profileActions.fetchProfileFollow(username))
    }
  }

  getUserDetails(token) {
    this.props.dispatch(userActions.getUserDetails(token.token_type, token.access_token))
    this.props.dispatch(userActions.authCheckUser(token.token_type, token.access_token))
  }

  getUserStatuses(token) {
    this.props.dispatch(userActions.getUserStatus(token.token_type, token.access_token))
    this.props.dispatch(profileActions.addStatusFieldOnChange(""))
  }

  refreshState() {
    this.fetchProfile(this.props.params.username.toLowerCase())

    var token = Storage.loadAccessToken()
    this.getUserDetails(token)
    this.getUserStatuses(token)
    this.fetchProfileFollow(this.props.params.username.toLowerCase(), token)
  }

  componentDidMount() {
    this.refreshState()
  }

  componentDidUpdate(prevProps) {
    if (this.hasUserChanged(prevProps, this.props) ||
        this.hasStatusChanged(prevProps, this.props)) {
      this.refreshState()
    }
  }

  renderUserStatus(withUpdate) {
    var className = "profile-block section-block " + (withUpdate ? "col-md-8" : "col-md-12")
    var profileUsername = (this.props.profile.details.statuses.length > 0 ? this.props.profile.details.statuses[0].username : "")
    return (

        <UserStatus
          username={profileUsername}
          statusHistory={this.props.profile.details.statuses}
          fullHistory={true}
          isFollowing={this.isUserFollowing()}
          isUser={this.isUserProfile()}
          isAuthenticated={this.isUserSignedIn()}
          onFollowClick={this.handleFollowClick}
          onUnfollowClick={this.handleUnfollowClick} />

    )
  }

  renderUpdateStatus(allowed) {
    if (allowed && this.props.userStatus.details.statuses) {
      return (

          <StatusManager
            statuses={this.props.userStatus.details.statuses}
            elementStatus={this.props.addStatusField}
            onSetStatusClick={this.handleSetStatus}
            onConfirmDeleteStatusClick={this.handleConfirmDeleteStatusModal}
            onDeleteStatusClick={this.handleDeleteStatus}
            onAddStatusClick={this.handleAddStatus}
            onAddStatusType={this.handleAddStatusType}
            deleteModalData={this.props.statusDeleteModal}
            closeDeleteConfirmModal={this.handleHideDeleteStatusModal}
            />

      )
    }
    return
  }

  renderFollowBlock(type) {
    if (!this.props.follows.data || this.props.follows.isFetching) {
      return (
        <PendingBlock height="360px" fontSize="32px" />
      )
    } else {
      var sortedData = (this.props.follows.data ? this.props.follows.data[type] : [])

      if (sortedData.length > 0) {
        sortedData = this.props.follows.data[type].sort((a, b) => {
          return a.since - b.since
        })
      }
      return (
        <DataList title={type}
                  data={sortedData}
                  contentHeight={200}
                  itemElement={UserStatusItem}
                  noDataMessage="No users found" />
      )
    }
  }

  renderProfile() {
    if (this.isProfileValid()) {
      return (

            {this.renderUserStatus(this.isUserProfile())}
            {this.renderUpdateStatus(this.isUserProfile())}

              {this.renderFollowBlock("following")}

              {this.renderFollowBlock("followers")}

      )
    } else {
      return (

Cannot find user

      )
    }
  }

  renderPage() {
    if (this.props.profile.isFetching) {
      return (
        <PendingBlock height="363px" fontSize="32px" />
      )
    } else {
      return (
        this.renderProfile()
      )
    }
  }

  render() {
    return (
      <MainContent>
        {this.renderPage()}
      </MainContent>
    )
  }
}

Profile.propTypes = {
  user: PropTypes.object.isRequired
}

function mapStateToProps(state) {
  return {
    user: state.userReducer.userDetails,
    profile: state.profileReducer.profileDetails,
    userStatus: state.userReducer.userStatus,
    addStatusField: state.profileReducer.addStatusField,
    follows: state.profileReducer.profileFollowDetails,
    statusDeleteModal: state.profileReducer.confirmDeleteModal
  }
}

export default connect(mapStateToProps)(Profile)
<|endoftext|>


<commit_before>var rewire = require('rewire');
var validator = rewire('../dist/validator.js');
var validatorFns = validator.__get__('validate');

var msg = {
  name: { missing: 'name must be defined' },
  data: { obj: 'data must be an object' },
  type: {
    invalid: '"fil" is not a valid type',
    missing: 'type must be defined'
  },
  start: {
    num: 'start time must be a number',
    zero: 'start time must be at least zero'
  },
  end: {
    toLow: 'end time must be larger than start time',
    num: 'end time must be a number'
  }
};

describe('Validator', function () {
  var errors;

  beforeEach(function () {
    errors = { type: 'type', key: 'id1', message: msg.type.invalid };
  });

  describe('JSON', function () {
    var data;

    beforeEach(function () {
      data = {
        name: 'filen',
        data: {
          '1': {
            type: 'file',
            start: 0,
            end: 5
          },
          '2': {
            type: 'file',
            start: 0,
            end: 5
          }
        }
      };
    });

    it('should return empty array if no errors', function () {
      expect(validator(data)).toEqual([]);
    });

    it('should create error if name-field is missing', function () {
      delete data.name;
      expect(validator(data)).toEqual([
        { type: 'name', key: 'name', message: msg.name.missing }
      ]);
    });

    it('should create error if data-field is missing', function () {
      delete data.data;
      expect(validator(data)).toEqual([
        { type: 'data', key: 'data', message: msg.data.obj }
      ]);
    });

    describe('data', function () {
      it('should create error if type is invalid', function () {
        data.data['1'].type = 'fil';
        expect(validator(data))
          .toEqual([{ type: 'type', key: '1', message: msg.type.invalid }]);
      });

      it('should create error if start is invalid', function () {
        data.data['1'].start = -2;
        expect(validator(data))
          .toEqual([{ type: 'start', key: '1', message: msg.start.zero }]);
      });

      it('should create error if end-field is missing', function () {
        delete data.data['2'].end;
        expect(validator(data)).toEqual([
          { type: 'end', key: '2', message: msg.end.num }
        ]);
      });
    });
  });

  describe('#type', function () {
    it('should have type-function', function () {
      expect(validatorFns.type).toBeDefined();
    });

    it('should return empty array when type is "file"', function () {
      expect(validatorFns.type([], 'id1', { type: 'file' })).toEqual([]);
    });

    it('should return old errors when data is valid', function () {
      expect(validatorFns.type([errors], 'id1', { type: 'file' })).toEqual([errors]);
    });

    it('should return error when type is wrong', function () {
      expect(validatorFns.type([], 'id1', { type: 'fil' })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.invalid }
      ]);
    });

    it('should return error when type is empty', function () {
      expect(validatorFns.type([], 'id1', { type: '' })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });

    it('should return error when type is undefined', function () {
      expect(validatorFns.type([], 'id1', { })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });

    it('should return error when type is null', function () {
      expect(validatorFns.type([], 'id1', { type: null })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });

    it('should return old and new errors when data is invalid', function () {
      expect(validatorFns.type([errors], 'id1', { type: '' })).toEqual([
          errors,
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });
  });

  describe('#start', function () {
    it('should have start-function', function () {
      expect(validatorFns.start).toBeDefined();
    });

    it('should return empty array when start time is valid', function () {
      expect(validatorFns.start([], 'id1', { start: 0 })).toEqual([]);
    });

    it('should return old errors when start time is valid', function () {
      expect(validatorFns.start([errors], 'id1', { start: 8 })).toEqual([errors]);
    });

    it('should return error when start time is not a number', function () {
      expect(validatorFns.start([], 'id1', { start: '' })).toEqual([
          { type: 'start', key: 'id1', message: msg.start.num }
      ]);
    });

    it('should return old errors and new error when start time is not a number', function () {
      expect(validatorFns.start([errors], 'id1', { start: '' })).toEqual([
          errors,
          { type: 'start', key: 'id1', message: msg.start.num }
      ]);
    });

    it('should return error when start time is negative', function () {
      expect(validatorFns.start([], 'id1', { start: -1 })).toEqual([
          { type: 'start', key: 'id1', message: msg.start.zero }
      ]);
    });

    it('should return old errors and new error when start time is negative', function () {
      expect(validatorFns.start([errors], 'id1', { start: -5 })).toEqual([
          errors,
          { type: 'start', key: 'id1', message: msg.start.zero }
      ]);
    });
  });

  describe('#end', function () {
    it('should have end-function', function () {
      expect(validatorFns.end).toBeDefined();
    });

    it('should not return error when end time is valid', function () {
      expect(validatorFns.end([], 'id1', { start: 0, end: 10 })).toEqual([]);
    });

    it('should return error when end time is lower than start time', function () {
      expect(validatorFns.end([errors], 'id1', { start: 10, end: 1 })).toEqual([
          errors,
          { type: 'end', key: 'id1', message: msg.end.toLow }
      ]);
    });

    it('should return error when end time is missing', function () {
      expect(validatorFns.end([errors], 'id1', { start: 0 })).toEqual([
          errors,
          { type: 'end', key: 'id1', message: msg.end.num }
      ]);
    });

    it('should return error when end time is not a number', function () {
      expect(validatorFns.end([errors], 'id1', { start: [] })).toEqual([
          errors,
          { type: 'end', key: 'id1', message: msg.end.num }
      ]);
    });
  });

  describe('#name', function () {
    it('should be defined', function () {
      expect(validatorFns.name).toBeDefined();
    });

    it('should return empty array if valid', function () {
      expect(validatorFns.name([ ], 'id1', { name: 'hej' })).toEqual([ ]);
    });

    it('should return error if invalid', function () {
      expect(validatorFns.name([ ], 'id1', { })).toEqual([
        { type: 'name', key: 'id1', message: msg.name.missing }
      ]);
    });
  });

  describe('#data', function () {
    it('should be defined', function () {
      expect(validatorFns.data).toBeDefined();
    });

    it('should return empty array if valid', function () {
      expect(validatorFns.data([ ], 'id1', { data: {} })).toEqual([ ]);
    });

    it('should return error if data is not an object', function () {
      var error = { type: 'data', key: 'id1', message: msg.data.obj };
      expect(validatorFns.data([ ], 'id1', { data: 1 })).toEqual([ error ]);
      expect(validatorFns.data([ ], 'id1', { data: [] })).toEqual([ error ]);
      expect(validatorFns.data([ ], 'id1', { })).toEqual([ error ]);
    });
  });
});
<commit_msg>Use fixtures<commit_after>var rewire = require('rewire');
var validator = rewire('../dist/validator.js');
var validatorFns = validator.__get__('validate');
var audioJson = require('./fixtures/audio_json.js');

var msg = {
  name: { missing: 'name must be defined' },
  data: { obj: 'data must be an object' },
  type: {
    invalid: '"fil" is not a valid type',
    missing: 'type must be defined'
  },
  start: {
    num: 'start time must be a number',
    zero: 'start time must be at least zero'
  },
  end: {
    toLow: 'end time must be larger than start time',
    num: 'end time must be a number'
  }
};

describe('Validator', function () {
  var errors;

  beforeEach(function () {
    errors = { type: 'type', key: 'id1', message: msg.type.invalid };
  });

  describe('JSON', function () {
    var data;

    beforeEach(function () {
      data = audioJson();
    });

    it('should return empty array if no errors', function () {
      expect(validator(data)).toEqual([]);
    });

    it('should create error if name-field is missing', function () {
      delete data.name;
      expect(validator(data)).toEqual([
        { type: 'name', key: 'name', message: msg.name.missing }
      ]);
    });

    it('should create error if data-field is missing', function () {
      delete data.data;
      expect(validator(data)).toEqual([
        { type: 'data', key: 'data', message: msg.data.obj }
      ]);
    });

    describe('data', function () {
      it('should create error if type is invalid', function () {
        data.data['1'].type = 'fil';
        expect(validator(data))
          .toEqual([{ type: 'type', key: '1', message: msg.type.invalid }]);
      });

      it('should create error if start is invalid', function () {
        data.data['1'].start = -2;
        expect(validator(data))
          .toEqual([{ type: 'start', key: '1', message: msg.start.zero }]);
      });

      it('should create error if end-field is missing', function () {
        delete data.data['2'].end;
        expect(validator(data)).toEqual([
          { type: 'end', key: '2', message: msg.end.num }
        ]);
      });
    });
  });

  describe('#type', function () {
    it('should have type-function', function () {
      expect(validatorFns.type).toBeDefined();
    });

    it('should return empty array when type is "file"', function () {
      expect(validatorFns.type([], 'id1', { type: 'file' })).toEqual([]);
    });

    it('should return old errors when data is valid', function () {
      expect(validatorFns.type([errors], 'id1', { type: 'file' })).toEqual([errors]);
    });

    it('should return error when type is wrong', function () {
      expect(validatorFns.type([], 'id1', { type: 'fil' })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.invalid }
      ]);
    });

    it('should return error when type is empty', function () {
      expect(validatorFns.type([], 'id1', { type: '' })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });

    it('should return error when type is undefined', function () {
      expect(validatorFns.type([], 'id1', { })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });

    it('should return error when type is null', function () {
      expect(validatorFns.type([], 'id1', { type: null })).toEqual([
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });

    it('should return old and new errors when data is invalid', function () {
      expect(validatorFns.type([errors], 'id1', { type: '' })).toEqual([
          errors,
          { type: 'type', key: 'id1', message: msg.type.missing }
      ]);
    });
  });

  describe('#start', function () {
    it('should have start-function', function () {
      expect(validatorFns.start).toBeDefined();
    });

    it('should return empty array when start time is valid', function () {
      expect(validatorFns.start([], 'id1', { start: 0 })).toEqual([]);
    });

    it('should return old errors when start time is valid', function () {
      expect(validatorFns.start([errors], 'id1', { start: 8 })).toEqual([errors]);
    });

    it('should return error when start time is not a number', function () {
      expect(validatorFns.start([], 'id1', { start: '' })).toEqual([
          { type: 'start', key: 'id1', message: msg.start.num }
      ]);
    });

    it('should return old errors and new error when start time is not a number', function () {
      expect(validatorFns.start([errors], 'id1', { start: '' })).toEqual([
          errors,
          { type: 'start', key: 'id1', message: msg.start.num }
      ]);
    });

    it('should return error when start time is negative', function () {
      expect(validatorFns.start([], 'id1', { start: -1 })).toEqual([
          { type: 'start', key: 'id1', message: msg.start.zero }
      ]);
    });

    it('should return old errors and new error when start time is negative', function () {
      expect(validatorFns.start([errors], 'id1', { start: -5 })).toEqual([
          errors,
          { type: 'start', key: 'id1', message: msg.start.zero }
      ]);
    });
  });

  describe('#end', function () {
    it('should have end-function', function () {
      expect(validatorFns.end).toBeDefined();
    });

    it('should not return error when end time is valid', function () {
      expect(validatorFns.end([], 'id1', { start: 0, end: 10 })).toEqual([]);
    });

    it('should return error when end time is lower than start time', function () {
      expect(validatorFns.end([errors], 'id1', { start: 10, end: 1 })).toEqual([
          errors,
          { type: 'end', key: 'id1', message: msg.end.toLow }
      ]);
    });

    it('should return error when end time is missing', function () {
      expect(validatorFns.end([errors], 'id1', { start: 0 })).toEqual([
          errors,
          { type: 'end', key: 'id1', message: msg.end.num }
      ]);
    });

    it('should return error when end time is not a number', function () {
      expect(validatorFns.end([errors], 'id1', { start: [] })).toEqual([
          errors,
          { type: 'end', key: 'id1', message: msg.end.num }
      ]);
    });
  });

  describe('#name', function () {
    it('should be defined', function () {
      expect(validatorFns.name).toBeDefined();
    });

    it('should return empty array if valid', function () {
      expect(validatorFns.name([ ], 'id1', { name: 'hej' })).toEqual([ ]);
    });

    it('should return error if invalid', function () {
      expect(validatorFns.name([ ], 'id1', { })).toEqual([
        { type: 'name', key: 'id1', message: msg.name.missing }
      ]);
    });
  });

  describe('#data', function () {
    it('should be defined', function () {
      expect(validatorFns.data).toBeDefined();
    });

    it('should return empty array if valid', function () {
      expect(validatorFns.data([ ], 'id1', { data: {} })).toEqual([ ]);
    });

    it('should return error if data is not an object', function () {
      var error = { type: 'data', key: 'id1', message: msg.data.obj };
      expect(validatorFns.data([ ], 'id1', { data: 1 })).toEqual([ error ]);
      expect(validatorFns.data([ ], 'id1', { data: [] })).toEqual([ error ]);
      expect(validatorFns.data([ ], 'id1', { })).toEqual([ error ]);
    });
  });
});
<|endoftext|>


<commit_before>import blacklist from 'blacklist';
import classNames from 'classnames';
import ErrorView from './ErrorView';
import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

function createViewsFromChildren (children) {
	var views = {};
	React.Children.forEach(children, function (view) {
		views[view.props.name] = view;
	});
	return views;
}

var ViewContainer = React.createClass({
	statics: {
		shouldFillVerticalSpace: true
	},
	propTypes: {
		children: React.PropTypes.node
	},
	render () {
		var props = blacklist(this.props, 'children');
		return
{this.props.children}
;
	}
});

var ViewManager = React.createClass({
	statics: {
		shouldFillVerticalSpace: true
	},
	contextTypes: {
		app: React.PropTypes.object.isRequired
	},
	propTypes: {
		name: React.PropTypes.string,
		children: React.PropTypes.node,
		className: React.PropTypes.string,
		defaultView: React.PropTypes.string,
		onViewChange: React.PropTypes.func
	},
	getDefaultProps () {
		return {
			name: '__default'
		};
	},
	getInitialState () {
		return {
			views: createViewsFromChildren(this.props.children),
			currentView: this.props.defaultView,
			options: {}
		};
	},
	componentDidMount () {
		this.context.app.viewManagers[this.props.name] = this;
	},
	componentWillUnmount () {
		delete this.context.app.viewManagers[this.props.name];
	},
	componentWillReceiveProps (nextProps) {
		this.setState({
			views: createViewsFromChildren(this.props.children)
		});
		if (nextProps.name !== this.props.name) {
			this.context.app.viewManagers[nextProps.name] = this;
			delete this.context.app.viewManagers[this.props.name];
		}
		if (nextProps.currentView && nextProps.currentView !== this.state.currentView) {
			this.transitionTo(nextProps.currentView, { viewProps: nextProps.viewProps });
		}
	},
	transitionTo (viewKey, options) {
		if (typeof options === 'string') {
			options = { transition: options };
		}
		if (!options) options = {};
		this.activeTransitionOptions = options;
		this.context.app.viewManagerInTransition = this;
		this.props.onViewChange && this.props.onViewChange(viewKey);
		this.setState({
			currentView: viewKey,
			options: options
		}, () => {
			delete this.activeTransitionOptions;
			delete this.context.app.viewManagerInTransition;
		});
	},
	renderViewContainer () {
		var viewKey = this.state.currentView;
		if (!viewKey) {
			return (
				<ErrorView>
					ViewManager: {this.props.name}
					Error: There is no current View.
				</ErrorView>
			);
		}
		var view = this.state.views[viewKey];
		if (!view || !view.props.component) {
			return (
				<ErrorView>
					ViewManager: "{this.props.name}"
					The View "{viewKey}" is invalid.
				</ErrorView>
			);
		}
		var options = this.state.options || {};
		var viewClassName = classNames('View View--' + viewKey, view.props.className);
		var ViewComponent = view.props.component;
		var viewProps = blacklist(view.props, 'component', 'className');
		Object.assign(viewProps, options.viewProps);
		var viewElement = <ViewComponent {...viewProps} />;

		if (this.__lastRenderedView !== viewKey) {
			// console.log('initialising view ' + viewKey + ' with options', options);
			if (viewElement.type.navigationBar && viewElement.type.getNavigation) {
				var app = this.context.app;
				var transition = options.transition;
				if (app.viewManagerInTransition) {
					transition = app.viewManagerInTransition.activeTransitionOptions.transition;
				}
				setTimeout(() => {
					app.navigationBars[viewElement.type.navigationBar].updateWithTransition(viewElement.type.getNavigation(viewProps, app), transition);
				}, 0);
			}
			this.__lastRenderedView = viewKey;
		}

		return (
			<ViewContainer className={viewClassName} key={viewKey}>
				{viewElement}
			</ViewContainer>
		);
	},
	render () {
		var className = classNames('ViewManager', this.props.className);
		var viewContainer = this.renderViewContainer(this.state.currentView, { viewProps: this.state.currentViewProps });

		var transitionName = 'view-transition-instant';
		var transitionDurationEnter = 10;
		var transitionDurationLeave = 60;
		if (this.state.options.transition) {
			// console.log('applying view transition: ' + this.state.options.transition + ' to view ' + this.state.currentView);
			transitionName = 'view-transition-' + this.state.options.transition;
			if (this.state.options.transition === 'fade') {
				var transitionDurationEnter = 10;
				var transitionDurationLeave = 340;
			} else {
				var transitionDurationEnter = 500;
				var transitionDurationLeave = 500;
			}
		}
		return (
			<ReactCSSTransitionGroup transitionName={transitionName} transitionEnterTimeout={transitionDurationEnter} transitionLeaveTimeout={transitionDurationLeave} className={className} component="div">
				{viewContainer}
			</ReactCSSTransitionGroup>
		);
	}
});

export default ViewManager;
<commit_msg>Fix for https://github.com/touchstonejs/touchstonejs-starter/issues/51<commit_after>import blacklist from 'blacklist';
import classNames from 'classnames';
import ErrorView from './ErrorView';
import React from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';

function createViewsFromChildren (children) {
	var views = {};
	React.Children.forEach(children, function (view) {
		views[view.props.name] = view;
	});
	return views;
}

var ViewContainer = React.createClass({
	statics: {
		shouldFillVerticalSpace: true
	},
	propTypes: {
		children: React.PropTypes.node
	},
	render () {
		var props = blacklist(this.props, 'children');
		return
{this.props.children}
;
	}
});

var ViewManager = React.createClass({
	statics: {
		shouldFillVerticalSpace: true
	},
	contextTypes: {
		app: React.PropTypes.object.isRequired
	},
	propTypes: {
		name: React.PropTypes.string,
		children: React.PropTypes.node,
		className: React.PropTypes.string,
		defaultView: React.PropTypes.string,
		onViewChange: React.PropTypes.func
	},
	getDefaultProps () {
		return {
			name: '__default'
		};
	},
	getInitialState () {
		return {
			views: createViewsFromChildren(this.props.children),
			currentView: this.props.defaultView,
			options: {}
		};
	},
	componentDidMount () {
		this.context.app.viewManagers[this.props.name] = this;
	},
	componentWillUnmount () {
		delete this.context.app.viewManagers[this.props.name];
	},
	componentWillReceiveProps (nextProps) {
		this.setState({
			views: createViewsFromChildren(this.props.children)
		});
		if (nextProps.name !== this.props.name) {
			this.context.app.viewManagers[nextProps.name] = this;
			delete this.context.app.viewManagers[this.props.name];
		}
		if (nextProps.currentView && nextProps.currentView !== this.state.currentView) {
			this.transitionTo(nextProps.currentView, { viewProps: nextProps.viewProps });
		}
	},
	transitionTo (viewKey, options) {
		if (typeof options === 'string') {
			options = { transition: options };
		}
		if (!options) options = {};
		this.activeTransitionOptions = options;
		this.context.app.viewManagerInTransition = this;
		this.props.onViewChange && this.props.onViewChange(viewKey);
		this.setState({
			currentView: viewKey,
			options: options
		}, () => {
			delete this.activeTransitionOptions;
			delete this.context.app.viewManagerInTransition;
		});
	},
	renderViewContainer () {
		var viewKey = this.state.currentView;
		if (!viewKey) {
			return (
				<ErrorView>
					ViewManager: {this.props.name}
					Error: There is no current View.
				</ErrorView>
			);
		}
		var view = this.state.views[viewKey];
		if (!view || !view.props.component) {
			return (
				<ErrorView>
					ViewManager: "{this.props.name}"
					The View "{viewKey}" is invalid.
				</ErrorView>
			);
		}
		var options = this.state.options || {};
		var viewClassName = classNames('View View--' + viewKey, view.props.className);
		var ViewComponent = view.props.component;
		var viewProps = blacklist(view.props, 'component', 'className');
		Object.assign(viewProps, options.viewProps);
		var viewElement = <ViewComponent {...viewProps} />;

		if (this.__lastRenderedView !== viewKey) {
			// console.log('initialising view ' + viewKey + ' with options', options);
			if (viewElement.type.navigationBar && viewElement.type.getNavigation) {
				var app = this.context.app;
				var transition = options.transition;
				if (app.viewManagerInTransition) {
					transition = app.viewManagerInTransition.activeTransitionOptions.transition;
				}
				setTimeout(() => {
					app.navigationBars[viewElement.type.navigationBar].updateWithTransition(viewElement.type.getNavigation(viewProps, app), transition);
				}, 0);
			}
			this.__lastRenderedView = viewKey;
		}

		return (
			<ViewContainer className={viewClassName} key={viewKey}>
				{viewElement}
			</ViewContainer>
		);
	},
	render () {
		var className = classNames('ViewManager', this.props.className);
		var viewContainer = this.renderViewContainer(this.state.currentView, { viewProps: this.state.currentViewProps });

		var transitionName = 'view-transition-instant';
		var transitionDurationEnter = 10;
		var transitionDurationLeave = 60;
		if (this.state.options.transition) {
			// console.log('applying view transition: ' + this.state.options.transition + ' to view ' + this.state.currentView);
			transitionName = 'view-transition-' + this.state.options.transition;
			if (this.state.options.transition === 'fade') {
				var transitionDurationEnter = 10;
				var transitionDurationLeave = 340;
			} else {
				var transitionDurationEnter = this.state.options.transition === 'instant'? 50: 500;
				var transitionDurationLeave = this.state.options.transition === 'instant'? 50: 500;
			}
		}
		return (
			<ReactCSSTransitionGroup transitionName={transitionName} transitionEnterTimeout={transitionDurationEnter} transitionLeaveTimeout={transitionDurationLeave} className={className} component="div">
				{viewContainer}
			</ReactCSSTransitionGroup>
		);
	}
});

export default ViewManager;
<|endoftext|>


<commit_before>const path = require('path');
const webpack = require("webpack");

module.exports = {
  output: {
    path: path.resolve(__dirname),
    filename: "../dist/bundle.js"
  },
  resolve: {
    extensions: [".js", ".vue", ".json"],
    alias: {
      "vue$": "vue/dist/vue.common.js",
      // // allows you to specify a relative require to source without absolute directory
      // "src": sourceDir,
      "Vue": "vue",
      "should": "should"
    }

  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.ProvidePlugin({
      // alias these global variables to their appropriate modules in node
      // so that they don"t have to be explicitly required in your code
      Vue: "Vue",
      $: "jquery"
    })
  ]

};
<commit_msg>Add css-loader for btrzDateInput<commit_after>const path = require('path');
const webpack = require("webpack");

module.exports = {
  output: {
    path: path.resolve(__dirname),
    filename: "../dist/bundle.js"
  },
  resolve: {
    extensions: [".js", ".vue", ".json"],
    alias: {
      "vue$": "vue/dist/vue.common.js",
      // // allows you to specify a relative require to source without absolute directory
      // "src": sourceDir,
      "Vue": "vue",
      "should": "should"
    }

  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.css$/,
        use: ["css-loader"]
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.ProvidePlugin({
      // alias these global variables to their appropriate modules in node
      // so that they don"t have to be explicitly required in your code
      Vue: "Vue",
      $: "jquery"
    })
  ]

};
<|endoftext|>


<commit_before>var should = require('should')
  , wsUtils = require('../../src/ws/ws-utils.js');

describe('wsUtils', function() {

  it('should have getUrls function', function() {
    wsUtils.should.have.property('getUrls');
  });

  describe('getUrls', function() {

    var wsOptions = {}

    beforeEach(function() {
      wsOptions = {
        alura: "http://fakealura.com.br",
        path: "/ws/fake/",
        hash: "/fAk3h45h",

        courses: {
        }
      }
    });

    it('should return url given 1 course', function() {
      wsOptions.courses.module1 = "module1";

      var urls = wsUtils.getUrls(wsOptions);
      urls.should.have.length(1);
    });

    it('should return urls given 2 courses', function() {
      wsOptions.courses.module1 = "module1";
      wsOptions.courses.module2 = "module2";

      var urls = wsUtils.getUrls(wsOptions);
      urls.should.have.length(2);
    });
  });
});<commit_msg>More tests for ws-utils getUrls<commit_after>var should = require('should')
  , wsUtils = require('../../src/ws/ws-utils.js');

describe('wsUtils', function() {

  it('should have getUrls function', function() {
    wsUtils.should.have.property('getUrls');
  });

  describe('getUrls', function() {

    var wsOptions = {}

    beforeEach(function() {
      wsOptions = {
        alura: "http://fakealura.com.br",
        path: "/ws/fake/",
        hash: "/fAk3h45h",

        courses: {
        }
      }
    });

    it('should return url given 1 course', function() {
      wsOptions.courses.module1 = "module1";

      var urls = wsUtils.getUrls(wsOptions);
      urls.should.have.length(1);
    });

    it('should return urls given 2 courses', function() {
      wsOptions.courses.module1 = "module1";
      wsOptions.courses.module2 = "module2";

      var urls = wsUtils.getUrls(wsOptions);
      urls.should.have.length(2);
    });

    it('should throw error if no course was found', function() {
      wsUtils.getUrls.bind(null, wsOptions).should.throw('No course was found.');
    });
  });
});<|endoftext|>


<commit_before>import {Scrollable, ScrollableContainer} from 'widget/scrollable'
import {connect} from 'store'
import {filter} from 'rxjs/operators'
import {fromEvent} from 'rxjs'
import {isMobile} from 'widget/userAgent'
import {selectFrom} from 'stateUtils'
import Keybinding from 'widget/keybinding'
import Portal from 'widget/portal'
import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash'
import escapeStringRegexp from 'escape-string-regexp'
import lookStyles from 'style/look.module.css'
import styles from './combo.module.css'

const mapStateToProps = state => ({
    dimensions: selectFrom(state, 'dimensions') || []
})

class Combo extends React.Component {
    subscriptions = []
    input = React.createRef()
    list = React.createRef()
    highlighted = React.createRef()
    state = {
        dimensions: {},
        showOptions: false,
        filter: '',
        filteredOptions: [],
        flattenedOptions: [],
        highlightedOption: null,
        selectedOption: null,
        mouseOver: null
    }

    render() {
        const {showOptions} = this.state
        return (

 showOptions ? this.hideOptions() : this.showOptions()}>
                    {this.renderInput()}

                {showOptions ? this.renderOptions() : null}

        )
    }

    renderInput() {
        const {placeholder, autoFocus, disabled, busy} = this.props
        const {filter, selectedOption} = this.state
        const keymap = {
            ArrowUp: () => this.showOptions(),
            ArrowDown: () => this.showOptions()
        }
        return (
            <Keybinding
                disabled={disabled}
                keymap={keymap}>
                <input
                    className={selectedOption ? styles.fakePlaceholder : null}
                    type='search'
                    value={filter}
                    placeholder={selectedOption ? selectedOption.label : placeholder}
                    autoFocus={autoFocus}
                    disabled={disabled || busy || isMobile()}
                    onChange={e => this.setFilter(e.target.value)}/>
            </Keybinding>
        )
    }

    renderOptions() {
        const {placement = 'below'} = this.props
        const {dimensions: {height, top, bottom, left, right}, filteredOptions} = this.state
        const keymap = {
            Enter: () => this.selectHighlighted(),
            Escape: () => this.setFilter(),
            ArrowUp: () => this.highlightPrevious(),
            ArrowDown: () => this.highlightNext(),
            Home: () => this.highlightFirst(),
            End: () => this.highlightLast()
        }
        const style = {
            '--left': left,
            '--width': right - left,
            '--above-height': top,
            '--above-bottom': height - top,
            '--below-height': height - bottom,
            '--below-top': bottom
        }
        return (
            <Portal>
                <Keybinding keymap={keymap}>

                        <ScrollableContainer>
                            <Scrollable className={styles.items}>

                                    {this.renderItems(filteredOptions)}

                            </Scrollable>
                        </ScrollableContainer>

                </Keybinding>
            </Portal>
        )
    }

    renderItems(options) {
        return options.length
            ? options.map((item, index) => this.renderItem(item, index))
            : this.renderItem({label: 'No results'}) // [TODO] msg
    }

    renderItem(item, index) {
        return item.value
            ? this.renderSelectableItem(item)
            : item.options
                ? this.renderGroup(item, index)
                : this.renderNonSelectableItem(item, index)
    }

    renderGroup(item, index) {
        return (
            <React.Fragment key={index}>

                    {item.label}

                {this.renderItems(item.options)}
            </React.Fragment>
        )
    }

    renderNonSelectableItem(item, index) {
        return (

                {item.label}

        )
    }

    isHighlighted(option) {
        const {highlightedOption} = this.state
        return highlightedOption && option && highlightedOption.value === option.value
    }

    renderSelectableItem(item) {
        const {mouseOver} = this.state
        const highlighted = this.isHighlighted(item)
        const ref = highlighted
            ? this.highlighted
            : null
        return (

 this.highlightOption(item)}
                onClick={() => this.selectOption(item)}>
                {item.label}

        )
    }

    showOptions() {
        const {showOptions, flattenedOptions, selectedOption} = this.state
        if (!showOptions) {
            const highlightedOption = selectedOption || flattenedOptions[0]
            this.setState({
                showOptions: true,
                highlightedOption
            }, this.scroll)
        }
    }

    hideOptions() {
        this.setState({
            showOptions: false
        })
    }

    selectHighlighted() {
        const {highlightedOption} = this.state
        if (highlightedOption) {
            this.selectOption(highlightedOption)
        }
    }

    highlightPrevious() {
        const previousOption = (options, option) => {
            const index = _.indexOf(options, option)
            return index === -1
                ? null
                : options[Math.max(0, index - 1)]
        }
        this.setState(prevState => ({
            highlightedOption: previousOption(prevState.flattenedOptions, prevState.highlightedOption),
            mouseOver: false
        }), this.scroll)
    }

    highlightNext() {
        const nextOption = (options, option) => {
            const index = _.indexOf(options, option)
            return index === -1
                ? null
                : options[Math.min(options.length - 1, index + 1)]
        }
        this.setState(prevState => ({
            highlightedOption: nextOption(prevState.flattenedOptions, prevState.highlightedOption),
            mouseOver: false
        }), this.scroll)
    }

    highlightFirst() {
        const firstOption = options => options[0]
        this.setState(prevState => ({
            highlightedOption: firstOption(prevState.flattenedOptions),
            mouseOver: false
        }), this.scroll)
    }

    highlightLast() {
        const lastOption = options => options[options.length - 1]
        this.setState(prevState => ({
            highlightedOption: lastOption(prevState.flattenedOptions),
            mouseOver: false
        }), this.scroll)
    }

    scroll() {
        this.highlighted.current && this.highlighted.current.scrollIntoView({
            behavior: 'auto',
            block: 'nearest'
        })
    }

    setFilter(filter = '') {
        this.setState({
            showOptions: !!filter,
            filter,
            highlightedOption: null
        }, this.updateOptions)
    }

    selectOption(item) {
        const {input, onChange} = this.props
        this.setSelectedOption(item)
        this.setFilter()
        input.set(item.value)
        onChange && onChange({
            value: item.value
        })
    }

    highlightOption(highlightedOption) {
        this.setState({
            highlightedOption,
            mouseOver: true
        })
    }

    setSelectedOption(selectedOption) {
        this.updateState({selectedOption})
    }

    updateState(state, callback) {
        const updatedState = (prevState, state) =>
            _.isEqual(_.pick(prevState, _.keys(state)), state) ? null : state
        this.setState(
            prevState =>
                updatedState(prevState, _.isFunction(state) ? state(prevState) : state),
            callback
        )
    }

    componentDidMount() {
        const {onBlur} = this.props
        const click$ = fromEvent(document, 'click').pipe(
            filter(() => this.state.showOptions)
        )
        const isInputClick = e =>
            this.input.current && this.input.current.contains(e.target)
        const isListClick = e =>
            this.list.current && this.list.current.contains(e.target)

        this.subscriptions.push(
            click$.subscribe(
                e => {
                    if (!isInputClick(e) && !isListClick(e)) {
                        this.setFilter()
                        onBlur && onBlur(e)
                    }
                }
            )
        )
        this.setFilter()
        this.updateDimensions()
    }

    componentDidUpdate() {
        this.updateOptions()
        this.updateDimensions()
    }

    componentWillUnmount() {
        this.subscriptions.forEach(subscription => subscription.unsubscribe())
    }

    matcher(filter) {
        // match beginning of multiple words in order (e.g. "u k" matches "United Kingdom")
        return RegExp(filter.split(/\s/).map(part => '\\b' + escapeStringRegexp(part)).join('.*'), 'i')
    }

    updateOptions() {
        const {input, options} = this.props
        const {filter} = this.state
        const matcher = this.matcher(filter)
        const getFilteredOptions = options =>
            _.compact(
                options.map(option =>
                    option.options
                        ? {...option, options: getFilteredOptions(option.options)}
                        : matcher.test(option.label)
                            ? option
                            : null
                )
            )
        const getFlattenedOptions = options =>
            _.flatten(
                options.map(option =>
                    option.options
                        ? option.options
                        : option
                )
            )
        const filteredOptions = getFilteredOptions(options)
        const flattenedOptions = getFlattenedOptions(filteredOptions)

        const getFirstOption = () =>
            flattenedOptions && flattenedOptions.find(option => option.value)

        const getSelectedOption = () =>
            input && flattenedOptions && flattenedOptions.find(option => option.value === input.value)

        this.updateState(prevState => ({
            filteredOptions,
            flattenedOptions,
            highlightedOption: prevState.highlightedOption || getFirstOption(),
            selectedOption: prevState.selectedOption || getSelectedOption()
        }))
    }

    updateDimensions() {
        const {dimensions: {height}} = this.props
        const {top, bottom, left, right} = this.getBoundingBox()
        this.updateState({dimensions: {height, top, bottom, left, right}})
    }

    getBoundingBox() {
        const ref = this.input.current
        return ref
            ? ref.getBoundingClientRect()
            : {}
    }
}

export default connect(mapStateToProps)(Combo)

Combo.propTypes = {
    input: PropTypes.any.isRequired,
    options: PropTypes.any.isRequired,
    autoFocus: PropTypes.any,
    busy: PropTypes.any,
    disabled: PropTypes.any,
    keepOpen: PropTypes.any,
    placeholder: PropTypes.string,
    placement: PropTypes.oneOf(['above', 'below']),
    onBlur:  PropTypes.func,
    onChange:  PropTypes.func
}
<commit_msg>Fixed Combo keepOpen.<commit_after>import {Scrollable, ScrollableContainer} from 'widget/scrollable'
import {connect} from 'store'
import {filter} from 'rxjs/operators'
import {fromEvent} from 'rxjs'
import {isMobile} from 'widget/userAgent'
import {selectFrom} from 'stateUtils'
import Keybinding from 'widget/keybinding'
import Portal from 'widget/portal'
import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash'
import escapeStringRegexp from 'escape-string-regexp'
import lookStyles from 'style/look.module.css'
import styles from './combo.module.css'

const mapStateToProps = state => ({
    dimensions: selectFrom(state, 'dimensions') || []
})

class Combo extends React.Component {
    subscriptions = []
    input = React.createRef()
    list = React.createRef()
    highlighted = React.createRef()
    state = {
        dimensions: {},
        showOptions: false,
        filter: '',
        filteredOptions: [],
        flattenedOptions: [],
        highlightedOption: null,
        selectedOption: null,
        mouseOver: null
    }

    render() {
        const {showOptions, keepOpen} = this.state
        const onClick = () =>
            keepOpen
                ? null
                : showOptions
                    ? this.hideOptions()
                    : this.showOptions()
        return (

                    {this.renderInput()}

                {showOptions ? this.renderOptions() : null}

        )
    }

    renderInput() {
        const {placeholder, autoFocus, disabled, busy} = this.props
        const {filter, selectedOption} = this.state
        const keymap = {
            ArrowUp: () => this.showOptions(),
            ArrowDown: () => this.showOptions()
        }
        return (
            <Keybinding
                disabled={disabled}
                keymap={keymap}>
                <input
                    className={selectedOption ? styles.fakePlaceholder : null}
                    type='search'
                    value={filter}
                    placeholder={selectedOption ? selectedOption.label : placeholder}
                    autoFocus={autoFocus}
                    disabled={disabled || busy || isMobile()}
                    onChange={e => this.setFilter(e.target.value)}/>
            </Keybinding>
        )
    }

    renderOptions() {
        const {placement = 'below'} = this.props
        const {dimensions: {height, top, bottom, left, right}, filteredOptions} = this.state
        const keymap = {
            Enter: () => this.selectHighlighted(),
            Escape: () => this.setFilter(),
            ArrowUp: () => this.highlightPrevious(),
            ArrowDown: () => this.highlightNext(),
            Home: () => this.highlightFirst(),
            End: () => this.highlightLast()
        }
        const style = {
            '--left': left,
            '--width': right - left,
            '--above-height': top,
            '--above-bottom': height - top,
            '--below-height': height - bottom,
            '--below-top': bottom
        }
        return (
            <Portal>
                <Keybinding keymap={keymap}>

                        <ScrollableContainer>
                            <Scrollable className={styles.items}>

                                    {this.renderItems(filteredOptions)}

                            </Scrollable>
                        </ScrollableContainer>

                </Keybinding>
            </Portal>
        )
    }

    renderItems(options) {
        return options.length
            ? options.map((item, index) => this.renderItem(item, index))
            : this.renderItem({label: 'No results'}) // [TODO] msg
    }

    renderItem(item, index) {
        return item.value
            ? this.renderSelectableItem(item)
            : item.options
                ? this.renderGroup(item, index)
                : this.renderNonSelectableItem(item, index)
    }

    renderGroup(item, index) {
        return (
            <React.Fragment key={index}>

                    {item.label}

                {this.renderItems(item.options)}
            </React.Fragment>
        )
    }

    renderNonSelectableItem(item, index) {
        return (

                {item.label}

        )
    }

    isHighlighted(option) {
        const {highlightedOption} = this.state
        return highlightedOption && option && highlightedOption.value === option.value
    }

    renderSelectableItem(item) {
        const {mouseOver} = this.state
        const highlighted = this.isHighlighted(item)
        const ref = highlighted
            ? this.highlighted
            : null
        return (

 this.highlightOption(item)}
                onClick={() => this.selectOption(item)}>
                {item.label}

        )
    }

    showOptions() {
        const {showOptions, flattenedOptions, selectedOption} = this.state
        if (!showOptions) {
            const highlightedOption = selectedOption || flattenedOptions[0]
            this.setState({
                showOptions: true,
                highlightedOption
            }, this.scroll)
        }
    }

    hideOptions() {
        this.setState({
            showOptions: false
        })
    }

    selectHighlighted() {
        const {highlightedOption} = this.state
        if (highlightedOption) {
            this.selectOption(highlightedOption)
        }
    }

    highlightPrevious() {
        const previousOption = (options, option) => {
            const index = _.indexOf(options, option)
            return index === -1
                ? null
                : options[Math.max(0, index - 1)]
        }
        this.setState(prevState => ({
            highlightedOption: previousOption(prevState.flattenedOptions, prevState.highlightedOption),
            mouseOver: false
        }), this.scroll)
    }

    highlightNext() {
        const nextOption = (options, option) => {
            const index = _.indexOf(options, option)
            return index === -1
                ? null
                : options[Math.min(options.length - 1, index + 1)]
        }
        this.setState(prevState => ({
            highlightedOption: nextOption(prevState.flattenedOptions, prevState.highlightedOption),
            mouseOver: false
        }), this.scroll)
    }

    highlightFirst() {
        const firstOption = options => options[0]
        this.setState(prevState => ({
            highlightedOption: firstOption(prevState.flattenedOptions),
            mouseOver: false
        }), this.scroll)
    }

    highlightLast() {
        const lastOption = options => options[options.length - 1]
        this.setState(prevState => ({
            highlightedOption: lastOption(prevState.flattenedOptions),
            mouseOver: false
        }), this.scroll)
    }

    scroll() {
        this.highlighted.current && this.highlighted.current.scrollIntoView({
            behavior: 'auto',
            block: 'nearest'
        })
    }

    setFilter(filter = '') {
        const {keepOpen} = this.props
        this.setState({
            showOptions: !!filter || keepOpen,
            filter,
            highlightedOption: null
        }, this.updateOptions)
    }

    selectOption(item) {
        const {input, onChange} = this.props
        this.setSelectedOption(item)
        this.setFilter()
        input.set(item.value)
        onChange && onChange({
            value: item.value
        })
    }

    highlightOption(highlightedOption) {
        this.setState({
            highlightedOption,
            mouseOver: true
        })
    }

    setSelectedOption(selectedOption) {
        this.updateState({selectedOption})
    }

    updateState(state, callback) {
        const updatedState = (prevState, state) =>
            _.isEqual(_.pick(prevState, _.keys(state)), state) ? null : state
        this.setState(
            prevState =>
                updatedState(prevState, _.isFunction(state) ? state(prevState) : state),
            callback
        )
    }

    handleBlurEvents() {
        const {onBlur, keepOpen} = this.props
        const click$ = fromEvent(document, 'click')
        const isInputClick = e =>
            this.input.current && this.input.current.contains(e.target)
        const isListClick = e =>
            this.list.current && this.list.current.contains(e.target)

        this.subscriptions.push(
            click$.subscribe(
                e => {
                    if ((keepOpen || !isInputClick(e)) && !isListClick(e)) {
                        this.setFilter()
                        onBlur && onBlur(e)
                    }
                }
            )
        )
    }

    componentDidMount() {
        this.setFilter()
        this.updateDimensions()
        this.handleBlurEvents()
    }

    componentDidUpdate() {
        this.updateOptions()
        this.updateDimensions()
    }

    componentWillUnmount() {
        this.subscriptions.forEach(subscription => subscription.unsubscribe())
    }

    matcher(filter) {
        // match beginning of multiple words in order (e.g. "u k" matches "United Kingdom")
        return RegExp(filter.split(/\s/).map(part => '\\b' + escapeStringRegexp(part)).join('.*'), 'i')
    }

    updateOptions() {
        const {input, options} = this.props
        const {filter} = this.state
        const matcher = this.matcher(filter)
        const getFilteredOptions = options =>
            _.compact(
                options.map(option =>
                    option.options
                        ? {...option, options: getFilteredOptions(option.options)}
                        : matcher.test(option.label)
                            ? option
                            : null
                )
            )
        const getFlattenedOptions = options =>
            _.flatten(
                options.map(option =>
                    option.options
                        ? option.options
                        : option
                )
            )
        const filteredOptions = getFilteredOptions(options)
        const flattenedOptions = getFlattenedOptions(filteredOptions)

        const getFirstOption = () =>
            flattenedOptions && flattenedOptions.find(option => option.value)

        const getSelectedOption = () =>
            input && flattenedOptions && flattenedOptions.find(option => option.value === input.value)

        this.updateState(prevState => ({
            filteredOptions,
            flattenedOptions,
            highlightedOption: prevState.highlightedOption || getFirstOption(),
            selectedOption: prevState.selectedOption || getSelectedOption()
        }))
    }

    updateDimensions() {
        const {dimensions: {height}} = this.props
        const {top, bottom, left, right} = this.getBoundingBox()
        this.updateState({dimensions: {height, top, bottom, left, right}})
    }

    getBoundingBox() {
        const ref = this.input.current
        return ref
            ? ref.getBoundingClientRect()
            : {}
    }
}

export default connect(mapStateToProps)(Combo)

Combo.propTypes = {
    input: PropTypes.any.isRequired,
    options: PropTypes.any.isRequired,
    autoFocus: PropTypes.any,
    busy: PropTypes.any,
    disabled: PropTypes.any,
    keepOpen: PropTypes.any,
    placeholder: PropTypes.string,
    placement: PropTypes.oneOf(['above', 'below']),
    onBlur:  PropTypes.func,
    onChange:  PropTypes.func
}
<|endoftext|>


<commit_before>var CatFacts = function() {};
(function(){

var interestingFacts = [
'Both humans and cats have identical regions in the brain responsible for emotion.',
'A cat\'s brain is more similar to a human brain than that of a dog.',
'Cats can rotate each ear independently from the other in 180 degrees.',
'Cats\' hearing stops at 65 khz (kilohertz); humans\' hearing stops at 20 khz.',
'Cats see about 6 times better than humans at night.',
'Cats can judge within 3 inches the precise location of a sound being made 1 yard away.',
'Cats can be right-pawed or left-pawed.',
'Cats cannot see directly under their nose.',
'Cats express their present state of mind through their tail.',
'Cats are the only animals that walk on their toes.',
'Cats were worshipped as holy in Ancient Egypt and granted great respect in every household.',
'Phoenician ships likely brought the first domesticated cats to Europe in about 900 BC.',
'Ancient Egyptians shaved their eyebrows in mourning when the family cat died.',
'In Siam, a cat rode in a chariot at the head of a parade celebrating the new king.',
'Most cats adore sardines.',
'Cats use their whiskers to measure distances and changes in air pressure.',
'Abraham Lincoln kept four cats at the White House during his presidency.',
'Cats purr at the same frequency as an idling diesel engine.',
'Nikola Tesla was inspired to study electricity after his cat static-shocked him in his youth.',
'Isaac Newton invented the cat-flap.',
'Cats use their tails for balance and have nearly 30 individual bones in them.',
'Even though Napoleon conquered many countries, he was always very afraid of cats.',
'A kitten\'s eye are always blue at first.',
'A cat can jump as high as seven times as it is tall.',
'Kittens begin dreaming at just over one week old.',
'Every cat\'s nose is unique, and no two nose-prints are the same.',
'The Pilgrims were the first to introduce cats to North America.',
'Cats purr to communicate.',
'A group of kittens is called a kindle.',
'The British Government owns thousands of cats, deployed in government building to get rid of mice.',
'Cats are more active during the evening hours.',
'Cats have a field of vision of about 185 degrees.',
'Cats have the AB blood type, which is also found in humans.',
'Cats have a homing ability that uses its biological clock, the sun\'s angle and the Earth\'s magnetic field.',
'A cat taken far from its home can return to it thanks to their remarkable homing instincts.',
'Cats successfully catch mice in about one out of three attempts.'
];

CatFacts.getFact = function() {
	return interestingFacts[Math.floor(Math.random() * interestingFacts.length)];
}

})();<commit_msg>Cat facts grammar tweaks<commit_after>var CatFacts = function() {};
(function(){

var interestingFacts = [
'Cats and humans have identical regions in the brain responsible for emotion.',
'A cat\'s brain is more similar to a human brain than that of a dog.',
'Cats can rotate each ear independently from the other in 180 degrees.',
'Cat hearing stops at 65kHz; human hearing stops at 20kHz.',
'Cats see about 6 times better than humans at night.',
'Cats can judge within 3 inches the precise location of a sound being made 1 yard away.',
'Cats can be right-pawed or left-pawed.',
'Cats cannot see directly under their nose.',
'Cats express their present state of mind through their tail.',
'Cats are the only animals that walk on their toes.',
'Cats were worshipped as holy in Ancient Egypt and granted great respect in every household.',
'Phoenician ships likely brought the first domesticated cats to Europe in about 900 BC.',
'Ancient Egyptians shaved their eyebrows in mourning when the family cat died.',
'In Siam, a cat rode in a chariot at the head of a parade celebrating the new king.',
'Most cats adore sardines.',
'Cats use their whiskers to measure distances and changes in air pressure.',
'Abraham Lincoln kept four cats at the White House during his presidency.',
'Cats purr at the same frequency as an idling diesel engine.',
'Nikola Tesla was inspired to study electricity after his cat static-shocked him in his youth.',
'Isaac Newton invented the cat-flap.',
'Cats use their tails for balance and have nearly 30 individual bones in them.',
'Even though Napoleon conquered many countries, he was always very afraid of cats.',
'A kitten\'s eye are always blue at first.',
'A cat can jump as high as seven times as it is tall.',
'Kittens begin dreaming at just over one week old.',
'Every cat\'s nose is unique, and no two nose-prints are the same.',
'The Pilgrims were the first to introduce cats to North America.',
'Cats purr to communicate.',
'A group of kittens is called a kindle.',
'The British Government owns thousands of cats, deployed in government building to get rid of mice.',
'Cats are more active during the evening hours.',
'Cats have a field of vision of about 185 degrees.',
'Cats have the AB blood type, which is also found in humans.',
'Cats have a homing ability that uses its biological clock, the sun\'s angle and the Earth\'s magnetic field.',
'A cat taken far from its home can return to it thanks to their remarkable homing instincts.',
'Cats successfully catch mice in about one out of three attempts.'
];

CatFacts.getFact = function() {
	return interestingFacts[Math.floor(Math.random() * interestingFacts.length)];
}

})();<|endoftext|>


<commit_before>/* Magic Mirror Module: MMM-TFL-Arrivals
 * Version: 1.0.0
 *
 * By Ricardo Gonzalez https://github.com/ryck/MMM-TFL-Arrivals
 * MIT Licensed.
 */
Module.register("MMM-TFL-Arrivals", {
	defaults: {
		app_id: "",
		api_key: "",
		naptanId: "", // StopPoint id
		updateInterval: 60 * 1 * 1000, // Every minute
		animationSpeed: 2000,
		fade: true,
		fadePoint: 0.25, // Start on 1/4th of the list.
		limit: 5,
		initialLoadDelay: 0, // start delay in milliseconds.
		color: true,
		debug: false
	},
	start: function() {
		Log.log("Starting module: " + this.name);
		if (this.data.classes === "MMM-TFL-Arrivals") {
			this.data.classes = "bright medium";
		}
		// Set up the local values, here we construct the request url to use
		this.apiBase = "https://api.tfl.gov.uk/StopPoint/";
		this.loaded = false;
		this.buses = {};
		this.loaded = false;
		this.scheduleUpdate(this.config.initialLoadDelay);
		this.updateTimer = null;
		this.url = encodeURI(this.apiBase + this.config.naptanId + "/arrivals" + this.getParams());
		if(this.config.debug) {
			Log.info(this.url);
		}
		this.updateBusInfo(this);
	},
	// updateBusInfo
	updateBusInfo: function(self) {
		self.sendSocketNotification("GET_BUSARRIVALS", {"url":self.url});
	},
	getStyles: function() {
		return ["MMM-TFL-Arrivals.css", "font-awesome.css"];
	},
	// Define required scripts.
	getScripts: function() {
		return ["moment.js"];
	},
	//Define header for module.
	getHeader: function() {
		return this.config.header;
	},
	// Override dom generator.
	getDom: function() {
		var wrapper = document.createElement("div");

		if (this.config.naptanId === "") {
			wrapper.innerHTML = "Please set the station naptan code: " + this.atcocode + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.app_id === "") {
			wrapper.innerHTML = "Please set the application ID: " + this.app_id + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.app_key === "") {
			wrapper.innerHTML = "Please set the application key: " + this.app_key + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (!this.loaded) {
			wrapper.innerHTML = "Loading bus arrival predictions...";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.buses.data !== null) {
			this.config.header = this.buses.data[0].stopName;
		}

		//Dump bus data
		if(this.config.debug) {
			Log.info(this.buses);
		}

		// *** Start Building Table
		var bustable = document.createElement("table");
		bustable.className = "small";

		//If we have departure info
		if(this.buses.data !== null) {

			//Figure out how long the results are
			var counter = this.buses.data.length;

			//See if there are more results than requested and limit if necessary
			if (counter > this.config.limit) {
				counter = this.config.limit;
			}

			for (var t = 0; t < counter; t++) {
				var bus = this.buses.data[t];

				var row = document.createElement("tr");
				bustable.appendChild(row);

				//Route name/Number
				var routeCell = document.createElement("td");
				routeCell.className = "route";
				switch (bus.modeName) {
					case "bus":
					routeCell.className += " bus";
					break;
					case "tube":
					routeCell.className += " tube";
					break;
				}
				routeCell.innerHTML = " " + bus.routeName + " ";
				row.appendChild(routeCell);

				//Direction Info
				var directionCell = document.createElement("td");
				directionCell.className = "dest";
				directionCell.innerHTML = bus.direction;
				row.appendChild(directionCell);

				//Time Tabled Departure
				var timeTabledCell = document.createElement("td");
				var timeToStation = "";
				timeTabledCell.className = "timeTabled";
				var minutes = moment.duration(bus.timeToStation, "seconds").minutes();
				if (minutes < 1) {
					timeToStation = "Due"
					if (this.config.color) {
						timeTabledCell.className += " due";
					}
				} else if (minutes < 2) {
					timeToStation = minutes + " " + "min";
					if (this.config.color) {
						timeTabledCell.className += " late";
					}
				} else {
					timeToStation = minutes + " " + "mins";
				}

				timeTabledCell.innerHTML = timeToStation
				row.appendChild(timeTabledCell);

				if (this.config.fade && this.config.fadePoint < 1) {
					if (this.config.fadePoint < 0) {
						this.config.fadePoint = 0;
					}
					var startingPoint = this.buses.data.length * this.config.fadePoint;
					var steps = this.buses.data.length - startingPoint;
					if (t >= startingPoint) {
						var currentStep = t - startingPoint;
						row.style.opacity = 1 - (1 / steps * currentStep);
					}
				}
			}
		} else {
			var row1 = document.createElement("tr");
			bustable.appendChild(row1);

			var messageCell = document.createElement("td");
			messageCell.innerHTML = " " + this.buses.message + " ";
			messageCell.className = "bright";
			row1.appendChild(messageCell);

			var row2 = document.createElement("tr");
			bustable.appendChild(row2);

			var timeCell = document.createElement("td");
			timeCell.innerHTML = " " + this.buses.timestamp + " ";
			timeCell.className = "bright";
			row2.appendChild(timeCell);
		}

		wrapper.appendChild(bustable);
		// *** End building results table

		return wrapper;

	},
	/* processBuses(data)
	* Uses the received data to set the various values into a new array.
	*/
	processBuses: function(data) {
		//Check we have data back from API
		if (typeof data !== "undefined" && data !== null && data.length !== 0) {
			if(this.config.debug) {
				Log.info(data);
			}
			//Define object to hold bus data
			this.buses = {};
			//Define array of departure info
	    	this.buses.data = [];
			//Define timestamp of current data
			this.buses.timestamp = moment().format("LLL");
			//Define message holder
			this.buses.message = null;

			//Figure out how long the results are
			var counter = data.length;

			for (var i = 0; i < counter; i++) {
				var bus = data[i];

				if(this.config.debug) {
					Log.info(bus.stationName + ", " + bus.lineName + ", " + bus.towards + ", " + bus.expectedArrival + ", " + bus.timeToStation + ", " + bus.modeName);
				}
				this.buses.data.push({
					stopName: bus.stationName,
					routeName: bus.lineName,
					direction: bus.destinationName,
					expectedDeparture: bus.expectedArrival,
					timeToStation: bus.timeToStation,
					modeName: bus.modeName
				});
			}

			this.buses.data.sort(function(a,b){
			    return a.timeToStation - b.timeToStation;
			    }
			);
		} else {
			//No data returned - set error message
			this.buses.message = "No data returned";
			this.buses.data = null;
			this.buses.timestamp = moment().format("LLL");
			if(this.config.debug) {
				Log.error("No data returned");
				Log.error(this.buses);
			}
		}

	    this.loaded = true;

		this.updateDom(this.config.animationSpeed);
	},
	getParams: function() {
		var params = "?";
		params += "app_id=" + this.config.app_id;
		params += "&api_key=" + this.config.api_key;
		if(this.config.debug) {
			Log.info(params);
		}
		return params;
	},
	/* scheduleUpdate()
	 * Schedule next update.
	 * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
	 */
	scheduleUpdate: function(delay) {
		var nextLoad = this.config.updateInterval;
		if (typeof delay !== "undefined" && delay >= 0) {
			nextLoad = delay;
		}

		var self = this;
		clearTimeout(this.updateTimer);
		this.updateTimer = setTimeout(function() {
			self.updateBusInfo(self);
		}, nextLoad);
	},
	// Process data returned
	socketNotificationReceived: function(notification, payload) {
	  if (notification === "BUS_DATA" && payload.url === this.url) {
			this.processBuses(payload.data);
			this.scheduleUpdate(this.config.updateInterval);
	  }
	}
});
<commit_msg>Fix typo (app_key)<commit_after>/* Magic Mirror Module: MMM-TFL-Arrivals
 * Version: 1.0.0
 *
 * By Ricardo Gonzalez https://github.com/ryck/MMM-TFL-Arrivals
 * MIT Licensed.
 */
Module.register("MMM-TFL-Arrivals", {
	defaults: {
		app_id: "",
		app_key: "",
		naptanId: "", // StopPoint id
		updateInterval: 60 * 1 * 1000, // Every minute
		animationSpeed: 2000,
		fade: true,
		fadePoint: 0.25, // Start on 1/4th of the list.
		limit: 5,
		initialLoadDelay: 0, // start delay in milliseconds.
		color: true,
		debug: false
	},
	start: function() {
		Log.log("Starting module: " + this.name);
		if (this.data.classes === "MMM-TFL-Arrivals") {
			this.data.classes = "bright medium";
		}
		// Set up the local values, here we construct the request url to use
		this.apiBase = "https://api.tfl.gov.uk/StopPoint/";
		this.loaded = false;
		this.buses = {};
		this.loaded = false;
		this.scheduleUpdate(this.config.initialLoadDelay);
		this.updateTimer = null;
		this.url = encodeURI(this.apiBase + this.config.naptanId + "/arrivals" + this.getParams());
		if(this.config.debug) {
			Log.info(this.url);
		}
		this.updateBusInfo(this);
	},
	// updateBusInfo
	updateBusInfo: function(self) {
		self.sendSocketNotification("GET_BUSARRIVALS", {"url":self.url});
	},
	getStyles: function() {
		return ["MMM-TFL-Arrivals.css", "font-awesome.css"];
	},
	// Define required scripts.
	getScripts: function() {
		return ["moment.js"];
	},
	//Define header for module.
	getHeader: function() {
		return this.config.header;
	},
	// Override dom generator.
	getDom: function() {
		var wrapper = document.createElement("div");

		if (this.config.naptanId === "") {
			wrapper.innerHTML = "Please set the station naptan code: " + this.atcocode + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.app_id === "") {
			wrapper.innerHTML = "Please set the application ID: " + this.app_id + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.app_key === "") {
			wrapper.innerHTML = "Please set the application key: " + this.app_key + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (!this.loaded) {
			wrapper.innerHTML = "Loading bus arrival predictions...";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.buses.data !== null) {
			this.config.header = this.buses.data[0].stopName;
		}

		//Dump bus data
		if(this.config.debug) {
			Log.info(this.buses);
		}

		// *** Start Building Table
		var bustable = document.createElement("table");
		bustable.className = "small";

		//If we have departure info
		if(this.buses.data !== null) {

			//Figure out how long the results are
			var counter = this.buses.data.length;

			//See if there are more results than requested and limit if necessary
			if (counter > this.config.limit) {
				counter = this.config.limit;
			}

			for (var t = 0; t < counter; t++) {
				var bus = this.buses.data[t];

				var row = document.createElement("tr");
				bustable.appendChild(row);

				//Route name/Number
				var routeCell = document.createElement("td");
				routeCell.className = "route";
				switch (bus.modeName) {
					case "bus":
					routeCell.className += " bus";
					break;
					case "tube":
					routeCell.className += " tube";
					break;
				}
				routeCell.innerHTML = " " + bus.routeName + " ";
				row.appendChild(routeCell);

				//Direction Info
				var directionCell = document.createElement("td");
				directionCell.className = "dest";
				directionCell.innerHTML = bus.direction;
				row.appendChild(directionCell);

				//Time Tabled Departure
				var timeTabledCell = document.createElement("td");
				var timeToStation = "";
				timeTabledCell.className = "timeTabled";
				var minutes = moment.duration(bus.timeToStation, "seconds").minutes();
				if (minutes < 1) {
					timeToStation = "Due"
					if (this.config.color) {
						timeTabledCell.className += " due";
					}
				} else if (minutes < 2) {
					timeToStation = minutes + " " + "min";
					if (this.config.color) {
						timeTabledCell.className += " late";
					}
				} else {
					timeToStation = minutes + " " + "mins";
				}

				timeTabledCell.innerHTML = timeToStation
				row.appendChild(timeTabledCell);

				if (this.config.fade && this.config.fadePoint < 1) {
					if (this.config.fadePoint < 0) {
						this.config.fadePoint = 0;
					}
					var startingPoint = this.buses.data.length * this.config.fadePoint;
					var steps = this.buses.data.length - startingPoint;
					if (t >= startingPoint) {
						var currentStep = t - startingPoint;
						row.style.opacity = 1 - (1 / steps * currentStep);
					}
				}
			}
		} else {
			var row1 = document.createElement("tr");
			bustable.appendChild(row1);

			var messageCell = document.createElement("td");
			messageCell.innerHTML = " " + this.buses.message + " ";
			messageCell.className = "bright";
			row1.appendChild(messageCell);

			var row2 = document.createElement("tr");
			bustable.appendChild(row2);

			var timeCell = document.createElement("td");
			timeCell.innerHTML = " " + this.buses.timestamp + " ";
			timeCell.className = "bright";
			row2.appendChild(timeCell);
		}

		wrapper.appendChild(bustable);
		// *** End building results table

		return wrapper;

	},
	/* processBuses(data)
	* Uses the received data to set the various values into a new array.
	*/
	processBuses: function(data) {
		//Check we have data back from API
		if (typeof data !== "undefined" && data !== null && data.length !== 0) {
			if(this.config.debug) {
				Log.info(data);
			}
			//Define object to hold bus data
			this.buses = {};
			//Define array of departure info
	    	this.buses.data = [];
			//Define timestamp of current data
			this.buses.timestamp = moment().format("LLL");
			//Define message holder
			this.buses.message = null;

			//Figure out how long the results are
			var counter = data.length;

			for (var i = 0; i < counter; i++) {
				var bus = data[i];

				if(this.config.debug) {
					Log.info(bus.stationName + ", " + bus.lineName + ", " + bus.towards + ", " + bus.expectedArrival + ", " + bus.timeToStation + ", " + bus.modeName);
				}
				this.buses.data.push({
					stopName: bus.stationName,
					routeName: bus.lineName,
					direction: bus.destinationName,
					expectedDeparture: bus.expectedArrival,
					timeToStation: bus.timeToStation,
					modeName: bus.modeName
				});
			}

			this.buses.data.sort(function(a,b){
			    return a.timeToStation - b.timeToStation;
			    }
			);
		} else {
			//No data returned - set error message
			this.buses.message = "No data returned";
			this.buses.data = null;
			this.buses.timestamp = moment().format("LLL");
			if(this.config.debug) {
				Log.error("No data returned");
				Log.error(this.buses);
			}
		}

	    this.loaded = true;

		this.updateDom(this.config.animationSpeed);
	},
	getParams: function() {
		var params = "?";
		params += "app_id=" + this.config.app_id;
		params += "&app_key=" + this.config.app_key;
		if(this.config.debug) {
			Log.info(params);
		}
		return params;
	},
	/* scheduleUpdate()
	 * Schedule next update.
	 * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
	 */
	scheduleUpdate: function(delay) {
		var nextLoad = this.config.updateInterval;
		if (typeof delay !== "undefined" && delay >= 0) {
			nextLoad = delay;
		}

		var self = this;
		clearTimeout(this.updateTimer);
		this.updateTimer = setTimeout(function() {
			self.updateBusInfo(self);
		}, nextLoad);
	},
	// Process data returned
	socketNotificationReceived: function(notification, payload) {
	  if (notification === "BUS_DATA" && payload.url === this.url) {
			this.processBuses(payload.data);
			this.scheduleUpdate(this.config.updateInterval);
	  }
	}
});
<|endoftext|>


<commit_before><commit_msg>DevTools: add fake callframe for live object expansion in console.<commit_after><|endoftext|>