<commit_before>import Joi from 'joi';

import {
  getAvatar,
  getMissing,
  importAvatar,
} from '../../handlers/census/avatar';

import {
  getEmployee,
  getManager,
  getTeam,
  importEmployee,
} from '../../handlers/census/employee';

import {
  getEmployees,
  getEmployeesBySurname,
  getPaginatedEmployees,
} from '../../handlers/census/employees';

import { orgHandler } from '../../handlers/census/organisation';
import { statsHandler } from '../../handlers/census/statistics';
import { searchHandler } from '../../handlers/census/search';

module.exports.register = (server, options, next) => {
  server.route([
    {
      method: 'POST',
      path: '/api/v1/census/employees/import',
      config: {
        payload: {
          output: 'stream',
          allow: 'multipart/form-data',
        },
        handler: importEmployee,
        description: 'Import',
        notes: 'Import employee spreadsheet.',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employees/search',
      config: {
        validate: {
          query: {
            q: Joi.string().required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: searchHandler,
        description: 'Search',
        notes: 'Search for employees.',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employees',
      config: {
        validate: {
          query: Joi.object({
            bran: Joi.string(),
            div: Joi.string(),
            sect: Joi.string(),
            location_name: Joi.string(),
            sort: Joi.string(),
            page: Joi.number().integer(),
            limit: Joi.number().integer(),
          }).options({ allowUnknown: false }),
        },
        plugins: {
          pagination: {
            enabled: true,
          },
        },
        handler: getPaginatedEmployees,
        description: 'Filterable',
        notes: 'Filterable list of employees (Paginated!).',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employees/{letter}',
      config: {
        validate: {
          params: {
            letter: Joi.string()
              .regex(/^[A-z]+$/)
              .max(1)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: true,
          },
        },
        handler: getEmployeesBySurname,
        description: 'Surname',
        notes: 'Get a paginated list of employees grouped by the first letter of their surname.',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}',
      config: {
        validate: {
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getEmployee,
        description: 'Employee',
        notes: 'Returns information for the requested employeeID',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}/manager',
      config: {
        validate: {
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getManager,
        description: 'Manager',
        notes: 'Returns the manager for the requested employeeID',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}/team',
      config: {
        validate: {
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getTeam,
        description: 'Team',
        notes: 'Returns the team for the requested employeeID',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}/avatar',
      config: {
        validate: {
          query: {
            size: Joi.number()
              .integer()
              .min(50)
              .max(500),
          },
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getAvatar,
        description: 'Avatar',
        notes: 'Returns an avatar for the supplied employeeID if avaliable. A default avatar is shown if the avatar is missing.',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/avatar/missing',
      config: {
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getMissing,
        description: 'Missing',
        notes: 'Returns userids of unused avatars. However, this is not a list of employees with missing avatars.',
        tags: ['api', 'Avatar'],
      },
    },
    {
      method: 'POST',
      path: '/api/v1/census/avatar',
      config: {
        payload: {
          output: 'stream',
          allow: 'multipart/form-data',
          parse: true,
        },
        handler: importAvatar,
        description: 'Import',
        notes: 'Import an avatar. Filename needs to be userid_date.jpg...e.g. AAlain_20170420.jpg',
        tags: ['api', 'Avatar'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/organisation/statistics',
      config: {
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: statsHandler,
        description: 'Statistics',
        notes: 'Returns the count of groups, divisions, directorates, branches, employees and locations',
        tags: ['api', 'Organisation'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/organisation/structure',
      config: {
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: orgHandler,
        description: 'Structure',
        notes: 'Returns a nested organisation structure.',
        tags: ['api', 'Organisation'],
      },
    },
  ]);

  next();
};

module.exports.register.attributes = {
  name: 'routes.census',
};
<commit_msg>Allow hyphens in employee user ID strings for API endpoint validation<commit_after>import Joi from 'joi';

import {
  getAvatar,
  getMissing,
  importAvatar,
} from '../../handlers/census/avatar';

import {
  getEmployee,
  getManager,
  getTeam,
  importEmployee,
} from '../../handlers/census/employee';

import {
  getEmployees,
  getEmployeesBySurname,
  getPaginatedEmployees,
} from '../../handlers/census/employees';

import { orgHandler } from '../../handlers/census/organisation';
import { statsHandler } from '../../handlers/census/statistics';
import { searchHandler } from '../../handlers/census/search';

module.exports.register = (server, options, next) => {
  server.route([
    {
      method: 'POST',
      path: '/api/v1/census/employees/import',
      config: {
        payload: {
          output: 'stream',
          allow: 'multipart/form-data',
        },
        handler: importEmployee,
        description: 'Import',
        notes: 'Import employee spreadsheet.',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employees/search',
      config: {
        validate: {
          query: {
            q: Joi.string().required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: searchHandler,
        description: 'Search',
        notes: 'Search for employees.',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employees',
      config: {
        validate: {
          query: Joi.object({
            bran: Joi.string(),
            div: Joi.string(),
            sect: Joi.string(),
            location_name: Joi.string(),
            sort: Joi.string(),
            page: Joi.number().integer(),
            limit: Joi.number().integer(),
          }).options({ allowUnknown: false }),
        },
        plugins: {
          pagination: {
            enabled: true,
          },
        },
        handler: getPaginatedEmployees,
        description: 'Filterable',
        notes: 'Filterable list of employees (Paginated!).',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employees/{letter}',
      config: {
        validate: {
          params: {
            letter: Joi.string()
              .regex(/^[A-z]+$/)
              .max(1)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: true,
          },
        },
        handler: getEmployeesBySurname,
        description: 'Surname',
        notes: 'Get a paginated list of employees grouped by the first letter of their surname.',
        tags: ['api', 'Employees'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}',
      config: {
        validate: {
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z-]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getEmployee,
        description: 'Employee',
        notes: 'Returns information for the requested employeeID',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}/manager',
      config: {
        validate: {
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z-]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getManager,
        description: 'Manager',
        notes: 'Returns the manager for the requested employeeID',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}/team',
      config: {
        validate: {
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z-]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getTeam,
        description: 'Team',
        notes: 'Returns the team for the requested employeeID',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/employee/{employeeID}/avatar',
      config: {
        validate: {
          query: {
            size: Joi.number()
              .integer()
              .min(50)
              .max(500),
          },
          params: {
            employeeID: Joi.string()
              .regex(/^[A-z-]+$/)
              .required(),
          },
        },
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getAvatar,
        description: 'Avatar',
        notes: 'Returns an avatar for the supplied employeeID if available. A default avatar is shown if the avatar is missing.',
        tags: ['api', 'Employee'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/avatar/missing',
      config: {
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: getMissing,
        description: 'Missing',
        notes: 'Returns userids of unused avatars. However, this is not a list of employees with missing avatars.',
        tags: ['api', 'Avatar'],
      },
    },
    {
      method: 'POST',
      path: '/api/v1/census/avatar',
      config: {
        payload: {
          output: 'stream',
          allow: 'multipart/form-data',
          parse: true,
        },
        handler: importAvatar,
        description: 'Import',
        notes: 'Import an avatar. Filename needs to be userid_date.jpg...e.g. AAlain_20170420.jpg',
        tags: ['api', 'Avatar'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/organisation/statistics',
      config: {
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: statsHandler,
        description: 'Statistics',
        notes: 'Returns the count of groups, divisions, directorates, branches, employees and locations',
        tags: ['api', 'Organisation'],
      },
    },
    {
      method: 'GET',
      path: '/api/v1/census/organisation/structure',
      config: {
        plugins: {
          pagination: {
            enabled: false,
          },
        },
        handler: orgHandler,
        description: 'Structure',
        notes: 'Returns a nested organisation structure.',
        tags: ['api', 'Organisation'],
      },
    },
  ]);

  next();
};

module.exports.register.attributes = {
  name: 'routes.census',
};
<|endoftext|>


<commit_before>Zotero.debug('debug-bridge load attempt');
if (!Zotero.DebugBridge) {
  Zotero.debug('Installing debug-bridge');

  (async () => {
    await Zotero.Schema.schemaUpdatePromise;

    var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

    Zotero.DebugBridge = {};

    Zotero.DebugBridge.Execute = function() {};
    Zotero.Server.Endpoints["/debug-bridge/execute"] = Zotero.DebugBridge.Execute;
    Zotero.DebugBridge.Execute.prototype = {
      supportedMethods: ["POST"],
      supportedDataTypes: '*',
      permitBookmarklet: false,

      init: async function (options) {
        const password = {
          expected: Zotero.Prefs.get('debug-bridge.password') || '',
          found: (options.query || {}).password || '',
        }

        Zotero.debug(`debug-bridge: password=${JSON.stringify(password)}`)

        if (password.expected !== password.found) {
          return [401, "text/plain", 'wrong password'];
        }

        Zotero.debug("debug-bridge: executing\n" + options.data);
        let start = new Date()
        let response;
        try {
          let action = new AsyncFunction('query', options.data);
          response = await action(options.query);
          if (typeof response === 'undefined') response = null;
          response = JSON.stringify(response);
        } catch (err) {
          Zotero.debug('debug-bridge failed (' + ((new Date()) - start) + 'ms): ' + err);
          return [500, "application/text", 'debug-bridge failed: ' + err + "\n" + err.stack];
        }
        Zotero.debug('debug-bridge succeeded (' + ((new Date()) - start) + 'ms)');
        return [201, "application/json", response];
      }
    };

    Zotero.DebugBridge.Form = function() {};
    Zotero.Server.Endpoints["/debug-bridge"] = Zotero.DebugBridge.Form;
    Zotero.DebugBridge.Form.prototype = {
      supportedMethods: ["GET"],
      supportedDataTypes: '*',
      permitBookmarklet: false,

      init: function (options) {
        return [200, "text/html", Zotero.File.getContentsFromURL('resource://zotero-debug-bridge/index.html')];
      }
    };

    Zotero.debug('debug-bridge: endpoint installed');
  })();
}
<commit_msg>debug bridge<commit_after>Zotero.debug('debug-bridge load attempt');
if (!Zotero.DebugBridge) {
  Zotero.debug('Installing debug-bridge');

  (async () => {
    await Zotero.Schema.schemaUpdatePromise;

    var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

    Zotero.DebugBridge = {};

    Zotero.DebugBridge.Execute = function() {};
    Zotero.Server.Endpoints["/debug-bridge/execute"] = Zotero.DebugBridge.Execute;
    Zotero.DebugBridge.Execute.prototype = {
      supportedMethods: ["POST"],
      supportedDataTypes: 'application/json',
      permitBookmarklet: false,

      init: async function (options) {
        const password = {
          expected: Zotero.Prefs.get('debug-bridge.password') || '',
          found: (options.query || {}).password || '',
        }

        Zotero.debug(`debug-bridge: password=${JSON.stringify(password)}`)

        if (password.expected !== password.found) {
          return [401, "text/plain", 'wrong password'];
        }

        Zotero.debug("debug-bridge: executing\n" + options.data);
        let start = new Date()
        let response;
        try {
          let action = new AsyncFunction('query', options.data);
          response = await action(options.query);
          if (typeof response === 'undefined') response = null;
          response = JSON.stringify(response);
        } catch (err) {
          Zotero.debug('debug-bridge failed (' + ((new Date()) - start) + 'ms): ' + err);
          return [500, "application/text", 'debug-bridge failed: ' + err + "\n" + err.stack];
        }
        Zotero.debug('debug-bridge succeeded (' + ((new Date()) - start) + 'ms)');
        return [201, "application/json", response];
      }
    };

    Zotero.DebugBridge.Form = function() {};
    Zotero.Server.Endpoints["/debug-bridge"] = Zotero.DebugBridge.Form;
    Zotero.DebugBridge.Form.prototype = {
      supportedMethods: ["GET"],
      supportedDataTypes: '*',
      permitBookmarklet: false,

      init: function (options) {
        return [200, "text/html", Zotero.File.getContentsFromURL('resource://zotero-debug-bridge/index.html')];
      }
    };

    Zotero.debug('debug-bridge: endpoint installed');
  })();
}
<|endoftext|>


<commit_before>import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from '../reducers'

export default () => {
  const store = createStore(
    reducer,
    applyMiddleware(thunk)
  )

  return store
}
<commit_msg>Add logger to store<commit_after>import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import reducer from '../reducers'

const logger = createLogger()
export default () => {
  const store = createStore(
    reducer,
    applyMiddleware(thunk, logger)
  )

  return store
}
<|endoftext|>


<commit_before>/** 主页 **/

/** 所需的各种插件 **/
import React from "react";
import { useStore } from "react-redux";

/** 所需的各种资源 **/
import "./index.less";
import ImgLogo from "../../assets/react-logo.jpg";

export default function HomePageContainer(props) {
  const store = useStore();
  console.log("store:", store);
  console.log("what props:", props);
  return (

React-Luo

          react17、redux4、router5、webpack5、eslint、babel7、antd4

  );
}
<commit_msg>update<commit_after>/** 主页 **/

/** 所需的各种插件 **/
import React from "react";
import { useStore } from "react-redux";

/** 所需的各种资源 **/
import "./index.less";
import ImgLogo from "../../assets/react-logo.jpg";

export default function HomePageContainer(props) {
  const store = useStore();
  console.log("store:", store);
  console.log("what props:", props);
  return (

React-Luo

          react17、redux4、router6、webpack5、eslint、babel7、antd4

  );
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import find from 'lodash/find';
import startsWith from 'lodash/startsWith';
import axios from 'axios';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
  fetchWeatherFromOpenWeather,
  setCityList,
  setNotification
} from '../actions/index';

import { AsyncTypeahead } from 'react-bootstrap-typeahead'; // ES2015
import 'react-bootstrap-typeahead/css/Typeahead.css';

import { logError } from '../helpers/loggly';

const searchForCity = (city, limit = 5) => {
  const env = process.env.NODE_ENV;
  let url_base = 'code.randomlysa.com/weather';
  if (env === 'development') url_base = 'localhost/react-weather';

  // development: localhost/react-weather
  // production code.randomlysa.com/weather
  return axios({
    method: 'get',
    url: `http://${url_base}/sqlite/sqliteSearchForName.php?city=${city}&limit=${limit}`,
    responseType: 'json'
  });
}; // searchForCity

class SearchBar extends Component {
  constructor(props) {
    super(props);
    // isLoading: needed for typeahead
    // caseSensitive: typeahead setting
    // city: is set to an object when a city is selected from the dropdown
    // options: list of cities returned from searching, used by typeahead
    // cityList: a list of cities to select from when typeahead isn't used
    this.state = {
      isLoading: false,
      caseSensitive: false,
      city: '',
      options: [],
      cityList: ''
    };

    this.onInputChange = this.onInputChange.bind(this);
    this.onFormSubmit = this.onFormSubmit.bind(this);
  }

  onFormSubmit(event) {
    // Scenarios:
    // 1. User typed in a city, selected it with keyboard/mouse/touch, and
    // pressed Submit. Everything here works as expected.

    // 2. User typed in a city, selected a result from typeahead, and pressed
    // enter. In this case, `userInput` should be in state.options. Find that
    // option and search for it.

    // 3. User typed in a city and pressed enter without selecting anything.
    // Search the database for matching cities and show a list.

    if (event) event.preventDefault();
    const userInput = this.typeahead.getInstance().getInput().value;
    let [city, area, country] = userInput.split(',').map(i => i.trim());

    // Returns the object if it's in state otherwise undefined.
    const userInputInOptions = find(this.state.options, {
      city,
      country,
      area
    });

    if (this.state.city || userInputInOptions) {
      let currentCity;
      if (this.state.city) {
        currentCity = this.state.city;
      } else {
        currentCity = userInputInOptions; // object
      }

      const id = parseInt(currentCity.id, 10);
      // Check if the id has already been searched for.
      const hasCity = find(this.props.cities, ['id', id]);
      if (hasCity) {
        return this.props.setNotification('This city is already in your list');
      }

      // Fetch weather data.
      this.props.fetchWeatherFromOpenWeather(currentCity);
      // Todo: setState city: '' still needed?
      this.setState({ city: '' });
      this.props.setNotification('');
    } else {
      // else for - if(this.state.city || userInputInOptions)
      // user typed in something and clicked search OR typed in something and
      // pressed enter. nothing selected from typeahead.
      // there is no city in state, search the database for all cities named
      // userInput.
      try {
        searchForCity(userInput, 10).then(({ data }) => {
          this.typeahead.getInstance().clear();
          this.setState({ cityList: data });
          this.props.setCityList(data);
        });
      } catch (e) {
        console.log(e);
        window._LTracker.push(e);
      }
      return;
    }

    // Todo: Maybe better to clear on focus?
    this.typeahead.getInstance().clear();
  }

  // For typeahead
  onInputChange(city) {
    this.setState({ isLoading: true });

    try {
      searchForCity(city).then(({ data }) => {
        if (data) {
          this.setState({
            isLoading: false,
            // Move the label to the component prop. Return data so id is
            // included, since it's recommended to have one.
            // See "Duplicate Data" section:
            // https://github.com/ericgio/react-bootstrap-typeahead/blob/27790f1e70a994432c55128fe15e50ba11e76fcf/docs/Data.md
            options: data
          });
        } else {
          this.setState({
            isLoading: false
          });
        }
      });
    } catch (e) {
      console.log(e);
      window._LTracker.push(e);
    } // try/catch
  } // onInputChange

  filterResults(location, props) {
    const { city, country } = location;
    const { text } = props;
    // No commma, easy filter
    if (!text.includes(',')) {
      return startsWith(city, text);
    } else {
      // The 'search by country' is already handled by PHP, so this
      // doesn't really filter. It splits the entered text on a comma,
      // then returns any results where the city starts with the city that
      // was entered, ignoring what was after the comma.
      // Example: entered text: "Bos, US"
      // searchForCity = Bos
      // ", US" is ignored, handled by PHP
      const [searchForCity, searchForCountry] = text.split(',');
      return startsWith(city, searchForCity);
    }

    // return props;
  }

  // Lets a user search by pressing enter without selecting from typeahead.
  // Previous, typeahead would do nothing if enter was pressed but no selection
  // had been made.
  handleKeyDown(e) {
    if (e.key === 'Enter') this.onFormSubmit();
  }

  // Props for AsyncTypeahead:
  // isLoading - Whether or not a request is currently pending
  //    * Necessary for the component to know when new results are available
  // onSearch - updates results when a user is typing
  // options - results to show in dropdown when user is searching
  // labelKey - sets the text that is diplayed - "city, area, country"
  // ref - used for this.typeahead.getInstance().clear()
  // onChange - when a city is selected, set that city to state.city
  // filterBy - custom filtering
  // onKeyDown - allow using enter key without selecting from typeahead
  // onInputChange - could be used to set value = state on each keypress.
  render() {
    return (

        <form
          onSubmit={this.onFormSubmit}
          data-cy="submit"
          className="input-group"
        >
          <AsyncTypeahead
            isLoading={this.state.isLoading}
            onSearch={query => this.onInputChange(query)}
            options={this.state.options}
            labelKey={row => `${row.city}, ${row.area}, ${row.country}`}
            ref={typeahead => (this.typeahead = typeahead)}
            placeholder="Check the weather in your favorite cities"
            // onChange={city => this.setState({ city: city[0] })}
            filterBy={this.filterResults.bind(this)}
            onKeyDown={e => this.handleKeyDown(e)}
            data-cy="searchbar"
          />

            <button type="submit" className="btn btn-secondary">
              Submit
            </button>

        </form>

    );
  }
}

function mapStateToProps(state) {
  return { cities: state.weather };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(
    { fetchWeatherFromOpenWeather, setCityList, setNotification },
    dispatch
  );
}

// null means we don't need state here.
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SearchBar);
<commit_msg>update error logging to Loggly<commit_after>import React, { Component } from 'react';
import find from 'lodash/find';
import startsWith from 'lodash/startsWith';
import axios from 'axios';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
  fetchWeatherFromOpenWeather,
  setCityList,
  setNotification
} from '../actions/index';

import { AsyncTypeahead } from 'react-bootstrap-typeahead'; // ES2015
import 'react-bootstrap-typeahead/css/Typeahead.css';

import { logError } from '../helpers/loggly';

const searchForCity = (city, limit = 5) => {
  const env = process.env.NODE_ENV;
  let url_base = 'code.randomlysa.com/weather';
  if (env === 'development') url_base = 'localhost/react-weather';

  // development: localhost/react-weather
  // production code.randomlysa.com/weather
  return axios({
    method: 'get',
    url: `http://${url_base}/sqlite/sqliteSearchForName.php?city=${city}&limit=${limit}`,
    responseType: 'json'
  });
}; // searchForCity

class SearchBar extends Component {
  constructor(props) {
    super(props);
    // isLoading: needed for typeahead
    // caseSensitive: typeahead setting
    // city: is set to an object when a city is selected from the dropdown
    // options: list of cities returned from searching, used by typeahead
    // cityList: a list of cities to select from when typeahead isn't used
    this.state = {
      isLoading: false,
      caseSensitive: false,
      city: '',
      options: [],
      cityList: ''
    };

    this.onInputChange = this.onInputChange.bind(this);
    this.onFormSubmit = this.onFormSubmit.bind(this);
  }

  onFormSubmit(event) {
    // Scenarios:
    // 1. User typed in a city, selected it with keyboard/mouse/touch, and
    // pressed Submit. Everything here works as expected.

    // 2. User typed in a city, selected a result from typeahead, and pressed
    // enter. In this case, `userInput` should be in state.options. Find that
    // option and search for it.

    // 3. User typed in a city and pressed enter without selecting anything.
    // Search the database for matching cities and show a list.

    if (event) event.preventDefault();
    const userInput = this.typeahead.getInstance().getInput().value;
    let [city, area, country] = userInput.split(',').map(i => i.trim());

    // Returns the object if it's in state otherwise undefined.
    const userInputInOptions = find(this.state.options, {
      city,
      country,
      area
    });

    if (this.state.city || userInputInOptions) {
      let currentCity;
      if (this.state.city) {
        currentCity = this.state.city;
      } else {
        currentCity = userInputInOptions; // object
      }

      const id = parseInt(currentCity.id, 10);
      // Check if the id has already been searched for.
      const hasCity = find(this.props.cities, ['id', id]);
      if (hasCity) {
        return this.props.setNotification('This city is already in your list');
      }

      // Fetch weather data.
      this.props.fetchWeatherFromOpenWeather(currentCity);
      // Todo: setState city: '' still needed?
      this.setState({ city: '' });
      this.props.setNotification('');
    } else {
      // else for - if(this.state.city || userInputInOptions)
      // user typed in something and clicked search OR typed in something and
      // pressed enter. nothing selected from typeahead.
      // there is no city in state, search the database for all cities named
      // userInput.
      try {
        searchForCity(userInput, 10).then(({ data }) => {
          this.typeahead.getInstance().clear();
          this.setState({ cityList: data });
          this.props.setCityList(data);
        });
      } catch (e) {
        console.log(e);
        logError(e);
      }
      return;
    }

    // Todo: Maybe better to clear on focus?
    this.typeahead.getInstance().clear();
  }

  // For typeahead
  onInputChange(city) {
    this.setState({ isLoading: true });

    try {
      searchForCity(city).then(({ data }) => {
        if (data) {
          this.setState({
            isLoading: false,
            // Move the label to the component prop. Return data so id is
            // included, since it's recommended to have one.
            // See "Duplicate Data" section:
            // https://github.com/ericgio/react-bootstrap-typeahead/blob/27790f1e70a994432c55128fe15e50ba11e76fcf/docs/Data.md
            options: data
          });
        } else {
          this.setState({
            isLoading: false
          });
        }
      });
    } catch (e) {
      console.log(e);
      logError(e);
    } // try/catch
  } // onInputChange

  filterResults(location, props) {
    const { city, country } = location;
    const { text } = props;
    // No commma, easy filter
    if (!text.includes(',')) {
      return startsWith(city, text);
    } else {
      // The 'search by country' is already handled by PHP, so this
      // doesn't really filter. It splits the entered text on a comma,
      // then returns any results where the city starts with the city that
      // was entered, ignoring what was after the comma.
      // Example: entered text: "Bos, US"
      // searchForCity = Bos
      // ", US" is ignored, handled by PHP
      const [searchForCity, searchForCountry] = text.split(',');
      return startsWith(city, searchForCity);
    }

    // return props;
  }

  // Lets a user search by pressing enter without selecting from typeahead.
  // Previous, typeahead would do nothing if enter was pressed but no selection
  // had been made.
  handleKeyDown(e) {
    if (e.key === 'Enter') this.onFormSubmit();
  }

  // Props for AsyncTypeahead:
  // isLoading - Whether or not a request is currently pending
  //    * Necessary for the component to know when new results are available
  // onSearch - updates results when a user is typing
  // options - results to show in dropdown when user is searching
  // labelKey - sets the text that is diplayed - "city, area, country"
  // ref - used for this.typeahead.getInstance().clear()
  // onChange - when a city is selected, set that city to state.city
  // filterBy - custom filtering
  // onKeyDown - allow using enter key without selecting from typeahead
  // onInputChange - could be used to set value = state on each keypress.
  render() {
    return (

        <form
          onSubmit={this.onFormSubmit}
          data-cy="submit"
          className="input-group"
        >
          <AsyncTypeahead
            isLoading={this.state.isLoading}
            onSearch={query => this.onInputChange(query)}
            options={this.state.options}
            labelKey={row => `${row.city}, ${row.area}, ${row.country}`}
            ref={typeahead => (this.typeahead = typeahead)}
            placeholder="Check the weather in your favorite cities"
            // onChange={city => this.setState({ city: city[0] })}
            filterBy={this.filterResults.bind(this)}
            onKeyDown={e => this.handleKeyDown(e)}
            data-cy="searchbar"
          />

            <button type="submit" className="btn btn-secondary">
              Submit
            </button>

        </form>

    );
  }
}

function mapStateToProps(state) {
  return { cities: state.weather };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(
    { fetchWeatherFromOpenWeather, setCityList, setNotification },
    dispatch
  );
}

// null means we don't need state here.
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(SearchBar);
<|endoftext|>


<commit_before>var mv = require('mv');
var replace = require('replace-in-file');
var find = require('find-in-files');

function copyAndReplace(name) {
    var fromName = `./src/app/${name}/${name}.component.alt.css`;
    var tempName = `./src/app/${name}/${name}.component.temp.css`;
    var toName = `./src/app/${name}/${name}.component.css`;
    mv(toName, tempName, { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
    mv(fromName, toName, { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
    mv(tempName, fromName, { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
}

function copyAndReplaceAll(names) {
    for (name of names) {
        copyAndReplace(name);
    }
}

let names = ["action", "actions", "application-properties", "attachment", "attachment-property", "button", "buttons",
    "cicero", "collection", "collections", "dialog", "dynamic-error", "dynamic-list", "dynamic-object", "edit-parameter",
    "edit-property", "error", "footer", "home", "list", "menu", "menus", "multi-line-dialog", "object", "parameters",
    "properties", "recent"]

copyAndReplaceAll(names);

mv("./src/styles.css", "./src/styles.temp.css", { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
mv("./src/styles.alt.css", "./src/styles.css", { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
mv("./src/styles.temp.css", "./src/styles.alt.css", { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });

// to update package name
var optionsToAlt = {
    files: ["./package.json"],
    from: [/nakedobjects.spa/g],
    to: "nakedobjects.alt.spa"
};

var optionsFromAlt = {
    files: ["./package.json"],
    from: [/nakedobjects.alt.spa/g],
    to: "nakedobjects.spa"
};

find.findSync("name", ".", "package.json").then(s => {

    try {
        var nameLine = s["package.json"].line[0];
        var nameSplit = nameLine.split('"');
        var name = nameSplit[3];

        var newName = name.indexOf("alt") >= 0 ? "nakedobjects.spa" : "nakedobjects.alt.spa";

        var regex = new RegExp(name, "g");

        var options = {
            files: ["package.json"],
            from: regex,
            to: newName
        };

        replace.sync(options);
    } catch (e) {
        console.error('Error occurred updating name:', e);
    }
});<commit_msg>move packaging<commit_after>var mv = require('mv');
var replace = require('replace-in-file');
var find = require('find-in-files');

function copyAndReplace(name) {
    var fromName = `./src/app/${name}/${name}.component.alt.css`;
    var tempName = `./src/app/${name}/${name}.component.temp.css`;
    var toName = `./src/app/${name}/${name}.component.css`;
    mv(toName, tempName, { clobber: false, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
    mv(fromName, toName, { clobber: false, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
    mv(tempName, fromName, { clobber: false, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
}

function copyAndReplaceAll(names) {
    for (name of names) {
        copyAndReplace(name);
    }
}

let names = ["action", "actions", "application-properties", "attachment", "attachment-property", "button", "buttons",
    "cicero", "collection", "collections", "dialog", "dynamic-error", "dynamic-list", "dynamic-object", "edit-parameter",
    "edit-property", "error", "footer", "home", "list", "menu", "menus", "multi-line-dialog", "object", "parameters",
    "properties", "recent"]

copyAndReplaceAll(names);

mv("./src/styles.css", "./src/styles.temp.css", { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
mv("./src/styles.alt.css", "./src/styles.css", { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });
mv("./src/styles.temp.css", "./src/styles.alt.css", { clobber: true, mkdirp: false }, function (err) { if (err) console.error('Error occurred:', err); });

// to update package name
var optionsToAlt = {
    files: ["./package.json"],
    from: [/nakedobjects.spa/g],
    to: "nakedobjects.alt.spa"
};

find.findSync("name", ".", "package.json").then(s => {

    try {
        var nameLine = s["package.json"].line[0];
        var nameSplit = nameLine.split('"');
        var name = nameSplit[3];

        var newName = name.indexOf("alt") >= 0 ? "nakedobjects.spa" : "nakedobjects.alt.spa";

        var regex = new RegExp(name, "g");

        var options = {
            files: ["package.json"],
            from: regex,
            to: newName
        };

        replace.sync(options);
    } catch (e) {
        console.error('Error occurred updating name:', e);
    }
});<|endoftext|>


<commit_before>
// keywords: js, javascript, json, deep clone

/**
 * Clones jsonable object in deep
 *
 * @param  {A} object The jsonable object to clone
 * @return {A} The cloned object
 */
function jsonDeepClone(object) {
  var tmp = JSON.stringify(object);
  if (tmp === undefined) {
    return undefined;
  }
  return JSON.parse(tmp);
}

////////////////////////////////////////////////////////////////////////////////

// keywords: js, javascript, native, deep clone

/**
 * Clones all native object in deep. Managed types: Object, Array, String,
 * Number, Boolean, Function, null.
 *
 * @param  {A} object The object to clone
 * @return {A} The cloned object
 */
function deepClone(object) {
  var i, cloned;
  if (Array.isArray(object)) {
    cloned = [];
    for (i = 0; i < object.length; i += 1) {
      cloned[i] = deepClone(object[i]);
    }
    return cloned;
  }
  if (typeof object === "object") {
    cloned = {};
    for (i in object) {
      if (object.hasOwnProperty(i)) {
        cloned[i] = deepClone(object[i]);
      }
    }
    return cloned;
  }
  return object;
}

exports.jsonDeepClone = jsonDeepClone
exports.deepClone = deepClone

////////////////////////////////////////////////////////////////////////////////
// Visual Tests

var a = {"_events": {
  "a": function () {},
  "b": [function () {}, function () {}],
  "c": [1, "a"]
}};
var b = deepClone(a);
delete a._events.c
delete a._events.b[0]
console.log(a);
console.log(b);
<commit_msg>deepClone.js tests improved<commit_after>
// keywords: js, javascript, json, deep clone

/**
 * Clones jsonable object in deep
 *
 * @param  {A} object The jsonable object to clone
 * @return {A} The cloned object
 */
function jsonDeepClone(object) {
  var tmp = JSON.stringify(object);
  if (tmp === undefined) {
    return undefined;
  }
  return JSON.parse(tmp);
}

////////////////////////////////////////////////////////////////////////////////

// keywords: js, javascript, native, deep clone

/**
 * Clones all native object in deep. Managed types: Object, Array, String,
 * Number, Boolean, Function, null.
 *
 * @param  {A} object The object to clone
 * @return {A} The cloned object
 */
function deepClone(object) {
  var i, cloned;
  if (Array.isArray(object)) {
    cloned = [];
    for (i = 0; i < object.length; i += 1) {
      cloned[i] = deepClone(object[i]);
    }
    return cloned;
  }
  if (typeof object === "object") {
    cloned = {};
    for (i in object) {
      if (object.hasOwnProperty(i)) {
        cloned[i] = deepClone(object[i]);
      }
    }
    return cloned;
  }
  return object;
}

exports.jsonDeepClone = jsonDeepClone
exports.deepClone = deepClone

////////////////////////////////////////////////////////////////////////////////
// Visual Tests

var inspect = require('util').inspect;

var a = {"_events": {
  "a": function () {},
  "b": [function () {}, function () {}],
  "c": [1, "a"]
}};
var b = deepClone(a);
delete a._events.c
delete a._events.b[0]
console.log(inspect(a) ===
            "{ _events: { a: [Function], b: [ , [Function] ] } }");
console.log(inspect(b) ===
            "{ _events: { a: [Function], b: [ [Function], " +
            "[Function] ], c: [ 1, 'a' ] } }");
<|endoftext|>


<commit_before>import React from 'react';

import { shallow, mount } from 'enzyme';
import Immutable from 'immutable';
import {expect} from 'chai';

import CodeCell from '../../../../src/notebook/components/cell/code-cell';
import * as commutable from 'commutable';
import { displayOrder, transforms } from 'transformime-react';

const sharedProps = { displayOrder, transforms };
describe('CodeCell', () => {
  it('can be rendered', () => {
    const cell = shallow(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell).to.not.be.null;
  });
  it('creates an editor', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell.find('.input').length).to.be.greaterThan(0);
  });
});
<commit_msg>Add more tests for CodeCell<commit_after>import React from 'react';

import { shallow, mount } from 'enzyme';
import Immutable from 'immutable';
import {expect} from 'chai';

import CodeCell from '../../../../src/notebook/components/cell/code-cell';
import * as commutable from 'commutable';
import { displayOrder, transforms } from 'transformime-react';

const sharedProps = { displayOrder, transforms };
describe('CodeCell', () => {
  it('can be rendered', () => {
    const cell = shallow(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell).to.not.be.null;
  });
  it('creates an editor', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell.find('.input').length).to.be.greaterThan(0);
  });
  it('creates a pager', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}
      pagers={Immutable.Map({'content':
        Immutable.Map({'data': Immutable.Map({'text/plain': 'one'})})})}/>
    );
    expect(cell.find('.pagers').length).to.be.greaterThan(0);
  });

});
<|endoftext|>


<commit_before>// Characters that should be escaped when saving.
var EncodeToXhtmlEntity = ["amp", "gt", "lt", "quot", "nbsp"];

var XhtmlEntityFromChars = {};
for (var i = 0; i < EncodeToXhtmlEntity.length; i++) {
    var entity = EncodeToXhtmlEntity[i];
    XhtmlEntityFromChars[XhtmlEntities[entity]] = entity;
}

// A regular expression that matches characters that can be converted to entities.
var XhtmlEntityChars = "[";
for (var code in XhtmlEntityFromChars) {
    var c = parseInt(code).toString(16);
    while (c.length < 4) {
        c = "0" + c;
    }
    XhtmlEntityChars += "\\u" + c;
}
XhtmlEntityChars += "]";

function decodeText(text) {
	text = text.replace(/
/gi, "\n");
    text = text.replace(/&(\w+);/g, function(str, p1) {
            var c = XhtmlEntities[p1];
            if (c) {
                return String.fromCharCode(c);
            } else {
                return str;
            }
        });
    text = text.replace(/&#(\d+);/g, function(str, p1) {
            return String.fromCharCode(parseInt(p1));
        });
    text = text.replace(/&#x([0-9a-f]+);/gi, function(str, p1) {
            return String.fromCharCode(parseInt(p1, 16));
        });
    text = text.replace(/ +/g, " "); // truncate multiple spaces to single space
    text = text.replace(/\xA0/g, " "); // treat nbsp as space
	if ('true' == options.escapeDollar) {
		text = text.replace(/([^\\])\$\{/g, '$1$$$${'); // replace [^\]${...} with $${...}
		text = text.replace(/^\$\{/g, '$$$${'); // replace ^${...} with $${...}
		text = text.replace(/\\\$\{/g, '$${'); // replace \${...} with ${...}
	}
    // trim spaces
	text = text.replace(/^\s+/, "");
	text = text.replace(/\s+$/, "");
	return text;
}

function encodeText(text) {
    // & -> &
    // & -> &amp;
    // " -> &quot;
    // \xA0 ->
    text = text.replace(new RegExp(XhtmlEntityChars, "g"),
                        function(c) {
            var entity = XhtmlEntityFromChars[c.charCodeAt(c)];
            if (entity) {
                return "&" + entity + ";";
            } else {
                throw "Failed to encode entity: " + c;
            }
        });
    text = text.replace(/ {2,}/g, function(str) {
            var result = '';
            for (var i = 0; i < str.length; i++) {
                result += ' ';
            }
            return result;
        }); // convert multiple spaces to nbsp
	if ('true' == options.escapeDollar) {
		text = text.replace(/([^\$])\$\{/g, '$1\\${'); // replace [^$]${...} with \${...}
		text = text.replace(/^\$\{/g, '\\${'); // replace ^${...} with \${...}
		text = text.replace(/\$\$\{/g, '${'); // replace $${...} with ${...}
	}
    text = text.replace(/\n/g, "
");
	return text;
}

function convertText(command, converter) {
	var props = ['command', 'target', 'value'];
	for (var i = 0; i < props.length; i++) {
		var prop = props[i];
		command[prop] = converter(command[prop]);
	}
}

/**
 * Parse source and update TestCase. Throw an exception if any error occurs.
 *
 * @param testCase TestCase to update
 * @param source The source to parse
 */
function parse(testCase, source) {
	var commandRegexp = new RegExp(options.commandLoadPattern, 'i');
	var commentRegexp = new RegExp(options.commentLoadPattern, 'i');
	var commandOrCommentRegexp = new RegExp("((" + options.commandLoadPattern + ")|(" + options.commentLoadPattern + "))", 'ig');
	var doc = source;
	var commands = [];
	var commandFound = false;
	var lastIndex;
	while (true) {
		//log.debug("doc=" + doc + ", commandRegexp=" + commandRegexp);
		lastIndex = commandOrCommentRegexp.lastIndex;
		var docResult = commandOrCommentRegexp.exec(doc);
		if (docResult) {
			if (docResult[2]) { // command
				var command = new Command();
				command.skip = docResult.index - lastIndex;
				command.index = docResult.index;
				var result = commandRegexp.exec(doc.substring(lastIndex));
				eval(options.commandLoadScript);
				convertText(command, decodeText);
				commands.push(command);
				if (!commandFound) {
					// remove comments before the first command or comment
					for (var i = commands.length - 1; i >= 0; i--) {
						if (commands[i].skip > 0) {
							commands.splice(0, i);
							break;
						}
					}
					testCase.header = doc.substr(0, commands[0].index);
					commandFound = true;
				}
			} else { // comment
				var comment = new Comment();
				comment.skip = docResult.index - lastIndex;
				comment.index = docResult.index;
				var result = commentRegexp.exec(doc.substring(lastIndex));
				eval(options.commentLoadScript);
				commands.push(comment);
			}
		} else {
			break;
		}
	}
	if (commands.length > 0) {
		testCase.footer = doc.substring(lastIndex);
		log.debug("header=" + testCase.header);
		log.debug("footer=" + testCase.footer);
        if (testCase.header &&
            /<link\s+rel="selenium\.base"\s+href="(.*)"/.test(testCase.header)) {
            testCase.baseURL = decodeURI(RegExp.$1);
        }
		//log.debug("commands.length=" + commands.length);
		testCase.commands = commands;
	} else {
		throw "no command found";
	}
}

function getSourceForCommand(commandObj) {
	var command = null;
	var comment = null;
	var template = '';
	if (commandObj.type == 'command') {
		command = commandObj;
		command = command.createCopy();
		convertText(command, this.encodeText);
		template = options.commandTemplate;
	} else if (commandObj.type == 'comment') {
		comment = commandObj;
		template = options.commentTemplate;
	}
	var result;
	var text = template.replace(/\$\{([a-zA-Z0-9_\.]+)\}/g,
        function(str, p1, offset, s) {
            result = eval(p1);
            return result != null ? result : '';
        });
	return text;
}

/**
 * Format an array of commands to the snippet of source.
 * Used to copy the source into the clipboard.
 *
 * @param The array of commands to sort.
 */
function formatCommands(commands) {
	var commandsText = '';
	for (i = 0; i < commands.length; i++) {
		var text = getSourceForCommand(commands[i]);
		commandsText = commandsText + text;
	}
	return commandsText;
}

/**
 * Format TestCase and return the source.
 * The 3rd and 4th parameters are used only in default HTML format.
 *
 * @param testCase TestCase to format
 * @param name The name of the test case, if any. It may be used to embed title into the source.
 * @param saveHeaderAndFooter true if the header and footer should be saved into the TestCase.
 * @param useDefaultHeaderAndFooter Parameter used for only default format.
 */
function format(testCase, name, saveHeaderAndFooter, useDefaultHeaderAndFooter) {
	var text;
	var commandsText = "";
	var testText;
	var i;

	for (i = 0; i < testCase.commands.length; i++) {
		var text = getSourceForCommand(testCase.commands[i]);
		commandsText = commandsText + text;
	}

	var testText;
	if (testCase.header == null || testCase.footer == null || useDefaultHeaderAndFooter) {
		testText = options.testTemplate;
		testText = testText.replace(/\$\{name\}/g, name);
		var encoding = options["global.encoding"];
		if (!encoding) encoding = "UTF-8";
		testText = testText.replace(/\$\{encoding\}/g, encoding);
		testText = testText.replace(/\$\{baseURL\}/g, encodeURI(testCase.baseURL));
		var commandsIndex = testText.indexOf("${commands}");
		if (commandsIndex >= 0) {
			var header = testText.substr(0, commandsIndex);
			var footer = testText.substr(commandsIndex + "${commands}".length);
			testText = header + commandsText + footer;
			if (saveHeaderAndFooter) {
				testCase.header = header;
				testCase.footer = footer;
			}
		}
	} else {
		testText = testCase.header + commandsText + testCase.footer;
	}

	return testText;
}

/*
 * Optional: The customizable option that can be used in format/parse functions.
 */
this.options = {
	commandLoadPattern:
	"
]*>" +
	"\\s*()?" +
	"\\s*]*>\\s*([\\w]*?)\\s*	" +
	"\\s*]*>([\\d\\D]*?)	" +
	"\\s*(|]*>([\\d\\D]*?)	)" +
	"\\s*
\\s*",

	commandLoadScript:
	"command.command = result[2];\n" +
	"command.target = result[3];\n" +
	"command.value = result[5] || '';\n",

	commentLoadPattern:
	"\\s*",

	commentLoadScript:
	"comment.comment = result[1];\n",

	testTemplate:
    '<?xml version="1.0" encoding="${encoding}"?>\n' +
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
	'\n' +
	'\n' +
	'<meta http-equiv="Content-Type" content="text/html; charset=${encoding}" />\n' +
    '<link rel="selenium.base" href="${baseURL}" />\n' +
	"<title>${name}</title>\n" +
	"\n" +
	"\n" +
	'
\n'+
	'
\n' +
	'
${name}
\n' +
	"

\n" +
	"${commands}\n" +
	"

\n" +
	"\n" +
	"\n",

	commandTemplate:
	"
\n" +
	"\t${command.command}	\n" +
	"\t${command.target}	\n" +
	"\t${command.value}	\n" +
	"
\n",

	commentTemplate:
	"\n",

	escapeDollar:
	"false"
};

/*
 * Optional: XUL XML String for the UI of the options dialog
 */
this.configForm =
	//'<tabbox flex="1"><tabs orient="horizontal"><tab label="Load"/><tab label="Save"/></tabs>' +
	//'<tabpanels flex="1">' +
	//'<tabpanel orient="vertical">' +
	'<description>Regular expression for each command entry</description>' +
	'<textbox id="options_commandLoadPattern" flex="1"/>' +
	'<separator class="thin"/>' +
	'<description>Script to load command from the pattern</description>' +
	'<textbox id="options_commandLoadScript" multiline="true" flex="1" rows="2"/>' +
	//'<separator class="thin"/>' +
	//'<description>Regular expression for comments between commands</description>' +
	//'<textbox id="options_commentLoadPattern" flex="1"/>' +
	//'<separator class="thin"/>' +
	//'<description>Script to load comment from the pattern</description>' +
	//'<textbox id="options_commentLoadScript" multiline="true" flex="1" rows="2"/>' +
	'<separator class="groove"/>' +
	//'</vbox><vbox>' +
	//'</tabpanel>' +
	//'<tabpanel orient="vertical">' +
	'<description>Template for new test html file</description>' +
	'<textbox id="options_testTemplate" multiline="true" flex="1" rows="3"/>' +
	'<separator class="thin"/>' +
	'<description>Template for command entries in the test html file</description>' +
	'<textbox id="options_commandTemplate" multiline="true" flex="1" rows="3"/>' +
	'<separator class="groove"/>' +
	'<checkbox id="options_escapeDollar" label="Escape \'${\' as \'\\${\' (useful for JSP 2.0)"/>';
	//'<separator class="thin"/>' +
	//'<description>Template for comment entries in the test html file</description>' +
	//'<textbox id="options_commentTemplate" multiline="true" flex="1"/>' +
	//'</tabpanel></tabpanels></tabbox>';
<commit_msg>Make decodeText/encodeText work even when text is null (maybe it will fix SIDE-208?)<commit_after>// Characters that should be escaped when saving.
var EncodeToXhtmlEntity = ["amp", "gt", "lt", "quot", "nbsp"];

var XhtmlEntityFromChars = {};
for (var i = 0; i < EncodeToXhtmlEntity.length; i++) {
    var entity = EncodeToXhtmlEntity[i];
    XhtmlEntityFromChars[XhtmlEntities[entity]] = entity;
}

// A regular expression that matches characters that can be converted to entities.
var XhtmlEntityChars = "[";
for (var code in XhtmlEntityFromChars) {
    var c = parseInt(code).toString(16);
    while (c.length < 4) {
        c = "0" + c;
    }
    XhtmlEntityChars += "\\u" + c;
}
XhtmlEntityChars += "]";

function decodeText(text) {
    if (text == null) return "";
	text = text.replace(/
/gi, "\n");
    text = text.replace(/&(\w+);/g, function(str, p1) {
            var c = XhtmlEntities[p1];
            if (c) {
                return String.fromCharCode(c);
            } else {
                return str;
            }
        });
    text = text.replace(/&#(\d+);/g, function(str, p1) {
            return String.fromCharCode(parseInt(p1));
        });
    text = text.replace(/&#x([0-9a-f]+);/gi, function(str, p1) {
            return String.fromCharCode(parseInt(p1, 16));
        });
    text = text.replace(/ +/g, " "); // truncate multiple spaces to single space
    text = text.replace(/\xA0/g, " "); // treat nbsp as space
	if ('true' == options.escapeDollar) {
		text = text.replace(/([^\\])\$\{/g, '$1$$$${'); // replace [^\]${...} with $${...}
		text = text.replace(/^\$\{/g, '$$$${'); // replace ^${...} with $${...}
		text = text.replace(/\\\$\{/g, '$${'); // replace \${...} with ${...}
	}
    // trim spaces
	text = text.replace(/^\s+/, "");
	text = text.replace(/\s+$/, "");
	return text;
}

function encodeText(text) {
    if (text == null) return "";
    // & -> &
    // & -> &amp;
    // " -> &quot;
    // \xA0 ->
    text = text.replace(new RegExp(XhtmlEntityChars, "g"),
                        function(c) {
            var entity = XhtmlEntityFromChars[c.charCodeAt(c)];
            if (entity) {
                return "&" + entity + ";";
            } else {
                throw "Failed to encode entity: " + c;
            }
        });
    text = text.replace(/ {2,}/g, function(str) {
            var result = '';
            for (var i = 0; i < str.length; i++) {
                result += ' ';
            }
            return result;
        }); // convert multiple spaces to nbsp
	if ('true' == options.escapeDollar) {
		text = text.replace(/([^\$])\$\{/g, '$1\\${'); // replace [^$]${...} with \${...}
		text = text.replace(/^\$\{/g, '\\${'); // replace ^${...} with \${...}
		text = text.replace(/\$\$\{/g, '${'); // replace $${...} with ${...}
	}
    text = text.replace(/\n/g, "
");
	return text;
}

function convertText(command, converter) {
	var props = ['command', 'target', 'value'];
	for (var i = 0; i < props.length; i++) {
		var prop = props[i];
		command[prop] = converter(command[prop]);
	}
}

/**
 * Parse source and update TestCase. Throw an exception if any error occurs.
 *
 * @param testCase TestCase to update
 * @param source The source to parse
 */
function parse(testCase, source) {
	var commandRegexp = new RegExp(options.commandLoadPattern, 'i');
	var commentRegexp = new RegExp(options.commentLoadPattern, 'i');
	var commandOrCommentRegexp = new RegExp("((" + options.commandLoadPattern + ")|(" + options.commentLoadPattern + "))", 'ig');
	var doc = source;
	var commands = [];
	var commandFound = false;
	var lastIndex;
	while (true) {
		//log.debug("doc=" + doc + ", commandRegexp=" + commandRegexp);
		lastIndex = commandOrCommentRegexp.lastIndex;
		var docResult = commandOrCommentRegexp.exec(doc);
		if (docResult) {
			if (docResult[2]) { // command
				var command = new Command();
				command.skip = docResult.index - lastIndex;
				command.index = docResult.index;
				var result = commandRegexp.exec(doc.substring(lastIndex));
				eval(options.commandLoadScript);
				convertText(command, decodeText);
				commands.push(command);
				if (!commandFound) {
					// remove comments before the first command or comment
					for (var i = commands.length - 1; i >= 0; i--) {
						if (commands[i].skip > 0) {
							commands.splice(0, i);
							break;
						}
					}
					testCase.header = doc.substr(0, commands[0].index);
					commandFound = true;
				}
			} else { // comment
				var comment = new Comment();
				comment.skip = docResult.index - lastIndex;
				comment.index = docResult.index;
				var result = commentRegexp.exec(doc.substring(lastIndex));
				eval(options.commentLoadScript);
				commands.push(comment);
			}
		} else {
			break;
		}
	}
	if (commands.length > 0) {
		testCase.footer = doc.substring(lastIndex);
		log.debug("header=" + testCase.header);
		log.debug("footer=" + testCase.footer);
        if (testCase.header &&
            /<link\s+rel="selenium\.base"\s+href="(.*)"/.test(testCase.header)) {
            testCase.baseURL = decodeURI(RegExp.$1);
        }
		//log.debug("commands.length=" + commands.length);
		testCase.commands = commands;
	} else {
		throw "no command found";
	}
}

function getSourceForCommand(commandObj) {
	var command = null;
	var comment = null;
	var template = '';
	if (commandObj.type == 'command') {
		command = commandObj;
		command = command.createCopy();
		convertText(command, this.encodeText);
		template = options.commandTemplate;
	} else if (commandObj.type == 'comment') {
		comment = commandObj;
		template = options.commentTemplate;
	}
	var result;
	var text = template.replace(/\$\{([a-zA-Z0-9_\.]+)\}/g,
        function(str, p1, offset, s) {
            result = eval(p1);
            return result != null ? result : '';
        });
	return text;
}

/**
 * Format an array of commands to the snippet of source.
 * Used to copy the source into the clipboard.
 *
 * @param The array of commands to sort.
 */
function formatCommands(commands) {
	var commandsText = '';
	for (i = 0; i < commands.length; i++) {
		var text = getSourceForCommand(commands[i]);
		commandsText = commandsText + text;
	}
	return commandsText;
}

/**
 * Format TestCase and return the source.
 * The 3rd and 4th parameters are used only in default HTML format.
 *
 * @param testCase TestCase to format
 * @param name The name of the test case, if any. It may be used to embed title into the source.
 * @param saveHeaderAndFooter true if the header and footer should be saved into the TestCase.
 * @param useDefaultHeaderAndFooter Parameter used for only default format.
 */
function format(testCase, name, saveHeaderAndFooter, useDefaultHeaderAndFooter) {
	var text;
	var commandsText = "";
	var testText;
	var i;

	for (i = 0; i < testCase.commands.length; i++) {
		var text = getSourceForCommand(testCase.commands[i]);
		commandsText = commandsText + text;
	}

	var testText;
	if (testCase.header == null || testCase.footer == null || useDefaultHeaderAndFooter) {
		testText = options.testTemplate;
		testText = testText.replace(/\$\{name\}/g, name);
		var encoding = options["global.encoding"];
		if (!encoding) encoding = "UTF-8";
		testText = testText.replace(/\$\{encoding\}/g, encoding);
		testText = testText.replace(/\$\{baseURL\}/g, encodeURI(testCase.baseURL));
		var commandsIndex = testText.indexOf("${commands}");
		if (commandsIndex >= 0) {
			var header = testText.substr(0, commandsIndex);
			var footer = testText.substr(commandsIndex + "${commands}".length);
			testText = header + commandsText + footer;
			if (saveHeaderAndFooter) {
				testCase.header = header;
				testCase.footer = footer;
			}
		}
	} else {
		testText = testCase.header + commandsText + testCase.footer;
	}

	return testText;
}

/*
 * Optional: The customizable option that can be used in format/parse functions.
 */
this.options = {
	commandLoadPattern:
	"
]*>" +
	"\\s*()?" +
	"\\s*]*>\\s*([\\w]*?)\\s*	" +
	"\\s*]*>([\\d\\D]*?)	" +
	"\\s*(|]*>([\\d\\D]*?)	)" +
	"\\s*
\\s*",

	commandLoadScript:
	"command.command = result[2];\n" +
	"command.target = result[3];\n" +
	"command.value = result[5] || '';\n",

	commentLoadPattern:
	"\\s*",

	commentLoadScript:
	"comment.comment = result[1];\n",

	testTemplate:
    '<?xml version="1.0" encoding="${encoding}"?>\n' +
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
	'\n' +
	'\n' +
	'<meta http-equiv="Content-Type" content="text/html; charset=${encoding}" />\n' +
    '<link rel="selenium.base" href="${baseURL}" />\n' +
	"<title>${name}</title>\n" +
	"\n" +
	"\n" +
	'
\n'+
	'
\n' +
	'
${name}
\n' +
	"

\n" +
	"${commands}\n" +
	"

\n" +
	"\n" +
	"\n",

	commandTemplate:
	"
\n" +
	"\t${command.command}	\n" +
	"\t${command.target}	\n" +
	"\t${command.value}	\n" +
	"
\n",

	commentTemplate:
	"\n",

	escapeDollar:
	"false"
};

/*
 * Optional: XUL XML String for the UI of the options dialog
 */
this.configForm =
	//'<tabbox flex="1"><tabs orient="horizontal"><tab label="Load"/><tab label="Save"/></tabs>' +
	//'<tabpanels flex="1">' +
	//'<tabpanel orient="vertical">' +
	'<description>Regular expression for each command entry</description>' +
	'<textbox id="options_commandLoadPattern" flex="1"/>' +
	'<separator class="thin"/>' +
	'<description>Script to load command from the pattern</description>' +
	'<textbox id="options_commandLoadScript" multiline="true" flex="1" rows="2"/>' +
	//'<separator class="thin"/>' +
	//'<description>Regular expression for comments between commands</description>' +
	//'<textbox id="options_commentLoadPattern" flex="1"/>' +
	//'<separator class="thin"/>' +
	//'<description>Script to load comment from the pattern</description>' +
	//'<textbox id="options_commentLoadScript" multiline="true" flex="1" rows="2"/>' +
	'<separator class="groove"/>' +
	//'</vbox><vbox>' +
	//'</tabpanel>' +
	//'<tabpanel orient="vertical">' +
	'<description>Template for new test html file</description>' +
	'<textbox id="options_testTemplate" multiline="true" flex="1" rows="3"/>' +
	'<separator class="thin"/>' +
	'<description>Template for command entries in the test html file</description>' +
	'<textbox id="options_commandTemplate" multiline="true" flex="1" rows="3"/>' +
	'<separator class="groove"/>' +
	'<checkbox id="options_escapeDollar" label="Escape \'${\' as \'\\${\' (useful for JSP 2.0)"/>';
	//'<separator class="thin"/>' +
	//'<description>Template for comment entries in the test html file</description>' +
	//'<textbox id="options_commentTemplate" multiline="true" flex="1"/>' +
	//'</tabpanel></tabpanels></tabbox>';
<|endoftext|>


<commit_before>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Displays species sunburst based on the combined lineages from all xrefs.
// Based on http://bl.ocks.org/mbostock/4348373
// Text labels: http://blog.luzid.com/2013/extending-the-d3-zoomable-sunburst-with-labels/

d3SpeciesSunburst = function(data, selector, width, height){

	var jsonTree = data,
	    radius = Math.min(width, height) / 2;

	var x = d3.scale.linear()
	    .range([0, 2 * Math.PI]);

	var y = d3.scale.linear()
	    .range([0, radius]);

	var color = d3.scale.category20c();

	var svg = d3.select(selector).append("svg")
	    .attr("width", width)
	    .attr("height", height)
	  .append("g")
	    .attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");

	var partition = d3.layout.partition()
	    .value(function(d) { return d.size; });

	var arc = d3.svg.arc()
	    .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
	    .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
	    .innerRadius(function(d) { return Math.max(0, y(d.y)); })
	    .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });

	// d3.json("flare.json", function(error, root) {
	  var g = svg.selectAll("g")
	      .data(partition.nodes(jsonTree))
	    .enter().append("g");

	  var path = g.append("path")
	    .attr("d", arc)
	    // .style("fill", function(d) { return color((d.children ? d : d.parent).name); })
	    .attr("fill", function(d) { return "#6baed6"; })
	    .attr("title", function(d) { return d.name; })
		.on("mouseover", function() {
		  d3.select(this)
		    .attr('fill', '#f0ad4e')
		})
		.on("mouseout",  function() {
		  d3.select(this)
		    .attr('fill', '#3F7D97' )
	  	});

	    ;
	    // .on("click", click);

	  // var text = g.append("text")
	  //   .attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
	  //   .attr("x", function(d) { return y(d.y); })
	  //   .attr("dx", "6") // margin
	  //   .attr("dy", ".35em") // vertical-align
	  //   .text(function(d) { return d.name; });

	  // function click(d) {
	  //   // fade out all text elements
	  //   text.transition().attr("opacity", 0);

	  //   path.transition()
	  //     .duration(750)
	  //     .attrTween("d", arcTween(d))
	  //     .each("end", function(e, i) {
	  //         // check if the animated element's data e lies within the visible angle span given in d
	  //         if (e.x >= d.x && e.x < (d.x + d.dx)) {
	  //           // get a selection of the associated text element
	  //           var arcText = d3.select(this.parentNode).select("text");
	  //           // fade in the text element and recalculate positions
	  //           arcText.transition().duration(0)
	  //             .attr("opacity", 1)
	  //             .attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
	  //             .attr("x", function(d) { return y(d.y); });
	  //         }
	  //     });
	  // }
	// });

	d3.select(self.frameElement).style("height", height + "px");

	//initializing tooltips
	$("svg path").tooltip({
	    'container': 'body'
	});

	// Interpolate the scales!
	function arcTween(d) {
	  var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
	      yd = d3.interpolate(y.domain(), [d.y, 1]),
	      yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
	  return function(d, i) {
	    return i
	        ? function(t) { return arc(d); }
	        : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
	  };
	}

	function computeTextRotation(d) {
	  return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
	}

};
<commit_msg>Show the number of sequences on mouseover on the species sunburst<commit_after>/*
Copyright [2009-2014] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Displays species sunburst based on the combined lineages from all xrefs.
// Based on http://bl.ocks.org/mbostock/4348373
// Text labels: http://blog.luzid.com/2013/extending-the-d3-zoomable-sunburst-with-labels/

d3SpeciesSunburst = function(data, selector, width, height){

	var jsonTree = data,
	    radius = Math.min(width, height) / 2;

	var x = d3.scale.linear()
	    .range([0, 2 * Math.PI]);

	var y = d3.scale.linear()
	    .range([0, radius]);

	var color = d3.scale.category20c();

	var svg = d3.select(selector).append("svg")
	    .attr("width", width)
	    .attr("height", height)
	  .append("g")
	    .attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");

	var partition = d3.layout.partition()
	    .value(function(d) { return d.size; });

	var arc = d3.svg.arc()
	    .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
	    .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
	    .innerRadius(function(d) { return Math.max(0, y(d.y)); })
	    .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });

	// d3.json("flare.json", function(error, root) {
	  var g = svg.selectAll("g")
	      .data(partition.nodes(jsonTree))
	    .enter().append("g");

	  // show the number of sequences for terminal nodes
	  var get_title = function(d){
	    var title = d.name;
	    if (d.hasOwnProperty('size')) {
	      title += " (" + d.size + " sequences)";
	    }
	    return title;
	  };

	  var path = g.append("path")
	    .attr("d", arc)
	    // .style("fill", function(d) { return color((d.children ? d : d.parent).name); })
	    .attr("fill", function(d) { return "#6baed6"; })
	    .attr("title", function(d) { return get_title(d); })
		.on("mouseover", function() {
		  d3.select(this)
		    .attr('fill', '#f0ad4e')
		})
		.on("mouseout",  function() {
		  d3.select(this)
		    .attr('fill', '#3F7D97' )
	  	});

	    ;
	    // .on("click", click);

	  // var text = g.append("text")
	  //   .attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
	  //   .attr("x", function(d) { return y(d.y); })
	  //   .attr("dx", "6") // margin
	  //   .attr("dy", ".35em") // vertical-align
	  //   .text(function(d) { return d.name; });

	  // function click(d) {
	  //   // fade out all text elements
	  //   text.transition().attr("opacity", 0);

	  //   path.transition()
	  //     .duration(750)
	  //     .attrTween("d", arcTween(d))
	  //     .each("end", function(e, i) {
	  //         // check if the animated element's data e lies within the visible angle span given in d
	  //         if (e.x >= d.x && e.x < (d.x + d.dx)) {
	  //           // get a selection of the associated text element
	  //           var arcText = d3.select(this.parentNode).select("text");
	  //           // fade in the text element and recalculate positions
	  //           arcText.transition().duration(0)
	  //             .attr("opacity", 1)
	  //             .attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
	  //             .attr("x", function(d) { return y(d.y); });
	  //         }
	  //     });
	  // }
	// });

	d3.select(self.frameElement).style("height", height + "px");

	//initializing tooltips
	$("svg path").tooltip({
	    'container': 'body'
	});

	// Interpolate the scales!
	function arcTween(d) {
	  var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
	      yd = d3.interpolate(y.domain(), [d.y, 1]),
	      yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
	  return function(d, i) {
	    return i
	        ? function(t) { return arc(d); }
	        : function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
	  };
	}

	function computeTextRotation(d) {
	  return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
	}

};
<|endoftext|>


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

Yatzy.chance = function() {
    return 15;
}

module.exports = Yatzy;

<commit_msg>Add JavaScript implementation<commit_after>var Yatzy = function(d1, d2, d3, d4, _5) {
    this.dice = [];
    this.dice[0] = d1;
    this.dice[1] = d2;
    this.dice[2] = d3;
    this.dice[3] = d4;
    this.dice[4] = _5;

    this.fours = function()
    {
        var sum;
        sum = 0;
        for (at = 0; at != 5; at++) {
            if (this.dice[at] == 4) {
                sum += 4;
            }
        }
        return sum;
    }

    this.fives = function()
    {
        s = 0
        var i
        for (i = 0; i < this.dice.length; i++)
            if (this.dice[i] == 5)
                s = s + 5;
        return s;
    }

    this.sixes = function()
    {
        sum = 0;
        for (var at = 0; at < this.dice.length; at++)
            if (this.dice[at] == 6)
                sum = sum + 6;
        return sum;
    }
}

Yatzy.chance = function(d1, d2, d3, d4, d5) {
    var total = 0;
    total += d1;
    total += d2;
    total += d3;
    total += d4;
    total += d5;
    return total;
}

Yatzy.yatzy = function() {
    var counts = [0, 0, 0, 0, 0, 0, 0, 0];
    for (var i = 0; i != arguments.length; ++i) {
    var die = arguments[i];
    counts[die-1]++; }
    for (i = 0; i != 6; i++)
        if (counts[i] == 5)
            return 50;
    return 0;
}

Yatzy.ones = function(d1, d2, d3, d4, d5) {
    var sum = 0;
    if (d1 == 1) sum++;
    if (d2 == 1) sum++;
    if (d3 == 1) sum++;
    if (d4 == 1) sum++;
    if (d5 == 1)
        sum++;

    return sum;
}

Yatzy.twos = function(d1, d2, d3, d4, d5) {
    var sum = 0;
    if (d1 == 2) sum += 2;
    if (d2 == 2) sum += 2;
    if (d3 == 2) sum += 2;
    if (d4 == 2) sum += 2;
    if (d5 == 2) sum += 2;
    return sum;
}

Yatzy.threes = function(d1, d2, d3, d4, d5) {
    var s;
    s = 0;
    if (d1 == 3) s += 3;
    if (d2 == 3) s += 3;
    if (d3 == 3) s += 3;
    if (d4 == 3) s += 3;
    if (d5 == 3) s += 3;
    return s;
}

Yatzy.score_pair = function(d1, d2, d3, d4, d5)
{
    var counts = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    counts[d1-1]++;
    counts[d2-1]++;
    counts[d3-1]++;
    counts[d4-1]++;
    counts[d5-1]++;
    var at;
    for (at = 0; at != 6; at++)
        if (counts[6-at-1] >= 2)
            return (6-at)*2;
    return 0;
}

Yatzy.two_pair = function(d1, d2, d3, d4, d5)
{
    var counts = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    counts[d1-1]++;
    counts[d2-1]++
    counts[d3-1]++
    counts[d4-1]++;
    counts[d5-1]++;
    var n = 0;
    var score = 0;
    for (i = 0; i < 6; i += 1)
        if (counts[6-i-1] >= 2) {
            n++;
            score += (6-i);
        }
    if (n == 2)
        return score * 2;
    else
        return 0;
}

Yatzy.four_of_a_kind = function(_1, _2, d3, d4, d5)
{
    var tallies;
    tallies = [0, 0, 0, 0, 0, 0, 0, 0]
    tallies[_1-1]++;
    tallies[_2-1]++;
    tallies[d3-1]++;
    tallies[d4-1]++;
    tallies[d5-1]++;
    for (i = 0; i < 6; i++)
        if (tallies[i] >= 4)
            return (i+1) * 4;
    return 0;
}

Yatzy.three_of_a_kind = function(d1, d2, d3, d4, d5)
{
    var t;
    t = [0, 0, 0, 0, 0, 0, 0, 0, 0]
    t[d1-1]++;
    t[d2-1]++;
    t[d3-1]++;
    t[d4-1]++;
    t[d5-1]++;
    for (i = 0; i < 6; i++)
        if (t[i] >= 3)
            return (i+1) * 3;
    return 0;
}

Yatzy.smallStraight = function(d1, d2, d3, d4, d5)
{
    var tallies;
    tallies = [0, 0, 0, 0, 0, 0, 0]
    tallies[d1-1] += 1;
    tallies[d2-1] += 1;
    tallies[d3-1] += 1;
    tallies[d4-1] += 1;
    tallies[d5-1] += 1;
    if (tallies[0] == 1 &&
        tallies[1] == 1 &&
        tallies[2] == 1 &&
        tallies[3] == 1 &&
        tallies[4] == 1)
        return 15;
    return 0;
}

Yatzy.largeStraight = function(d1, d2, d3, d4, d5)
{
    var tallies;
    tallies = [0, 0, 0, 0,0,0,0,0];
    tallies[d1-1] += 1;
    tallies[d2-1] += 1;
    tallies[d3-1] += 1;
    tallies[d4-1] += 1;
    tallies[d5-1] += 1;
    if (tallies[1] == 1 &&
        tallies[2] == 1 &&
        tallies[3] == 1 &&
        tallies[4] == 1
        && tallies[5] == 1)
        return 20;
    return 0;
}

Yatzy.fullHouse = function(d1, d2, d3, d4, d5)
{
    var tallies;
    var  _2 = false;
    var i;
    var _2_at = 0;
    var _3 = false;
    var _3_at = 0;

    tallies = [0, 0, 0, 0, 0, 0, 0, 0];
    tallies[d1-1] += 1;
    tallies[d2-1] += 1;
    tallies[d3-1] += 1;
    tallies[d4-1] += 1;
    tallies[d5-1] += 1;

    for (i = 0; i != 6; i += 1)
        if (tallies[i] == 2) {
            _2 = true;
            _2_at = i+1;
        }

    for (i = 0; i != 6; i += 1)
        if (tallies[i] == 3) {
            _3 = true;
            _3_at = i+1;
        }

    if (_2 && _3)
        return _2_at * 2 + _3_at * 3;
    else
        return 0;
}

module.exports = Yatzy;

<|endoftext|>


<commit_before>describe('angulargmMapController', function() {
  var scope, elm;
  var mapCtrl, mapCntr;

  beforeEach(function() {
    module('AngularGM');
  });

  beforeEach(inject(function($rootScope, $controller, angulargmContainer) {
    // set up scope
    scope = $rootScope.$new();
    scope.gmMapOptions = function() {
      return {
        center: new google.maps.LatLng(2, 3),
        zoom: 1,
        mapTypeId: google.maps.MapTypeId.TERRAIN
      };
    };
    scope.gmMapId = function() {
      return 'test';
    };

    // set up element
    elm = angular.element('
' +
                                '

' +
                              '</div');

    mapCtrl = $controller('angulargmMapController', {$scope: scope, $element: elm});
    mapCntr = angulargmContainer;
  }));

  afterEach(inject(function(angulargmContainer) {
    if (scope && scope.$destroy) {
      scope.$destroy();
    }
    angulargmContainer.removeMap('test');
  }));

  it('constructs the map using the provided map options', function() {
    expect(mapCtrl.dragging).toBeFalsy();
    expect(mapCtrl.center).toEqual(new google.maps.LatLng(2, 3));
    expect(mapCtrl.zoom).toEqual(1);
    var map = mapCntr.getMap(scope.gmMapId());
    expect(mapCtrl.bounds).toEqual(map.getBounds());
    expect(mapCtrl.mapTypeId).toEqual(google.maps.MapTypeId.TERRAIN);
  });

  it('constructs the map using defaults when there are no options', inject(function($rootScope, $controller, angulargmDefaults) {
    scope = $rootScope.$new();
    scope.gmMapOptions = function() { };
    scope.gmMapId = function() {
      return 'test2';
    };

    var elm2 = angular.element('
' +
                                '

' +
                              '</div');
    mapCtrl = $controller('angulargmMapController', {$scope: scope, $element: elm2});

    expect(mapCtrl.center).toEqual(angulargmDefaults.mapOptions.center);
    expect(mapCtrl.zoom).toEqual(angulargmDefaults.mapOptions.zoom);
  }));

  it('resets map on controller re-instantiation', inject(function($rootScope, $controller) {
    var map = mapCntr.getMap(scope.gmMapId());
    var scope2 = $rootScope.$new();
    var gmMapOptions = scope.gmMapOptions();
    var gmMapId = scope.gmMapId();
    scope2.gmMapOptions = function() { return gmMapOptions };
    scope2.gmMapId = function() { return gmMapId };

    // move map
    var newCenter = new google.maps.LatLng(gmMapOptions.center.lat() + 5,
      gmMapOptions.center.lng() + 5);
    map.setCenter(newCenter);
    map.setZoom(gmMapOptions.zoom + 2);
    map.setMapTypeId(google.maps.MapTypeId.SATELLITE);
    expect(mapCtrl.center).toEqual(newCenter); // sanity check--we actually changed something

    // destroy scope
    scope.$destroy();

    // re-instantiate controller
    mapCtrl = $controller('angulargmMapController', {$scope: scope2, $element: elm});

    expect(mapCtrl.center).toEqual(gmMapOptions.center);
    expect(mapCtrl.zoom).toEqual(gmMapOptions.zoom);
    expect(map.getMapTypeId()).toEqual(gmMapOptions.mapTypeId);
  }));

  it('removes markers on scope destroy', function() {
    var mapId = scope.gmMapId();
    scope.$destroy();
    numMarkers = 0;
    mapCtrl.forEachMarker(function(marker) {
      numMarkers++;
    });
    expect(numMarkers).toEqual(0);
  });

  it('adds listeners to the map', function() {
    var called = false;
    mapCtrl.addMapListener('center_changed', function() {
      called = true;
    });
    google.maps.event.trigger(mapCntr.getMap(scope.gmMapId()), 'center_changed');

    expect(called).toBeTruthy();
  });

  it('adds one time listeners to the map', function() {
    var callCount = 0;
    mapCtrl.addMapListenerOnce('center_changed', function() {
      callCount++;
    });
    // Does not get added to the listeners hash
    expect(mapCtrl._listeners.center_changed).not.toBeDefined();

    google.maps.event.trigger(mapCntr.getMap(scope.gmMapId()), 'center_changed');
    google.maps.event.trigger(mapCntr.getMap(scope.gmMapId()), 'center_changed');

    expect(callCount).toEqual(1);
  });

  it('keeps map listeners in a hash', function() {
    var listeners = mapCtrl._listeners;
    expect(listeners.click).toBeUndefined();
    expect(listeners.center_changed).toBeUndefined();

    mapCtrl.addMapListener('center_changed', function() {});
    mapCtrl.addMapListener('click', function() {});
    expect(listeners.click).not.toBeUndefined();
    expect(listeners.center_changed).not.toBeUndefined();
  });

  it('keeps multiple listeners on an event', function() {
    var aFunc = function() {};
    var anotherFunc = function() {};

    mapCtrl.addMapListener('click', aFunc);
    var listener = mapCtrl._listeners.click;
    mapCtrl.addMapListener('click', anotherFunc);

    expect(angular.isArray(mapCtrl._listeners.click)).toBeTruthy();
    expect(mapCtrl._listeners.click.length).toEqual(2);
  });

  it('adds generic listeners', function() {
    var called = false;
    var object = {};
    mapCtrl.addListener(object, 'event', function() {
      called = true;
    });
    google.maps.event.trigger(object, 'event');

    expect(called).toBeTruthy();
  });

  it('adds generic one time listeners', function() {
    var callCount = 0;
    var object = {};
    mapCtrl.addListenerOnce(object, 'event', function() {
      callCount++;
    });
    google.maps.event.trigger(object, 'event');
    google.maps.event.trigger(object, 'event');

    expect(callCount).toEqual(1);
  });

  it('triggers map events', function() {
    var callCount = 0;
    mapCtrl.addMapListener('event', function() {
      callCount++;
    });
    mapCtrl.mapTrigger('event');

    expect(callCount).toEqual(1);
  });

  it('triggers generic events', function() {
    var callCount = 0;
    var object = {};
    mapCtrl.addListener(object, 'event', function() {
      callCount++;
    });
    mapCtrl.trigger(object, 'event');

    expect(callCount).toEqual(1);
  });

  describe('marker functions', function() {
    var position, positionSame, positionVeryClose, position2;
    var markerOptions, markerOptionsSame, markerOptionsVeryClose, markerOptions2;

    beforeEach(function() {
      position = new google.maps.LatLng(1, 2);
      positionSame = new google.maps.LatLng(1.0004, 2.0004);
      positionVeryClose = new google.maps.LatLng(1.0005, 2.0005);
      position2 = new google.maps.LatLng(3, 4);

      scope = 'scope';

      markerOptions = {
        position: position
      };
      markerOptionsSame = {
        position: positionSame
      };
      markerOptionsVeryClose = {
        position: positionVeryClose
      };
      markerOptions2 = {
        position: position2
      };

      mapCtrl.addMarker(scope, markerOptions);
    });

    describe('addMarker', function() {

      it('adds new markers to the map', function() {
        added = mapCtrl.addMarker(scope, markerOptions2);
        expect(added).toBeTruthy();
      });

      it('does not add markers already on the map', function() {
        var added = mapCtrl.addMarker(scope, markerOptions);
        expect(added).toBeFalsy();
      });

      it('adds markers which differ by at least 0.0005', function() {
        var added = mapCtrl.addMarker(scope, markerOptionsVeryClose);
        expect(added).toBeTruthy();
      });

      it('does not add markers which differ less than 0.0005', function() {
        var added = mapCtrl.addMarker(scope, markerOptionsSame);
        expect(added).toBeFalsy();
      });

    });

    describe('getMarker', function() {

      it('retrieves markers that are on the map', function() {
        var marker = mapCtrl.getMarker(scope, position.lat(), position.lng());
        expect(marker.getPosition()).toEqual(markerOptions.position);
      });

      it('returns null for marker not on the map', function() {
        var marker = mapCtrl.getMarker(scope, position2.lat(), position2.lng());
        expect(marker).toBeNull();
      });

      it('retrives markers given a lat and lng that are within 0.0005', function() {
        var marker = mapCtrl.getMarker(scope, positionSame.lat(), positionSame.lng());
        expect(marker.getPosition()).toEqual(markerOptions.position);
      });

      it('does not retrieve marker given lat and lng more than 0.0005 away', function() {
        var marker = mapCtrl.getMarker(scope, positionVeryClose.lat(), positionVeryClose.lng());
        expect(marker).toBeNull();
      });

    });

    describe('removeMarker', function() {

      it('removes markers from the map', function() {
        var removed = mapCtrl.removeMarker(scope, position.lat(), position.lng());
        expect(removed).toBeTruthy();
        expect(mapCtrl.getMarker(scope, position.lat(), position.lng())).toBeNull();
      });

      it('does not remove markers not on the map', function() {
        var removed = mapCtrl.removeMarker(scope, position2.lat(), position2.lng());
        expect(removed).toBeFalsy();
        expect(mapCtrl.getMarker(scope, position.lat(), position.lng())).not.toBeNull();
      });

    });

    it('can apply a function to each marker', function() {
      markers = [];
      mapCtrl.forEachMarker(function(marker) {
        markers.push(marker);
      });
      expect(markers.length).toEqual(1);
      expect(markers[0].getPosition()).toEqual(markerOptions.position);
    });

    it('does not apply a function to removed markers', function() {
      mapCtrl.removeMarker(scope, 1, 2);
      var called = false;
      mapCtrl.forEachMarker(function(marker) {
        called = true;
      });
      expect(called).toBeFalsy();
    });

  });
});
<commit_msg>modify test to ensure call<commit_after>describe('angulargmMapController', function() {
  var scope, elm;
  var mapCtrl, mapCntr;

  beforeEach(function() {
    module('AngularGM');
  });

  beforeEach(inject(function($rootScope, $controller, angulargmContainer) {
    // set up scope
    scope = $rootScope.$new();
    scope.gmMapOptions = function() {
      return {
        center: new google.maps.LatLng(2, 3),
        zoom: 1,
        mapTypeId: google.maps.MapTypeId.TERRAIN
      };
    };
    scope.gmMapId = function() {
      return 'test';
    };

    // set up element
    elm = angular.element('
' +
                                '

' +
                              '</div');

    mapCtrl = $controller('angulargmMapController', {$scope: scope, $element: elm});
    mapCntr = angulargmContainer;
  }));

  afterEach(inject(function(angulargmContainer) {
    if (scope && scope.$destroy) {
      scope.$destroy();
    }
    angulargmContainer.removeMap('test');
  }));

  it('constructs the map using the provided map options', function() {
    expect(mapCtrl.dragging).toBeFalsy();
    expect(mapCtrl.center).toEqual(new google.maps.LatLng(2, 3));
    expect(mapCtrl.zoom).toEqual(1);
    var map = mapCntr.getMap(scope.gmMapId());
    expect(mapCtrl.bounds).toEqual(map.getBounds());
    expect(mapCtrl.mapTypeId).toEqual(google.maps.MapTypeId.TERRAIN);
  });

  it('constructs the map using defaults when there are no options', inject(function($rootScope, $controller, angulargmDefaults) {
    scope = $rootScope.$new();
    scope.gmMapOptions = function() { };
    scope.gmMapId = function() {
      return 'test2';
    };

    var elm2 = angular.element('
' +
                                '

' +
                              '</div');
    mapCtrl = $controller('angulargmMapController', {$scope: scope, $element: elm2});

    expect(mapCtrl.center).toEqual(angulargmDefaults.mapOptions.center);
    expect(mapCtrl.zoom).toEqual(angulargmDefaults.mapOptions.zoom);
  }));

  it('resets map on controller re-instantiation', inject(function($rootScope, $controller) {
    var map = mapCntr.getMap(scope.gmMapId());
    var scope2 = $rootScope.$new();
    var gmMapOptions = scope.gmMapOptions();
    var gmMapId = scope.gmMapId();
    scope2.gmMapOptions = function() { return gmMapOptions };
    scope2.gmMapId = function() { return gmMapId };

    // move map
    var newCenter = new google.maps.LatLng(gmMapOptions.center.lat() + 5,
      gmMapOptions.center.lng() + 5);
    map.setCenter(newCenter);
    map.setZoom(gmMapOptions.zoom + 2);
    map.setMapTypeId(google.maps.MapTypeId.SATELLITE);
    expect(mapCtrl.center).toEqual(newCenter); // sanity check--we actually changed something

    // destroy scope
    scope.$destroy();

    // re-instantiate controller
    mapCtrl = $controller('angulargmMapController', {$scope: scope2, $element: elm});

    expect(mapCtrl.center).toEqual(gmMapOptions.center);
    expect(mapCtrl.zoom).toEqual(gmMapOptions.zoom);
    expect(map.getMapTypeId()).toEqual(gmMapOptions.mapTypeId);
  }));

  it('removes markers on scope destroy', function() {
    var mapId = scope.gmMapId();
    scope.$destroy();
    numMarkers = 0;
    mapCtrl.forEachMarker(function(marker) {
      numMarkers++;
    });
    expect(numMarkers).toEqual(0);
  });

  it('adds listeners to the map', function() {
    var called = false;
    mapCtrl.addMapListener('center_changed', function() {
      called = true;
    });
    google.maps.event.trigger(mapCntr.getMap(scope.gmMapId()), 'center_changed');

    expect(called).toBeTruthy();
  });

  it('adds one time listeners to the map', function() {
    var callCount = 0;
    mapCtrl.addMapListenerOnce('center_changed', function() {
      callCount++;
    });
    // Does not get added to the listeners hash
    expect(mapCtrl._listeners.center_changed).not.toBeDefined();

    google.maps.event.trigger(mapCntr.getMap(scope.gmMapId()), 'center_changed');
    google.maps.event.trigger(mapCntr.getMap(scope.gmMapId()), 'center_changed');

    expect(callCount).toEqual(1);
  });

  it('keeps map listeners in a hash', function() {
    var listeners = mapCtrl._listeners;
    expect(listeners.click).toBeUndefined();
    expect(listeners.center_changed).toBeUndefined();

    mapCtrl.addMapListener('center_changed', function() {});
    mapCtrl.addMapListener('click', function() {});
    expect(listeners.click).not.toBeUndefined();
    expect(listeners.center_changed).not.toBeUndefined();
  });

  it('keeps multiple listeners on an event', function() {
    var aFunc = jasmine.createSpy('firstFunc');
    var anotherFunc = jasmine.createSpy('secondFunc');

    mapCtrl.addMapListener('click', aFunc);
    mapCtrl.addMapListener('click', anotherFunc);

    expect(angular.isArray(mapCtrl._listeners.click)).toBeTruthy();
    expect(mapCtrl._listeners.click.length).toEqual(2);

    mapCtrl.mapTrigger('click');
    expect(aFunc).toHaveBeenCalled();
    expect(anotherFunc).toHaveBeenCalled();
  });

  it('adds generic listeners', function() {
    var called = false;
    var object = {};
    mapCtrl.addListener(object, 'event', function() {
      called = true;
    });
    google.maps.event.trigger(object, 'event');

    expect(called).toBeTruthy();
  });

  it('adds generic one time listeners', function() {
    var callCount = 0;
    var object = {};
    mapCtrl.addListenerOnce(object, 'event', function() {
      callCount++;
    });
    google.maps.event.trigger(object, 'event');
    google.maps.event.trigger(object, 'event');

    expect(callCount).toEqual(1);
  });

  it('triggers map events', function() {
    var callCount = 0;
    mapCtrl.addMapListener('event', function() {
      callCount++;
    });
    mapCtrl.mapTrigger('event');

    expect(callCount).toEqual(1);
  });

  it('triggers generic events', function() {
    var callCount = 0;
    var object = {};
    mapCtrl.addListener(object, 'event', function() {
      callCount++;
    });
    mapCtrl.trigger(object, 'event');

    expect(callCount).toEqual(1);
  });

  describe('marker functions', function() {
    var position, positionSame, positionVeryClose, position2;
    var markerOptions, markerOptionsSame, markerOptionsVeryClose, markerOptions2;

    beforeEach(function() {
      position = new google.maps.LatLng(1, 2);
      positionSame = new google.maps.LatLng(1.0004, 2.0004);
      positionVeryClose = new google.maps.LatLng(1.0005, 2.0005);
      position2 = new google.maps.LatLng(3, 4);

      scope = 'scope';

      markerOptions = {
        position: position
      };
      markerOptionsSame = {
        position: positionSame
      };
      markerOptionsVeryClose = {
        position: positionVeryClose
      };
      markerOptions2 = {
        position: position2
      };

      mapCtrl.addMarker(scope, markerOptions);
    });

    describe('addMarker', function() {

      it('adds new markers to the map', function() {
        added = mapCtrl.addMarker(scope, markerOptions2);
        expect(added).toBeTruthy();
      });

      it('does not add markers already on the map', function() {
        var added = mapCtrl.addMarker(scope, markerOptions);
        expect(added).toBeFalsy();
      });

      it('adds markers which differ by at least 0.0005', function() {
        var added = mapCtrl.addMarker(scope, markerOptionsVeryClose);
        expect(added).toBeTruthy();
      });

      it('does not add markers which differ less than 0.0005', function() {
        var added = mapCtrl.addMarker(scope, markerOptionsSame);
        expect(added).toBeFalsy();
      });

    });

    describe('getMarker', function() {

      it('retrieves markers that are on the map', function() {
        var marker = mapCtrl.getMarker(scope, position.lat(), position.lng());
        expect(marker.getPosition()).toEqual(markerOptions.position);
      });

      it('returns null for marker not on the map', function() {
        var marker = mapCtrl.getMarker(scope, position2.lat(), position2.lng());
        expect(marker).toBeNull();
      });

      it('retrives markers given a lat and lng that are within 0.0005', function() {
        var marker = mapCtrl.getMarker(scope, positionSame.lat(), positionSame.lng());
        expect(marker.getPosition()).toEqual(markerOptions.position);
      });

      it('does not retrieve marker given lat and lng more than 0.0005 away', function() {
        var marker = mapCtrl.getMarker(scope, positionVeryClose.lat(), positionVeryClose.lng());
        expect(marker).toBeNull();
      });

    });

    describe('removeMarker', function() {

      it('removes markers from the map', function() {
        var removed = mapCtrl.removeMarker(scope, position.lat(), position.lng());
        expect(removed).toBeTruthy();
        expect(mapCtrl.getMarker(scope, position.lat(), position.lng())).toBeNull();
      });

      it('does not remove markers not on the map', function() {
        var removed = mapCtrl.removeMarker(scope, position2.lat(), position2.lng());
        expect(removed).toBeFalsy();
        expect(mapCtrl.getMarker(scope, position.lat(), position.lng())).not.toBeNull();
      });

    });

    it('can apply a function to each marker', function() {
      markers = [];
      mapCtrl.forEachMarker(function(marker) {
        markers.push(marker);
      });
      expect(markers.length).toEqual(1);
      expect(markers[0].getPosition()).toEqual(markerOptions.position);
    });

    it('does not apply a function to removed markers', function() {
      mapCtrl.removeMarker(scope, 1, 2);
      var called = false;
      mapCtrl.forEachMarker(function(marker) {
        called = true;
      });
      expect(called).toBeFalsy();
    });

  });
});
<|endoftext|>