<commit_before>// ---- Angular Hammer ----

// Copyright (c) 2014 Ryan S Mullins <ryan@ryanmullins.org>
// Licensed under the MIT Software License

(function (window, angular, Hammer) {
  'use strict';

  // Checking to make sure Hammer and Angular are defined

  if (typeof angular === 'undefined') {
    if (typeof require !== 'undefined' && require) {
      try {
        angular = require('angular');
      } catch (e) {
        return console.log('ERROR: Angular Hammer could not require() a reference to angular');
      }
    } else if (typeof window.angular !== 'undefined') {
      angular = window.angular;
    } else {
      return console.log('ERROR: Angular Hammer could not find or require() a reference to angular');
    }
  }

  if (typeof Hammer === 'undefined') {
    if (typeof require !== 'undefined' && require) {
      try {
        Hammer = require('hammerjs');
      } catch (e) {
        return console.log('ERROR: Angular Hammer could not require() a reference to Hammer');
      }
    } else if (typeof window.Hammer !== 'undefined') {
      Hammer = window.Hammer;
    } else {
      return console.log('ERROR: Angular Hammer could not find or require() a reference to Hammer');
    }
  }

  /**
   * Mapping of the gesture event names with the Angular attribute directive
   * names. Follows the form: <directiveName>:<eventName>.
   *
   * @type {Array}
   */
  var gestureTypes = [
    'hmCustom:custom',
    'hmSwipe:swipe',
    'hmSwipeleft:swipeleft',
    'hmSwiperight:swiperight',
    'hmSwipeup:swipeup',
    'hmSwipedown:swipedown',
    'hmPan:pan',
    'hmPanstart:panstart',
    'hmPanmove:panmove',
    'hmPanend:panend',
    'hmPancancel:pancancel',
    'hmPanleft:panleft',
    'hmPanright:panright',
    'hmPanup:panup',
    'hmPandown:pandown',
    'hmPress:press',
    'hmPressup:pressup',
    'hmRotate:rotate',
    'hmRotatestart:rotatestart',
    'hmRotatemove:rotatemove',
    'hmRotateend:rotateend',
    'hmRotatecancel:rotatecancel',
    'hmPinch:pinch',
    'hmPinchstart:pinchstart',
    'hmPinchmove:pinchmove',
    'hmPinchend:pinchend',
    'hmPinchcancel:pinchcancel',
    'hmPinchin:pinchin',
    'hmPinchout:pinchout',
    'hmTap:tap',
    'hmDoubletap:doubletap'
  ];

  // ---- Module Definition ----

  /**
   * @module hmTouchEvents
   * @description Angular.js module for adding Hammer.js event listeners to HTML
   * elements using attribute directives
   * @requires angular
   * @requires hammer
   */
  angular.module('hmTouchEvents', []);

  /**
   * Iterates through each gesture type mapping and creates a directive for
   * each of the
   *
   * @param  {String} type Mapping in the form of <directiveName>:<eventName>
   * @return None
   */
  angular.forEach(gestureTypes, function (type) {
    var directive = type.split(':'),
        directiveName = directive[0],
        eventName = directive[1];

    angular.module('hmTouchEvents')
      .directive(directiveName, ['$parse', '$window', function ($parse, $window) {
        return {
          'restrict' : 'A',
          'link' : function (scope, element, attrs) {

            // Check for Hammer and required functionality
            // If no Hammer, maybe bind tap and doubletap to click and dblclick

            if (!Hammer || !$window.addEventListener) {
              if (directiveName === 'hmTap') {
                element.bind('click', handler);
              }

              if (directiveName === 'hmDoubletap') {
                element.bind('dblclick', handler);
              }

              return;
            }

            var hammer = element.data('hammer'),
                managerOpts = angular.fromJson(attrs.hmManagerOptions),
                recognizerOpts = angular.fromJson(attrs.hmRecognizerOptions);

            // Check for a manager, make one if needed and destroy it when
            // the scope is destroyed

            if (!hammer) {
              hammer = new Hammer.Manager(element[0], managerOpts);
              element.data('hammer', hammer);
              scope.$on('$destroy', function () {
                hammer.destroy();
              });
            }

            // Instantiate the handler

            var handlerName = attrs[directiveName],
                handlerExpr = $parse(handlerName),
                handler = function (event) {
                  var phase = scope.$root.$$phase,
                      recognizer = hammer.get(event.type);

                  event.element = element;

                  if (recognizer) {
                    if (recognizer.options.preventDefault) {
                      event.preventDefault();
                    }

                    if (recognizer.options.stopPropagation) {
                      event.srcEvent.stopPropagation();
                    }
                  }

                  if (phase === '$apply' || phase === '$digest') {
                    callHandler();
                  } else {
                    scope.$apply(callHandler);
                  }

                  function callHandler () {
                    var fn = handlerExpr(scope);

                    if (fn) {
                      fn.call(scope, event);
                    }
                  }
                };

            // Setting up the recognizers based on the supplied options

            if (angular.isArray(recognizerOpts)) {
              // The recognizer options may be stored in an array. In this
              // case, Angular Hammer iterates through the array of options
              // trying to find an occurrence of the options.type in the event
              // name. If it find the type in the event name, it applies those
              // options to the recognizer for events with that name. If it
              // does not find the type in the event name it moves on.

              angular.forEach(recognizerOpts, function (options) {
                if (directiveName === 'hmCustom') {
                  eventName = options.event;
                } else {
                  if (!options.type) {
                    options.type = getRecognizerTypeFromeventName(eventName);
                  }

                  if (options.event) {
                    delete options.event;
                  }
                }

                if (directiveName === 'hmCustom' ||
                    eventName.indexOf(options.type) > -1) {
                  setupRecognizerWithOptions(
                    hammer,
                    applyManagerOptions(managerOpts, options),
                    element);
                }
              });
            } else if (angular.isObject(recognizerOpts)) {
              // Recognizer options may be stored as an object. In this case,
              // Angular Hammer checks to make sure that the options type
              // property is found in the event name. If the options are
              // designated for this general type of event, Angular Hammer
              // applies the options directly to the manager instance for
              // this element.

              if (directiveName === 'hmCustom') {
                eventName = recognizerOpts.event;
              } else {
                  if (!recognizerOpts.type) {
                    recognizerOpts.type = getRecognizerTypeFromeventName(eventName);
                  }

                  if (recognizerOpts.event) {
                    delete recognizerOpts.event;
                  }
              }

              if (directiveName === 'hmCustom' ||
                  eventName.indexOf(recognizerOpts.type) > -1) {
                setupRecognizerWithOptions(
                  hammer,
                  applyManagerOptions(managerOpts, recognizerOpts),
                  element);
              }
            } else if (directiveName !== 'hmCustom') {
              // If no options are supplied, or the supplied options do not
              // match any of the above conditions, Angular Hammer sets up
              // the default options that a manager instantiated using
              // Hammer() would have.

              recognizerOpts = {
                'type': getRecognizerTypeFromeventName(eventName)
              };

              if (directiveName === 'hmDoubletap') {
                recognizerOpts.event = eventName;
                recognizerOpts.taps = 2;

                if (hammer.get('tap')) {
                  recognizerOpts.recognizeWith = 'tap';
                }
              }

              if (recognizerOpts.type.indexOf('pan') > -1 &&
                  hammer.get('swipe')) {
                recognizerOpts.recognizeWith = 'swipe';
              }

              if (recognizerOpts.type.indexOf('pinch') > -1 &&
                  hammer.get('rotate')) {
                recognizerOpts.recognizeWith = 'rotate';
              }

              setupRecognizerWithOptions(
                hammer,
                applyManagerOptions(managerOpts, recognizerOpts),
                element);
            } else {
              eventName = null;
            }

            if (eventName) {
              hammer.on(eventName, handler);
            }
          }
        };
      }]);
  });

  // ---- Private Functions -----

  /**
   * Adds a gesture recognizer to a given manager. The type of recognizer to
   * add is determined by the value of the options.type property.
   *
   * @param {Object}  manager Hammer.js manager object assigned to an element
   * @param {Object}  options Options that define the recognizer to add
   * @return {Object} Reference to the new gesture recognizer, if successful,
   *                  null otherwise.
   */
  function addRecognizer (manager, options) {
    if (!manager || !options || !options.type) { return null; }

    var recognizer;

    if (options.type.indexOf('pan') > -1) {
      recognizer = new Hammer.Pan(options);
    } else if (options.type.indexOf('pinch') > -1) {
      recognizer = new Hammer.Pinch(options);
    } else if (options.type.indexOf('press') > -1) {
      recognizer = new Hammer.Press(options);
    } else if (options.type.indexOf('rotate') > -1) {
      recognizer = new Hammer.Rotate(options);
    } else if (options.type.indexOf('swipe') > -1) {
      recognizer = new Hammer.Swipe(options);
    } else {
      recognizer = new Hammer.Tap(options);
    }

    manager.add(recognizer);
    return recognizer;
  }

  /**
   * Applies certain manager options to individual recognizer options.
   *
   * @param  {Object} managerOpts    Manager options
   * @param  {Object} recognizerOpts Recognizer options
   * @return None
   */
  function applyManagerOptions (managerOpts, recognizerOpts) {
    if (managerOpts) {
      recognizerOpts.preventGhosts = managerOpts.preventGhosts;
    }

    return recognizerOpts;
  }

  /**
   * Extracts the type of recognizer that should be instantiated from a given
   * event name. Used only when no recognizer options are provided.
   *
   * @param  {String} eventName Name to derive the recognizer type from
   * @return {string}           Type of recognizer that fires events with that name
   */
  function getRecognizerTypeFromeventName (eventName) {
    if (eventName.indexOf('pan') > -1) {
      return 'pan';
    } else if (eventName.indexOf('pinch') > -1) {
      return 'pinch';
    } else if (eventName.indexOf('press') > -1) {
      return 'press';
    } else if (eventName.indexOf('rotate') > -1) {
      return 'rotate';
    } else if (eventName.indexOf('swipe') > -1) {
      return 'swipe';
    } else {
      return 'tap';
    }
  }

  /**
   * Applies the passed options object to the appropriate gesture recognizer.
   * Recognizers are created if they do not already exist. See the README for a
   * description of the options object that can be passed to this function.
   *
   * @param  {Object} manager Hammer.js manager object assigned to an element
   * @param  {Object} options Options applied to a recognizer managed by manager
   * @return None
   */
  function setupRecognizerWithOptions (manager, options, element) {
    if (!manager || !options) { return; }

    var recognizer = manager.get(options.type);

    if (!recognizer) {
      recognizer = addRecognizer(manager, options);
    }

    if (!options.directions) {
      if (options.type === 'pan' || options.type === 'swipe') {
        options.directions = 'DIRECTION_ALL';
      } else if (options.type.indexOf('left') > -1) {
        options.directions = 'DIRECTION_LEFT';
      } else if (options.type.indexOf('right') > -1) {
        options.directions = 'DIRECTION_RIGHT';
      } else if (options.type.indexOf('up') > -1) {
        options.directions = 'DIRECTION_UP';
      } else if (options.type.indexOf('down') > -1) {
        options.directions = 'DIRECTION_DOWN';
      } else {
        options.directions = '';
      }
    }

    options.direction = parseDirections(options.directions);
    recognizer.set(options);

    if (options.recognizeWith) {
      if (!manager.get(options.recognizeWith)){
        addRecognizer(manager, {type:options.recognizeWith});
      }

      recognizer.recognizeWith(manager.get(options.recognizeWith));
    }

    if (options.dropRecognizeWith && manager.get(options.dropRecognizeWith)) {
      recognizer.dropRecognizeWith(manager.get(options.dropRecognizeWith));
    }

    if (options.requireFailure) {
      if (!manager.get(options.requireFailure)){
        addRecognizer(manager, {type:options.requireFailure});
      }

      recognizer.requireFailure(manager.get(options.requireFailure));
    }

    if (options.dropRequireFailure && manager.get(options.dropRequireFailure)) {
      recognizer.dropRequireFailure(manager.get(options.dropRequireFailure));
    }

    if (options.preventGhosts && element) {
      preventGhosts(element);
    }
  }

  /**
   * Parses the value of the directions property of any Angular Hammer options
   * object and converts them into the standard Hammer.js directions values.
   *
   * @param  {String} dirs Direction names separated by '|' characters
   * @return {Number}      Hammer.js direction value
   */
  function parseDirections (dirs) {
    var directions = 0;

    angular.forEach(dirs.split('|'), function (direction) {
      if (Hammer.hasOwnProperty(direction)) {
        directions = directions | Hammer[direction];
      }
    });

    return directions;
  }

  // ---- Preventing Ghost Clicks ----

  /**
   * Modified from: https://gist.github.com/jtangelder/361052976f044200ea17
   *
   * Prevent click events after a touchend.
   *
   * Inspired/copy-paste from this article of Google by Ryan Fioravanti
   * https://developers.google.com/mobile/articles/fast_buttons#ghost
   */

  function preventGhosts (element) {
    if (!element) { return; }

    var coordinates = [],
        threshold = 25,
        timeout = 2500;

    if ('ontouchstart' in window) {
      element[0].addEventListener('touchstart', resetCoordinates, true);
      element[0].addEventListener('touchend', registerCoordinates, true);
      element[0].addEventListener('click', preventGhostClick, true);
      element[0].addEventListener('mouseup', preventGhostClick, true);
    }

    /**
     * prevent clicks if they're in a registered XY region
     * @param {MouseEvent} ev
     */
    function preventGhostClick (ev) {
      for (var i = 0; i < coordinates.length; i++) {
        var x = coordinates[i][0];
        var y = coordinates[i][1];

        // within the range, so prevent the click
        if (Math.abs(ev.clientX - x) < threshold &&
            Math.abs(ev.clientY - y) < threshold) {
          ev.stopPropagation();
          ev.preventDefault();
          break;
        }
      }
    }

    /**
     * reset the coordinates array
     */
    function resetCoordinates () {
      coordinates = [];
    }

    /**
     * remove the first coordinates set from the array
     */
    function popCoordinates () {
      coordinates.splice(0, 1);
    }

    /**
     * if it is an final touchend, we want to register it's place
     * @param {TouchEvent} ev
     */
    function registerCoordinates (ev) {
      // touchend is triggered on every releasing finger
      // changed touches always contain the removed touches on a touchend
      // the touches object might contain these also at some browsers (firefox os)
      // so touches - changedTouches will be 0 or lower, like -1, on the final touchend
      if(ev.touches.length - ev.changedTouches.length <= 0) {
        var touch = ev.changedTouches[0];
        coordinates.push([touch.clientX, touch.clientY]);

        setTimeout(popCoordinates, timeout);
      }
    }
  }
})(window, window.angular, window.Hammer);
<commit_msg>Reinstated the locals object in the callHandler function, fixes #21<commit_after>// ---- Angular Hammer ----

// Copyright (c) 2014 Ryan S Mullins <ryan@ryanmullins.org>
// Licensed under the MIT Software License

(function (window, angular, Hammer) {
  'use strict';

  // Checking to make sure Hammer and Angular are defined

  if (typeof angular === 'undefined') {
    if (typeof require !== 'undefined' && require) {
      try {
        angular = require('angular');
      } catch (e) {
        return console.log('ERROR: Angular Hammer could not require() a reference to angular');
      }
    } else if (typeof window.angular !== 'undefined') {
      angular = window.angular;
    } else {
      return console.log('ERROR: Angular Hammer could not find or require() a reference to angular');
    }
  }

  if (typeof Hammer === 'undefined') {
    if (typeof require !== 'undefined' && require) {
      try {
        Hammer = require('hammerjs');
      } catch (e) {
        return console.log('ERROR: Angular Hammer could not require() a reference to Hammer');
      }
    } else if (typeof window.Hammer !== 'undefined') {
      Hammer = window.Hammer;
    } else {
      return console.log('ERROR: Angular Hammer could not find or require() a reference to Hammer');
    }
  }

  /**
   * Mapping of the gesture event names with the Angular attribute directive
   * names. Follows the form: <directiveName>:<eventName>.
   *
   * @type {Array}
   */
  var gestureTypes = [
    'hmCustom:custom',
    'hmSwipe:swipe',
    'hmSwipeleft:swipeleft',
    'hmSwiperight:swiperight',
    'hmSwipeup:swipeup',
    'hmSwipedown:swipedown',
    'hmPan:pan',
    'hmPanstart:panstart',
    'hmPanmove:panmove',
    'hmPanend:panend',
    'hmPancancel:pancancel',
    'hmPanleft:panleft',
    'hmPanright:panright',
    'hmPanup:panup',
    'hmPandown:pandown',
    'hmPress:press',
    'hmPressup:pressup',
    'hmRotate:rotate',
    'hmRotatestart:rotatestart',
    'hmRotatemove:rotatemove',
    'hmRotateend:rotateend',
    'hmRotatecancel:rotatecancel',
    'hmPinch:pinch',
    'hmPinchstart:pinchstart',
    'hmPinchmove:pinchmove',
    'hmPinchend:pinchend',
    'hmPinchcancel:pinchcancel',
    'hmPinchin:pinchin',
    'hmPinchout:pinchout',
    'hmTap:tap',
    'hmDoubletap:doubletap'
  ];

  // ---- Module Definition ----

  /**
   * @module hmTouchEvents
   * @description Angular.js module for adding Hammer.js event listeners to HTML
   * elements using attribute directives
   * @requires angular
   * @requires hammer
   */
  angular.module('hmTouchEvents', []);

  /**
   * Iterates through each gesture type mapping and creates a directive for
   * each of the
   *
   * @param  {String} type Mapping in the form of <directiveName>:<eventName>
   * @return None
   */
  angular.forEach(gestureTypes, function (type) {
    var directive = type.split(':'),
        directiveName = directive[0],
        eventName = directive[1];

    angular.module('hmTouchEvents')
      .directive(directiveName, ['$parse', '$window', function ($parse, $window) {
        return {
          'restrict' : 'A',
          'link' : function (scope, element, attrs) {

            // Check for Hammer and required functionality
            // If no Hammer, maybe bind tap and doubletap to click and dblclick

            if (!Hammer || !$window.addEventListener) {
              if (directiveName === 'hmTap') {
                element.bind('click', handler);
              }

              if (directiveName === 'hmDoubletap') {
                element.bind('dblclick', handler);
              }

              return;
            }

            var hammer = element.data('hammer'),
                managerOpts = angular.fromJson(attrs.hmManagerOptions),
                recognizerOpts = angular.fromJson(attrs.hmRecognizerOptions);

            // Check for a manager, make one if needed and destroy it when
            // the scope is destroyed

            if (!hammer) {
              hammer = new Hammer.Manager(element[0], managerOpts);
              element.data('hammer', hammer);
              scope.$on('$destroy', function () {
                hammer.destroy();
              });
            }

            // Instantiate the handler

            var handlerName = attrs[directiveName],
                handlerExpr = $parse(handlerName),
                handler = function (event) {
                  var phase = scope.$root.$$phase,
                      recognizer = hammer.get(event.type);

                  event.element = element;

                  if (recognizer) {
                    if (recognizer.options.preventDefault) {
                      event.preventDefault();
                    }

                    if (recognizer.options.stopPropagation) {
                      event.srcEvent.stopPropagation();
                    }
                  }

                  if (phase === '$apply' || phase === '$digest') {
                    callHandler();
                  } else {
                    scope.$apply(callHandler);
                  }

                  function callHandler () {
                    var fn = handlerExpr(scope, {'$event':event});

                    if (fn) {
                      fn.call(scope, event);
                    }
                  }
                };

            // Setting up the recognizers based on the supplied options

            if (angular.isArray(recognizerOpts)) {
              // The recognizer options may be stored in an array. In this
              // case, Angular Hammer iterates through the array of options
              // trying to find an occurrence of the options.type in the event
              // name. If it find the type in the event name, it applies those
              // options to the recognizer for events with that name. If it
              // does not find the type in the event name it moves on.

              angular.forEach(recognizerOpts, function (options) {
                if (directiveName === 'hmCustom') {
                  eventName = options.event;
                } else {
                  if (!options.type) {
                    options.type = getRecognizerTypeFromeventName(eventName);
                  }

                  if (options.event) {
                    delete options.event;
                  }
                }

                if (directiveName === 'hmCustom' ||
                    eventName.indexOf(options.type) > -1) {
                  setupRecognizerWithOptions(
                    hammer,
                    applyManagerOptions(managerOpts, options),
                    element);
                }
              });
            } else if (angular.isObject(recognizerOpts)) {
              // Recognizer options may be stored as an object. In this case,
              // Angular Hammer checks to make sure that the options type
              // property is found in the event name. If the options are
              // designated for this general type of event, Angular Hammer
              // applies the options directly to the manager instance for
              // this element.

              if (directiveName === 'hmCustom') {
                eventName = recognizerOpts.event;
              } else {
                  if (!recognizerOpts.type) {
                    recognizerOpts.type = getRecognizerTypeFromeventName(eventName);
                  }

                  if (recognizerOpts.event) {
                    delete recognizerOpts.event;
                  }
              }

              if (directiveName === 'hmCustom' ||
                  eventName.indexOf(recognizerOpts.type) > -1) {
                setupRecognizerWithOptions(
                  hammer,
                  applyManagerOptions(managerOpts, recognizerOpts),
                  element);
              }
            } else if (directiveName !== 'hmCustom') {
              // If no options are supplied, or the supplied options do not
              // match any of the above conditions, Angular Hammer sets up
              // the default options that a manager instantiated using
              // Hammer() would have.

              recognizerOpts = {
                'type': getRecognizerTypeFromeventName(eventName)
              };

              if (directiveName === 'hmDoubletap') {
                recognizerOpts.event = eventName;
                recognizerOpts.taps = 2;

                if (hammer.get('tap')) {
                  recognizerOpts.recognizeWith = 'tap';
                }
              }

              if (recognizerOpts.type.indexOf('pan') > -1 &&
                  hammer.get('swipe')) {
                recognizerOpts.recognizeWith = 'swipe';
              }

              if (recognizerOpts.type.indexOf('pinch') > -1 &&
                  hammer.get('rotate')) {
                recognizerOpts.recognizeWith = 'rotate';
              }

              setupRecognizerWithOptions(
                hammer,
                applyManagerOptions(managerOpts, recognizerOpts),
                element);
            } else {
              eventName = null;
            }

            if (eventName) {
              hammer.on(eventName, handler);
            }
          }
        };
      }]);
  });

  // ---- Private Functions -----

  /**
   * Adds a gesture recognizer to a given manager. The type of recognizer to
   * add is determined by the value of the options.type property.
   *
   * @param {Object}  manager Hammer.js manager object assigned to an element
   * @param {Object}  options Options that define the recognizer to add
   * @return {Object} Reference to the new gesture recognizer, if successful,
   *                  null otherwise.
   */
  function addRecognizer (manager, options) {
    if (!manager || !options || !options.type) { return null; }

    var recognizer;

    if (options.type.indexOf('pan') > -1) {
      recognizer = new Hammer.Pan(options);
    } else if (options.type.indexOf('pinch') > -1) {
      recognizer = new Hammer.Pinch(options);
    } else if (options.type.indexOf('press') > -1) {
      recognizer = new Hammer.Press(options);
    } else if (options.type.indexOf('rotate') > -1) {
      recognizer = new Hammer.Rotate(options);
    } else if (options.type.indexOf('swipe') > -1) {
      recognizer = new Hammer.Swipe(options);
    } else {
      recognizer = new Hammer.Tap(options);
    }

    manager.add(recognizer);
    return recognizer;
  }

  /**
   * Applies certain manager options to individual recognizer options.
   *
   * @param  {Object} managerOpts    Manager options
   * @param  {Object} recognizerOpts Recognizer options
   * @return None
   */
  function applyManagerOptions (managerOpts, recognizerOpts) {
    if (managerOpts) {
      recognizerOpts.preventGhosts = managerOpts.preventGhosts;
    }

    return recognizerOpts;
  }

  /**
   * Extracts the type of recognizer that should be instantiated from a given
   * event name. Used only when no recognizer options are provided.
   *
   * @param  {String} eventName Name to derive the recognizer type from
   * @return {string}           Type of recognizer that fires events with that name
   */
  function getRecognizerTypeFromeventName (eventName) {
    if (eventName.indexOf('pan') > -1) {
      return 'pan';
    } else if (eventName.indexOf('pinch') > -1) {
      return 'pinch';
    } else if (eventName.indexOf('press') > -1) {
      return 'press';
    } else if (eventName.indexOf('rotate') > -1) {
      return 'rotate';
    } else if (eventName.indexOf('swipe') > -1) {
      return 'swipe';
    } else {
      return 'tap';
    }
  }

  /**
   * Applies the passed options object to the appropriate gesture recognizer.
   * Recognizers are created if they do not already exist. See the README for a
   * description of the options object that can be passed to this function.
   *
   * @param  {Object} manager Hammer.js manager object assigned to an element
   * @param  {Object} options Options applied to a recognizer managed by manager
   * @return None
   */
  function setupRecognizerWithOptions (manager, options, element) {
    if (!manager || !options) { return; }

    var recognizer = manager.get(options.type);

    if (!recognizer) {
      recognizer = addRecognizer(manager, options);
    }

    if (!options.directions) {
      if (options.type === 'pan' || options.type === 'swipe') {
        options.directions = 'DIRECTION_ALL';
      } else if (options.type.indexOf('left') > -1) {
        options.directions = 'DIRECTION_LEFT';
      } else if (options.type.indexOf('right') > -1) {
        options.directions = 'DIRECTION_RIGHT';
      } else if (options.type.indexOf('up') > -1) {
        options.directions = 'DIRECTION_UP';
      } else if (options.type.indexOf('down') > -1) {
        options.directions = 'DIRECTION_DOWN';
      } else {
        options.directions = '';
      }
    }

    options.direction = parseDirections(options.directions);
    recognizer.set(options);

    if (options.recognizeWith) {
      if (!manager.get(options.recognizeWith)){
        addRecognizer(manager, {type:options.recognizeWith});
      }

      recognizer.recognizeWith(manager.get(options.recognizeWith));
    }

    if (options.dropRecognizeWith && manager.get(options.dropRecognizeWith)) {
      recognizer.dropRecognizeWith(manager.get(options.dropRecognizeWith));
    }

    if (options.requireFailure) {
      if (!manager.get(options.requireFailure)){
        addRecognizer(manager, {type:options.requireFailure});
      }

      recognizer.requireFailure(manager.get(options.requireFailure));
    }

    if (options.dropRequireFailure && manager.get(options.dropRequireFailure)) {
      recognizer.dropRequireFailure(manager.get(options.dropRequireFailure));
    }

    if (options.preventGhosts && element) {
      preventGhosts(element);
    }
  }

  /**
   * Parses the value of the directions property of any Angular Hammer options
   * object and converts them into the standard Hammer.js directions values.
   *
   * @param  {String} dirs Direction names separated by '|' characters
   * @return {Number}      Hammer.js direction value
   */
  function parseDirections (dirs) {
    var directions = 0;

    angular.forEach(dirs.split('|'), function (direction) {
      if (Hammer.hasOwnProperty(direction)) {
        directions = directions | Hammer[direction];
      }
    });

    return directions;
  }

  // ---- Preventing Ghost Clicks ----

  /**
   * Modified from: https://gist.github.com/jtangelder/361052976f044200ea17
   *
   * Prevent click events after a touchend.
   *
   * Inspired/copy-paste from this article of Google by Ryan Fioravanti
   * https://developers.google.com/mobile/articles/fast_buttons#ghost
   */

  function preventGhosts (element) {
    if (!element) { return; }

    var coordinates = [],
        threshold = 25,
        timeout = 2500;

    if ('ontouchstart' in window) {
      element[0].addEventListener('touchstart', resetCoordinates, true);
      element[0].addEventListener('touchend', registerCoordinates, true);
      element[0].addEventListener('click', preventGhostClick, true);
      element[0].addEventListener('mouseup', preventGhostClick, true);
    }

    /**
     * prevent clicks if they're in a registered XY region
     * @param {MouseEvent} ev
     */
    function preventGhostClick (ev) {
      for (var i = 0; i < coordinates.length; i++) {
        var x = coordinates[i][0];
        var y = coordinates[i][1];

        // within the range, so prevent the click
        if (Math.abs(ev.clientX - x) < threshold &&
            Math.abs(ev.clientY - y) < threshold) {
          ev.stopPropagation();
          ev.preventDefault();
          break;
        }
      }
    }

    /**
     * reset the coordinates array
     */
    function resetCoordinates () {
      coordinates = [];
    }

    /**
     * remove the first coordinates set from the array
     */
    function popCoordinates () {
      coordinates.splice(0, 1);
    }

    /**
     * if it is an final touchend, we want to register it's place
     * @param {TouchEvent} ev
     */
    function registerCoordinates (ev) {
      // touchend is triggered on every releasing finger
      // changed touches always contain the removed touches on a touchend
      // the touches object might contain these also at some browsers (firefox os)
      // so touches - changedTouches will be 0 or lower, like -1, on the final touchend
      if(ev.touches.length - ev.changedTouches.length <= 0) {
        var touch = ev.changedTouches[0];
        coordinates.push([touch.clientX, touch.clientY]);

        setTimeout(popCoordinates, timeout);
      }
    }
  }
})(window, window.angular, window.Hammer);
<|endoftext|>


<commit_before>// --- Dependencies
import * as React from 'react'
import PropTypes from 'prop-types'

// --- Icons
import IconEdit from 'react-feather/dist/icons/edit-2'

/**
 * Component
 */

export default function MarkdownPageFooter({ group, isIndex, section, title }) {
  const baseUrl =
    'https://github.com/spree/spree/edit/master/guides/src/content'
  const edge = group
    .find(el => el.section === section)
    .edges.find(el => el.node.frontmatter.title === title)

  if (!edge) {
    return null
  }

  const pathname = edge.node.fields.slug.replace('.html', '')
  const url = `${baseUrl}${pathname}${isIndex ? 'index' : ''}.md`
  return (

      <IconEdit className="pointer dark-gray v-btm mr2 w1" />
      Edit this page on GitHub

  )
}

MarkdownPageFooter.propTypes = {
  section: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  group: PropTypes.array.isRequired,
  isIndex: PropTypes.bool.isRequired
}
<commit_msg>[Guides] Open "Edit this page on Github" link in tab/window<commit_after>// --- Dependencies
import * as React from 'react'
import PropTypes from 'prop-types'

// --- Icons
import IconEdit from 'react-feather/dist/icons/edit-2'

/**
 * Component
 */

export default function MarkdownPageFooter({ group, isIndex, section, title }) {
  const baseUrl =
    'https://github.com/spree/spree/edit/master/guides/src/content'
  const edge = group
    .find(el => el.section === section)
    .edges.find(el => el.node.frontmatter.title === title)

  if (!edge) {
    return null
  }

  const pathname = edge.node.fields.slug.replace('.html', '')
  const url = `${baseUrl}${pathname}${isIndex ? 'index' : ''}.md`
  return (

      <IconEdit className="pointer dark-gray v-btm mr2 w1" />
      Edit this page on GitHub

  )
}

MarkdownPageFooter.propTypes = {
  section: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  group: PropTypes.array.isRequired,
  isIndex: PropTypes.bool.isRequired
}
<|endoftext|>


<commit_before>// Load in our dependencies
var $ = require('jquery');
var async = require('async');
var expect = require('chai').expect;
var UAParser = require('ua-parser-js');
var applicationUtils = require('./utils/application');
var domUtils = require('./utils/dom');

// Start our tests
describe('An application with similarly failing images', function () {
  // Create our application
  applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_NOT_EQUAL);

  describe('when finding similarly failing images', function () {
    domUtils.dragOverElement({
      selector: '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]',
      startCoords: {x: 0, y: 0},
      endCoords: {x: 10, y: 10}
    });
    domUtils.click('[data-image-set="mock-img-not-equal"] ' +
      'button[data-action="find-similar-images"]');
    applicationUtils.screenshot('find-similar-matching');

    it('lists similarly failing images in results', function () {
      var resultsEl = this.containerEl.querySelector('.results');
      expect(resultsEl.textContent).to.not.contain('No similar images found');
      var similarImageSetEls = resultsEl.querySelectorAll('[data-similar-image-set]');
      expect(similarImageSetEls).to.have.length(2);
      expect(similarImageSetEls[0].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal');
      expect(similarImageSetEls[1].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal2');
    });

    it('marks current image set with keyword', function () {
      var resultsEl = this.containerEl.querySelector('.results');
      var similarImageSetEls = resultsEl.querySelectorAll('[data-similar-image-set]');
      expect(similarImageSetEls[0].textContent).to.contain(' (current set)');
    });
  });
});

describe('An application with no similarly failing images', function () {
  // Create our application
  applicationUtils.init(applicationUtils.IMAGE_SETS.DEFAULT);

  describe('when finding similarly failing images', function () {
    domUtils.dragOverElement({
      selector: '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]',
      startCoords: {x: 0, y: 0},
      endCoords: {x: 10, y: 10}
    });
    domUtils.click('[data-image-set="mock-img-not-equal"] ' +
      'button[data-action="find-similar-images"]');
    applicationUtils.screenshot('find-similar-no-matching');

    it('lists no similarly failing images in results', function () {
      var resultsEl = this.containerEl.querySelector('.results');
      expect(resultsEl.textContent).to.contain('No similar images found');
      var similarImageSetEls = resultsEl.querySelectorAll('[data-similar-image-set]');
      expect(similarImageSetEls).to.have.length(0);
    });
  });
});

// Define a performance test
// DEV: We skip PhantomJS as its performance results are inaccurate
var browser = new UAParser().getBrowser();
if (browser.name !== 'PhantomJS' && window.location.href.indexOf('/debug.html') === -1) {
  describe('An application with many similarly failing images', function () {
    // Create our application
    applicationUtils.init(applicationUtils.IMAGE_SETS.PERFORMANCE);

    describe('when finding similarly failing images', function () {
      domUtils.dragOverElement({
        selector: '[data-image-set="mock-img-not-equal1"] img[data-compare-type=diff]',
        startCoords: {x: 0, y: 0},
        endCoords: {x: 10, y: 10}
      });

      it('resolve them performantly', function (done) {
        // Find and prepare our button
        var that = this;
        var buttonEl = domUtils._findElement.call(this,
          '[data-image-set="mock-img-not-equal1"] button[data-action="find-similar-images"]');
        var $button = $(buttonEl);

        // Collect our samples
        // DEV: We were using `benchmark` but it caused our host system to lag
        //   Reference code: https://github.com/twolfson/multi-image-mergetool/blob/b36760d6054fc6a47e67b7d87fefeaf24152a6d7/test/browser/application-find-similar.js#L76-L98
        var samples = [];
        async.timesSeries(10, function collectSample (i, cb) {
          // Click our button and record sample
          var start = Date.now();
          $button.click();
          var end = Date.now();
          samples.push(end - start);

          // Continue to next action
          // DEV: We use `nextTick` to prevent sync/async zalgo
          process.nextTick(cb);
        }, function handleResult (err) {
          // If there was an error, callback with it
          if (err) {
            return done(err);
          }

          // Verify we have 50 matching images
          // DEV: This is a sanity check
          var similarImageSetEls = that.containerEl.querySelectorAll('.results [data-similar-image-set]');
          expect(similarImageSetEls).to.have.length(50);
          expect(similarImageSetEls[0].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal1');
          expect(similarImageSetEls[1].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal3');

          // Assert average/mean is good
          var total = samples.reduce(function sumSamples (a, b) {
            return a + b;
          }, 0);
          var mean = total / samples.length;
          expect(mean).to.be.at.least(100); // ms
          expect(mean).to.be.at.most(600); // ms

          // Complete our result
          done();
        });
      });
    });
  });
}
<commit_msg>Caught regression with new images and finding similar image sets<commit_after>// Load in our dependencies
var $ = require('jquery');
var async = require('async');
var expect = require('chai').expect;
var UAParser = require('ua-parser-js');
var applicationUtils = require('./utils/application');
var domUtils = require('./utils/dom');

// Start our tests
describe('An application with similarly failing images', function () {
  // Create our application
  applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_NOT_EQUAL);

  describe('when finding similarly failing images', function () {
    domUtils.dragOverElement({
      selector: '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]',
      startCoords: {x: 0, y: 0},
      endCoords: {x: 10, y: 10}
    });
    domUtils.click('[data-image-set="mock-img-not-equal"] ' +
      'button[data-action="find-similar-images"]');
    applicationUtils.screenshot('find-similar-matching');

    it('lists similarly failing images in results', function () {
      var resultsEl = this.containerEl.querySelector('.results');
      expect(resultsEl.textContent).to.not.contain('No similar images found');
      var similarImageSetEls = resultsEl.querySelectorAll('[data-similar-image-set]');
      expect(similarImageSetEls).to.have.length(2);
      expect(similarImageSetEls[0].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal');
      expect(similarImageSetEls[1].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal2');
    });

    it('marks current image set with keyword', function () {
      var resultsEl = this.containerEl.querySelector('.results');
      var similarImageSetEls = resultsEl.querySelectorAll('[data-similar-image-set]');
      expect(similarImageSetEls[0].textContent).to.contain(' (current set)');
    });
  });
});

describe('An application with no similarly failing images', function () {
  // Create our application
  applicationUtils.init(applicationUtils.IMAGE_SETS.DEFAULT);

  describe('when finding similarly failing images', function () {
    domUtils.dragOverElement({
      selector: '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]',
      startCoords: {x: 0, y: 0},
      endCoords: {x: 10, y: 10}
    });
    domUtils.click('[data-image-set="mock-img-not-equal"] ' +
      'button[data-action="find-similar-images"]');
    applicationUtils.screenshot('find-similar-no-matching');

    it('lists no similarly failing images in results', function () {
      var resultsEl = this.containerEl.querySelector('.results');
      expect(resultsEl.textContent).to.contain('No similar images found');
      var similarImageSetEls = resultsEl.querySelectorAll('[data-similar-image-set]');
      expect(similarImageSetEls).to.have.length(0);
    });
  });
});

describe.only('An application with new images', function () {
  // Create our application
  applicationUtils.init(applicationUtils.IMAGE_SETS.MULTIPLE_WITH_NEW);

  describe('when finding similarly failing images', function () {
    domUtils.dragOverElement({
      selector: '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]',
      startCoords: {x: 0, y: 0},
      endCoords: {x: 10, y: 10}
    });
    domUtils.click('[data-image-set="mock-img-not-equal"] ' +
      'button[data-action="find-similar-images"]');

    it('has no errors', function () {
      // An error would be thrown in `domUtils.click`
    });
  });
});

// Define a performance test
// DEV: We skip PhantomJS as its performance results are inaccurate
var browser = new UAParser().getBrowser();
if (browser.name !== 'PhantomJS' && window.location.href.indexOf('/debug.html') === -1) {
  describe('An application with many similarly failing images', function () {
    // Create our application
    applicationUtils.init(applicationUtils.IMAGE_SETS.PERFORMANCE);

    describe('when finding similarly failing images', function () {
      domUtils.dragOverElement({
        selector: '[data-image-set="mock-img-not-equal1"] img[data-compare-type=diff]',
        startCoords: {x: 0, y: 0},
        endCoords: {x: 10, y: 10}
      });

      it('resolve them performantly', function (done) {
        // Find and prepare our button
        var that = this;
        var buttonEl = domUtils._findElement.call(this,
          '[data-image-set="mock-img-not-equal1"] button[data-action="find-similar-images"]');
        var $button = $(buttonEl);

        // Collect our samples
        // DEV: We were using `benchmark` but it caused our host system to lag
        //   Reference code: https://github.com/twolfson/multi-image-mergetool/blob/b36760d6054fc6a47e67b7d87fefeaf24152a6d7/test/browser/application-find-similar.js#L76-L98
        var samples = [];
        async.timesSeries(10, function collectSample (i, cb) {
          // Click our button and record sample
          var start = Date.now();
          $button.click();
          var end = Date.now();
          samples.push(end - start);

          // Continue to next action
          // DEV: We use `nextTick` to prevent sync/async zalgo
          process.nextTick(cb);
        }, function handleResult (err) {
          // If there was an error, callback with it
          if (err) {
            return done(err);
          }

          // Verify we have 50 matching images
          // DEV: This is a sanity check
          var similarImageSetEls = that.containerEl.querySelectorAll('.results [data-similar-image-set]');
          expect(similarImageSetEls).to.have.length(50);
          expect(similarImageSetEls[0].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal1');
          expect(similarImageSetEls[1].getAttribute('data-similar-image-set')).to.equal('mock-img-not-equal3');

          // Assert average/mean is good
          var total = samples.reduce(function sumSamples (a, b) {
            return a + b;
          }, 0);
          var mean = total / samples.length;
          expect(mean).to.be.at.least(100); // ms
          expect(mean).to.be.at.most(600); // ms

          // Complete our result
          done();
        });
      });
    });
  });
}
<|endoftext|>


<commit_before>import { expect } from '../utility';
import {
  createSocialAccount,
  updateSocialAccount,
  deleteSocialAccount,
} from '../../../../src/cms/actions/socialAccounts';

import {
  CREATE_SOCIAL_ACCOUNT,
  UPDATE_SOCIAL_ACCOUNT,
  DELETE_SOCIAL_ACCOUNT
} from '../../../../src/cms/constants';

describe('socialAccount actions', () => {

  describe('createSocialAccount', () => {
    it('creates CREATE_SOCIAL_ACCOUNT', () => {
      const action = createSocialAccount();
      const expectedResponse = {
        type: CREATE_SOCIAL_ACCOUNT,
        payload: { socialAccount: {} }
      };
      expect(action).to.eql(expectedResponse);
    });
  });

  describe('updateSocialAccount', () => {
    it('creates UPDATE_SOCIAL_ACCOUNT and updates socialAccount', () => {
      const action = updateSocialAccount(1, { name: 'name' });
      const expectedResponse = {
        type: UPDATE_SOCIAL_ACCOUNT,
        payload: {
          sortRank: 1,
          socialAccount: {
            name: 'name'
          }
        }
      };
      expect(action).to.eql(expectedResponse);
    });
  });

  describe('deleteSocialAccount', () => {
    it('creates DELETE_SOCIAL_ACCOUNT and delete socialAccount', () => {
      const action = deleteSocialAccount(1);
      const expectedResponse = {
        type: DELETE_SOCIAL_ACCOUNT,
        payload: {
          sortRank: 1
        }
      };
      expect(action).to.eql(expectedResponse);
    });
  });

});
<commit_msg>modified cms/actions/social_accounts_test.js<commit_after>import { expect } from '../../helpers/utility';
import {
  fetchSocialAccounts,
  updateSocialAccount
} from 'cms/actions/socialAccounts';

import {
  FETCH_SOCIAL_ACCOUNTS,
  UPDATE_SOCIAL_ACCOUNT
} from 'shared/constants/actions';

describe('cms socialAccount actions', () => {

  describe('fetchSocialAccount', () => {
    it('creates FETCH_SOCIAL_ACCOUNT', () => {
      const action = fetchSocialAccounts({
        socialAccounts: [
        {
          accountType: 'twitter',
          url: 'http://twitter.com'
        }]
      });
      const expectedResponse = {
        type: FETCH_SOCIAL_ACCOUNTS,
        payload: { socialAccounts: [
          {
            accountType: 'twitter',
            url: 'http://twitter.com'
          }
        ]}
      };
      expect(action).to.eql(expectedResponse);
    });
  });

  describe('updateSocialAccount', () => {
    it('creates UPDATE_SOCIAL_ACCOUNT and updates socialAccount', () => {
      const action = updateSocialAccount(1, 'url');
      const expectedResponse = {
        type: UPDATE_SOCIAL_ACCOUNT,
        payload: {
          sortRank: 1,
          url: 'url'
        }
      };
      expect(action).to.eql(expectedResponse);
    });
  });

});
<|endoftext|>


<commit_before>/* global describe, it, before */

const Brave = require('../lib/brave')
const messages = require('../../js/constants/messages')
const {urlInput, urlBarSuggestions} = require('../lib/selectors')

describe.only('urlbarSuggestions', function () {
  function * setup (client) {
    yield client
      .waitUntilWindowLoaded()
      .waitForVisible('#window')
      .waitForVisible(urlInput)
  }

  Brave.beforeAll(this)
  before(function *() {
    this.page1Url = Brave.server.url('page1.html')
    yield setup(this.app.client)
    yield this.app.client.loadUrl(this.page1Url)
      .ipcSend(messages.SHORTCUT_NEW_FRAME)
      .waitForExist('.tab[data-frame-key="2"].active')
      .waitForElementFocus(urlInput)
  })

  it('navigates to a suggestion when clicked', function *() {
    yield this.app.client.ipcSend('shortcut-focus-url')
      .waitForElementFocus(urlInput)
      .setValue(urlInput, 'Page 1')
      .waitUntil(function () {
        return this.getValue(urlInput).then(val => val === 'Page 1')
      })
      .waitForExist(urlBarSuggestions)
      .click(urlBarSuggestions + ' li')
      .waitForExist('.tab[data-frame-key="1"].active')
  })

  it('navigates to a suggestion with keyboard', function *() {
    yield this.app.client.ipcSend(messages.SHORTCUT_NEW_FRAME)
      .waitForExist('.tab[data-frame-key="3"].active')
      .ipcSend('shortcut-focus-url')
      .waitForElementFocus(urlInput)
      .setValue(urlInput, 'Page 1')
      .waitUntil(function () {
        return this.getValue(urlInput).then(val => val === 'Page 1')
      })
      .waitForExist(urlBarSuggestions)
      .keys('Down arrow')
      .waitForExist(urlBarSuggestions + ' li.selected')
      .keys('Enter')
      .waitForExist('.tab[data-frame-key="1"].active')
  })
})
<commit_msg>Remove once for tests<commit_after>/* global describe, it, before */

const Brave = require('../lib/brave')
const messages = require('../../js/constants/messages')
const {urlInput, urlBarSuggestions} = require('../lib/selectors')

describe('urlbarSuggestions', function () {
  function * setup (client) {
    yield client
      .waitUntilWindowLoaded()
      .waitForVisible('#window')
      .waitForVisible(urlInput)
  }

  Brave.beforeAll(this)
  before(function *() {
    this.page1Url = Brave.server.url('page1.html')
    yield setup(this.app.client)
    yield this.app.client.loadUrl(this.page1Url)
      .ipcSend(messages.SHORTCUT_NEW_FRAME)
      .waitForExist('.tab[data-frame-key="2"].active')
      .waitForElementFocus(urlInput)
  })

  it('navigates to a suggestion when clicked', function *() {
    yield this.app.client.ipcSend('shortcut-focus-url')
      .waitForElementFocus(urlInput)
      .setValue(urlInput, 'Page 1')
      .waitUntil(function () {
        return this.getValue(urlInput).then(val => val === 'Page 1')
      })
      .waitForExist(urlBarSuggestions)
      .click(urlBarSuggestions + ' li')
      .waitForExist('.tab[data-frame-key="1"].active')
  })

  it('navigates to a suggestion with keyboard', function *() {
    yield this.app.client.ipcSend(messages.SHORTCUT_NEW_FRAME)
      .waitForExist('.tab[data-frame-key="3"].active')
      .ipcSend('shortcut-focus-url')
      .waitForElementFocus(urlInput)
      .setValue(urlInput, 'Page 1')
      .waitUntil(function () {
        return this.getValue(urlInput).then(val => val === 'Page 1')
      })
      .waitForExist(urlBarSuggestions)
      .keys('Down arrow')
      .waitForExist(urlBarSuggestions + ' li.selected')
      .keys('Enter')
      .waitForExist('.tab[data-frame-key="1"].active')
  })
})
<|endoftext|>


<commit_before>'use strict';

const sinon = require('sinon'),
    chai = require('chai'),
    sinonChai = require('sinon-chai'),
    expect = require('chai').expect;

chai.use(sinonChai);

describe('Order Controller', function () {

    let req, res, models, saveStub;

    beforeEach(function () {
        models = require('../../app/models/orderModel');
        saveStub = sinon.stub();
        req = {
            requestedURI: '/foo/bar',
            body: sinon.spy(),
            status: sinon.spy()
        };
        res = {
            status: sinon.spy(),
            send: sinon.spy(),
            location: sinon.spy()
        };
    });

    function setupOrderCtrl(sandbox, saveYieldValue) {
        saveStub.yields(saveYieldValue);
        const Order = sandbox.stub(models, 'Order');
        Order.returns({
            save: saveStub,
            _id: 123
        });
        const ctrl = require('../../app/controllers/orderController')(Order);
        return {
            controller: ctrl,
            order: Order
        }
        return ctrl;
    }

    describe('ordering a beverage', function () {

        it('should return status 201 when the order was successful', sinon.test(function (done) {
            const expected = 201;
            const mockOrderCtrl = setupOrderCtrl(this, null);
            mockOrderCtrl.controller.post(req, res);

            expect(res.status).to.have.been.calledWith(expected);
            done();
        }));

        it('should not send order back if it could not be saved', sinon.test(function (done) {
            const mockOrderCtrl = setupOrderCtrl(this, {});
            mockOrderCtrl.controller.post(req, res);

            res.send.called.should.be.false;
            done();

        }));

        it('should save an order', sinon.test(function (done) {
            const mockOrderCtrl = setupOrderCtrl(this, null);

            mockOrderCtrl.controller.post(req, res);

            sinon.assert.calledOnce(saveStub);
            done();
        }));

        it('should send http status 500 when an error occurs during save', sinon.test(function (done) {
            const expected = 500;
            const mockOrderCtrl = setupOrderCtrl(this, {});

            mockOrderCtrl.controller.post(req, res);

            expect(res.status).to.have.been.calledWith(expected);
            done();

        }));

        it('should set the location response with the entity id', sinon.test(function (done) {
            const expected = '/foo/bar/123';
            const mockOrderCtrl = setupOrderCtrl(this, null);

            mockOrderCtrl.controller.post(req, res);

            //process.nextTick(function () {
            expect(res.location).to.have.been.calledWith(expected);
            done();
            // })

        }));
    });
});
<commit_msg>clean up of tests<commit_after>'use strict';

const sinon = require('sinon'),
    chai = require('chai'),
    sinonChai = require('sinon-chai'),
    expect = require('chai').expect;
chai.use(sinonChai);

sinon.defaultConfig.useFakeTimers = false;

describe('Order Controller', function () {

    let req, res, models, saveStub;
    beforeEach(function () {
        models = require('../../app/models/orderModel');
        saveStub = sinon.stub();
        req = {
            requestedURI: '/foo/bar',
            body: sinon.spy(),
            status: sinon.spy()
        };
        res = {
            status: sinon.spy(),
            send: sinon.spy(),
            location: sinon.spy()
        };
    });

    function setupOrderCtrl(sandbox, saveYieldValue) {
        saveStub.yields(saveYieldValue);
        const Order = sandbox.stub(models, 'Order');
        Order.returns({
            save: saveStub,
            _id: 123
        });
        const ctrl = require('../../app/controllers/orderController')(Order);
        return {
            controller: ctrl,
            order: Order
        }
    }

    describe('ordering a beverage', function () {
        it('should return status 201 when the order was successful', sinon.test(function (done) {
            const expected = 201;
            const mockOrderCtrl = setupOrderCtrl(this, null);
            mockOrderCtrl.controller.post(req, res);

            expect(res.status).to.have.been.calledWith(expected);
            done();
        }));

        it('should not send order back if it could not be saved', sinon.test(function (done) {
            const mockOrderCtrl = setupOrderCtrl(this, {});
            mockOrderCtrl.controller.post(req, res);

            res.send.called.should.be.false;
            done();

        }));

        it('should save an order', sinon.test(function (done) {
            const mockOrderCtrl = setupOrderCtrl(this, null);

            mockOrderCtrl.controller.post(req, res);

            sinon.assert.calledOnce(saveStub);
            done();
        }));

        it('should send http status 500 when an error occurs during save', sinon.test(function (done) {
            const expected = 500;
            const mockOrderCtrl = setupOrderCtrl(this, {});

            mockOrderCtrl.controller.post(req, res);

            expect(res.status).to.have.been.calledWith(expected);
            done();

        }));

        it('should set the location response with the entity id', sinon.test(function (done) {
            const expected = '/foo/bar/123';
            const mockOrderCtrl = setupOrderCtrl(this, null);

            mockOrderCtrl.controller.post(req, res);

            //process.nextTick(function () {
            expect(res.location).to.have.been.calledWith(expected);
            done();
            // })
        }));
    });
});
<|endoftext|>


<commit_before>define(['jquery','components/slider/DeckSlider', 'components/InlineEditor', 'models/Deck', 'views/CardForm', 'utils/utils'], function($, DeckSlider, InlineEditor, Deck, CardForm, utils) {

var initModule = function() {
	var deck_slider = new DeckSlider($(".slider").first());
	var card_counter = {
		el: $("#counter"),
		slider: deck_slider,
		update: function() {
			var current = this.slider.getCurrentCardNum();
			var total = this.slider.getNumItems();
			this.el.html(current + " / " + total);
		}
	};
	var $allCards = $("#allCards");

	card_counter.update = $.proxy(card_counter.update, card_counter);

	deck_slider.bind("beforeslide", function(slider, card_id) {
		$allCards.find("[data-card-id]").removeClass("show").addClass("hide");
	});
	deck_slider.bind("slide", function(slider, card_id) {
		$allCards.find("[data-card-id="+card_id+"]").removeClass("hide").addClass("show");
	});
	deck_slider.bind("slide", card_counter.update);
	deck_slider.goToCurrent();
	card_counter.update();

	$('#review_mode').click(function(){
	    location.href=this.href+'?card_id='+deck_slider.getCurrentCardId();
	    return false;
	});
	$('#quiz_mode').click(function(){
	    location.href=this.href+'&card_id='+deck_slider.getCurrentCardId();
	    return false;
	});

	$('#addCard').click(function(){
	    $('#cardFormContainer').toggle();
	    $('#singleCardHolder').toggle();
	    return false;
	});

	$('#card_cancel').click(function(){
	    $('#cardFormContainer').hide();
	    $('#singleCardHolder').show();
	    return false;
	});

	$('#card_create').click(function(){
	        $('#cardForm').submit();
	        return false;
	});

	// fixes the reorganization of slider
	$("#initDeck").css("display","none");
	$("#holder").css("display","block");

	$('.reveal').click(function() {
		var $show = $(this).parent().next();
		if ($show.hasClass('show')){
			$show.removeClass('show');
			$show.addClass('hide');
			$(this).text('Reveal');
		}else{
			$show.removeClass('hide');
			$show.addClass('show');
			$(this).text('Hide');
		}
		return false;
	});

	$('#full_screen').click(function() {
	    var txt1 = 'Full Screen';
	    var txt2 = 'Exit Full Screen';
	    if ($("#full_screen").text() == txt1){
	        $("#wrapper").children().not("#singleCardHolder, .fullcardBtns").hide();
	        $("#appTitle, #navigation").hide();
	        $("#singleCardHolder").css('margin-left','8.5%');
	        $("#full_screen .control-text").text(txt2);
			$("#full").addClass("fa-compress").removeClass("fa-expand");
	    }
	    else{
	        $("#wrapper").children().not("#cardFormContainer, .fullcardBtns").show();
			$("#appTitle, #navigation").show();
	        $("#singleCardHolder").css('margin-left','0');
	        $("#full_screen .control-text").text(txt1);
			$("#full").addClass("fa-expand").removeClass("fa-compress");
	    }
	    return false;
	});

	// http://standardofnorms.wordpress.com/2012/04/08/shuffling-all-the-children-of-a-parent-element-in-javascript/
	$('#shuffle_cards').click(function() {
		deck_slider.shuffle();
		deck_slider.goToFirst();
	    return false;
	});

	$('#play_cards').click(function(){
	    var playText = 'Play';
	    var pauseText = 'Pause';
	    if ($("#play_cards .control-text").text() == playText){
			deck_slider.play(function() {
				$("#play_cards").click();
			});
	        $("#play_cards .control-text").text(pauseText);
			$("#play").removeClass('fa-play').addClass('fa-pause');
	    }
	    else{
			deck_slider.pause();
	        $("#play_cards .control-text").text(playText);
			$("#play").removeClass('fa-pause').addClass('fa-play');
	    }
	    return false;
	});

	utils.setupConfirm();

	setupEditableDeckTitle();
};

// Add ability to edit inline every element with data-editable=yes.
// Note: also requires data-editable-url (API endpoint)
// and data-editable-field (name of the field to POST to the API)
function setupEditableDeckTitle() {
	$("[data-editable]").each(function(index, el) {
		var $el = $(el);
		var editable = $el.data('editable') || 'no';
		var id = $el.data('editable-id') || '';
		if(editable !== 'yes') {
			return;
		}

		var editor = new InlineEditor($el, {
			edit: function(editor, value, settings) {
				var deck = new Deck({ id: id });
				return deck.rename(value);
			},
			success: function(data, textStatus, xhr) {
				var success = data.success;
				if(!success) {
					window.alert("Error saving: "+ data.errors[field]);
				}
				return success;
			},
			error: function(xhr, textStatus, errorThrown) {
				window.alert("Error saving: "+ errorThrown);
			}
		});
	});
}

	return {initModule:initModule};
});
<commit_msg>adding init check and method for setting flip value in localStorage<commit_after>define(['jquery','components/slider/DeckSlider', 'components/InlineEditor', 'models/Deck', 'views/CardForm', 'utils/utils'], function($, DeckSlider, InlineEditor, Deck, CardForm, utils) {

var initModule = function() {
	var deck_slider = new DeckSlider($(".slider").first());
	var card_counter = {
		el: $("#counter"),
		slider: deck_slider,
		update: function() {
			var current = this.slider.getCurrentCardNum();
			var total = this.slider.getNumItems();
			this.el.html(current + " / " + total);
		}
	};
	var $allCards = $("#allCards");

	card_counter.update = $.proxy(card_counter.update, card_counter);

	deck_slider.bind("beforeslide", function(slider, card_id) {
		$allCards.find("[data-card-id]").removeClass("show").addClass("hide");
	});
	deck_slider.bind("slide", function(slider, card_id) {
		$allCards.find("[data-card-id="+card_id+"]").removeClass("hide").addClass("show");
	});
	deck_slider.bind("slide", card_counter.update);
	deck_slider.goToCurrent();
	card_counter.update();

	$('#review_mode').click(function(){
	    location.href=this.href+'?card_id='+deck_slider.getCurrentCardId();
	    return false;
	});
	$('#quiz_mode').click(function(){
	    location.href=this.href+'&card_id='+deck_slider.getCurrentCardId();
	    return false;
	});

	$('#addCard').click(function(){
	    $('#cardFormContainer').toggle();
	    $('#singleCardHolder').toggle();
	    return false;
	});

	$('#card_cancel').click(function(){
	    $('#cardFormContainer').hide();
	    $('#singleCardHolder').show();
	    return false;
	});

	$('#card_create').click(function(){
	        $('#cardForm').submit();
	        return false;
	});

	// fixes the reorganization of slider
	$("#initDeck").css("display","none");
	$("#holder").css("display","block");

	$('.reveal').click(function() {
		var $show = $(this).parent().next();
		if ($show.hasClass('show')){
			$show.removeClass('show');
			$show.addClass('hide');
			$(this).text('Reveal');
		}else{
			$show.removeClass('hide');
			$show.addClass('show');
			$(this).text('Hide');
		}
		return false;
	});

	$('#full_screen').click(function() {
	    var txt1 = 'Full Screen';
	    var txt2 = 'Exit Full Screen';
	    if ($("#full_screen").text() == txt1){
	        $("#wrapper").children().not("#singleCardHolder, .fullcardBtns").hide();
	        $("#appTitle, #navigation").hide();
	        $("#singleCardHolder").css('margin-left','8.5%');
	        $("#full_screen .control-text").text(txt2);
			$("#full").addClass("fa-compress").removeClass("fa-expand");
	    }
	    else{
	        $("#wrapper").children().not("#cardFormContainer, .fullcardBtns").show();
			$("#appTitle, #navigation").show();
	        $("#singleCardHolder").css('margin-left','0');
	        $("#full_screen .control-text").text(txt1);
			$("#full").addClass("fa-expand").removeClass("fa-compress");
	    }
	    return false;
	});

	// http://standardofnorms.wordpress.com/2012/04/08/shuffling-all-the-children-of-a-parent-element-in-javascript/
	$('#shuffle_cards').click(function() {
		deck_slider.shuffle();
		deck_slider.goToFirst();
	    return false;
	});

	$('#play_cards').click(function(){
	    var playText = 'Play';
	    var pauseText = 'Pause';
	    if ($("#play_cards .control-text").text() == playText){
			deck_slider.play(function() {
				$("#play_cards").click();
			});
	        $("#play_cards .control-text").text(pauseText);
			$("#play").removeClass('fa-play').addClass('fa-pause');
	    }
	    else{
			deck_slider.pause();
	        $("#play_cards .control-text").text(playText);
			$("#play").removeClass('fa-pause').addClass('fa-play');
	    }
	    return false;
	});

	// initialize flip-mode
	if(localStorage['flip_mode'] === true){
		$('#flip_mode').addClass('down');
	}
	$('#flip_mode').click(function(){
		if(localStorage['flip_mode']){
			flipMode(false);
		} else {
			flipMode(true);
		}
	});

	utils.setupConfirm();

	setupEditableDeckTitle();
};

// Add ability to edit inline every element with data-editable=yes.
// Note: also requires data-editable-url (API endpoint)
// and data-editable-field (name of the field to POST to the API)
function setupEditableDeckTitle() {
	$("[data-editable]").each(function(index, el) {
		var $el = $(el);
		var editable = $el.data('editable') || 'no';
		var id = $el.data('editable-id') || '';
		if(editable !== 'yes') {
			return;
		}

		var editor = new InlineEditor($el, {
			edit: function(editor, value, settings) {
				var deck = new Deck({ id: id });
				return deck.rename(value);
			},
			success: function(data, textStatus, xhr) {
				var success = data.success;
				if(!success) {
					window.alert("Error saving: "+ data.errors[field]);
				}
				return success;
			},
			error: function(xhr, textStatus, errorThrown) {
				window.alert("Error saving: "+ errorThrown);
			}
		});
	});
}

var flipMode = function(flip){
	if(flip){
		$('#flip_mode').addClass('down');
		localStorage['flip_mode'] = true;
	} else {
		$('#flip_mode').removeClass('down');
		localStorage.removeItem('flip_mode');
	}
}

	return {initModule:initModule};
});
<|endoftext|>


<commit_before>var app = angular.module('app');

app.directive('project', function() {
  return {
    restrict: 'E',
    //scope: true,
    scope: {
      createDependency: '=',
      tree: '=',
      selectedNode: '=',
      colors: '=',
      dependency: '=',
      show: '='
    },
    template: '
' +
              '  <button class="btn btn-default" ng-click="btnAddRowClicked()"> Add row</button>' +
              '  <button class="btn btn-default" ng-click="btnRedrawClicked()">Redraw</button>' +
              '  <svg></svg>' +
              '
',
    link: function(scope, element, attrs) {
      var $scope = scope;

      var firstCommit = null;
      var selectedPath = null;
      $scope.selectedNode = null;

      var lineFunction = d3.svg.line()
        .x(function(d) { return d.x; })
        .y(function(d) { return d.y; })
        .interpolate("linear");

      $scope.btnRedrawClicked = function() {
        $scope.drawTree();
      };

      $scope.btnAddRowClicked = function() {
        $scope.tree.push({id: $scope.tree.length.toString(), parents: [], column: 0, parentsPaths: {}, color: '#5aa1be'});
        $scope.drawTree();
      };

      $scope.drawTree = function() {
        if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
          $scope.$apply();
        }
        var xGap = 30;
        var yGap = 30;
        var cols = 10;
        var radius = 8;

        var svg = d3.select(element.find('svg')[0])
          .style('border', '1px solid gray')
          .attr('width', '100%')
          .attr('height', '300px');
        svg.selectAll('*').remove();

        var linesGroup = svg.append('g');
        var lineGroup = linesGroup.selectAll('lines')
          .data($scope.tree)
          .enter()
          .append('g');

        lineGroup
          .append('path')
          .attr('d', function(path, idx) {
            var d = [];
            d.push({x: 0, y: idx * yGap + yGap});
            d.push({x: $(svg[0][0]).width(), y: idx * yGap + yGap});
            return lineFunction(d)
          })
          .attr('stroke-width', 1)
          .attr('fill', 'none')
          .attr('stroke', '#aaa')
          .on('click', function(a, row, c) {
            $scope.tree.splice(row, 1);
            $scope.drawTree();
          });

          lineGroup.selectAll('dummyCircle')
            .data(function(d, i) { return _.range(cols); })
            .enter()
            .append('circle')
              .attr('r', 5)
              .attr('stroke', '#ddd')
              .attr('fill', '#ddd')
              .attr('cx', function(c) { return c * xGap + xGap; })
              .attr('cy', function(c, i, a) { return a * yGap + yGap })
              .on('mouseenter', function() { d3.select(this).attr('fill', '#aaa'); })
              .on('mouseleave', function() { d3.select(this).attr('fill', '#ddd'); })
              .on('mousedown', function() {})
              .on('mouseup', function(item, columnIndex, rowIndex) {
                if (selectedPath) {
                  console.log(selectedPath, columnIndex, rowIndex);
                  addPathNode(selectedPath[1], columnIndex, rowIndex);
                  console.log(selectedPath[1]);
                  selectedPath = null;
                  $scope.drawTree();
                }
              })
              .on('click', function(a,b,c) {
                $scope.tree[c].column = a;
                $scope.drawTree();
              });

          var addPathNode = function(path, x, y) {
            if (y == path.path[0][1] && x > path.path[0][0]) {
              path.path.splice(1, 0, [x, y, 2]);
              return;
            }
            for (var i = 0; i < path.path.length; i++) {
              var pathItem = path.path[i];
              if (y > pathItem[1]) {
                continue;
              }
              path.path.splice(i, 0, [x, y, 1]);
              break;
            }
          };

          var nodesGroup = svg.append('g').attr('class', 'nodes');
          var nodeGroup = nodesGroup.selectAll('nodes')
            .data($scope.tree)
            .enter()
            .append('g')
            .attr('class', 'node');

          // Each paths
          var pathsGroup = nodeGroup
            .append('g')
            .attr('class', 'paths');
          pathsGroup
            .selectAll('g')
            .data(function(node) {
              return _.map(node.parentsPaths, function(pathItem, parentId) { return [node, pathItem, parentId]; });
            })
            .enter()
            .append('path')
              .attr('d', function(item) {
                var node = item[0];
                var path = item[1];
                var parentId = item[2];
                var p = $scope.tree[parentId];
                var result = [];
                _.each(path.path, function(item) {
                  var point = {
                    x: item[0] * xGap + xGap,
                    y: item[1] * yGap + yGap
                  };
                  if (item[2] == 2) {
                    point.y += 6;
                  }
                  if (item[2] == 1) {
                    point.y -= 6;
                  }
                  result.push(point);
                });
                return lineFunction(result)
              })
              .attr('stroke-width', 3)
              .attr('fill', 'none')
              .attr('stroke', function(item) {
                return item[1].color;
              })
              .on('mousedown', function(item) {
                selectedPath = item;
              })
              .on('click', function(item) {
                var node = item[0];
                var idx = _.indexOf(node.parents, item[1]);
                $scope.tree[node.id].parents.splice(idx, 1);
                $scope.drawTree();
              });

          var addPath = function(rowIndex) {
            var firstNode = $scope.tree[firstCommit];
            var lastNode = $scope.tree[rowIndex];
            $scope.tree[firstCommit].parents.push(rowIndex.toString());
            $scope.tree[firstCommit].parents = _.sortBy($scope.tree[firstCommit].parents);
            $scope.tree[firstCommit].parentsPaths[rowIndex.toString()] = {
              path: [
                [firstNode.column, parseInt(firstNode.id), 0],
                [lastNode.column, parseInt(lastNode.id), 0]],
              color: '#5aa1be'};
            firstCommit = null;
            $scope.drawTree();
          };

          // Each nodes
          nodeGroup.append('circle')
            .attr('r', radius)
            .attr('fill', function(node) { return node.color; })
            .attr('stroke', '#000')
            .attr('cx', function(node) {
              return node.column * xGap + xGap;
            })
            .attr('cy', function(node, idx) { return idx * yGap + yGap })
            .on('mouseenter', function() { d3.select(this).attr('fill', '#f00'); })
            .on('mouseleave', function(node) { d3.select(this).attr('fill', node.color); })
            .on('mousedown', function(node, rowIndex) {
              firstCommit = rowIndex;
            })
            .on('mouseup', function(node, rowIndex) {
              if (firstCommit == rowIndex) {
                return;
              }
              if (firstCommit > rowIndex) {
                firstCommit = [rowIndex, rowIndex = firstCommit][0]; // swap vars
              }
              if (!_.includes($scope.tree[firstCommit].parents, rowIndex)) {
                addPath(rowIndex);
              }
            })
            .on('click', function(node) {
              $scope.selectedNode = node;
              $scope.$apply();
            });

      };

      $scope.$watchCollection('tree', function(newValue, oldValue) {
        $scope.drawTree();
      });

    }
  }
});

app.directive('focusMe', function($timeout) {
  return {
    scope: { trigger: '@focusMe' },
    link: function(scope, element, attr) {
      var predicate = attr.focusMe || true;
      switch (predicate) {
        case "true": case "yes": case "1":
          predicate = true;
          break;
        case "false": case "no": case "0": case null:
          predicate = false;
          break;
        default:
          predicate = Boolean(predicate);
          break;
      }
      if (!predicate) {
        return;
      }
      scope.$watch('trigger', function(value) {
        $timeout(function() {
          element[0].focus();
        });
      });
    }
  };
});

app.directive('ngEnter', function() {
  return function(scope, element, attrs) {
    element.bind("keydown keypress", function(event) {
      if(event.which === 13) {
        scope.$apply(function(){
          scope.$eval(attrs.ngEnter, {'event': event});
        });
        event.preventDefault();
      }
    });
  };
});
<commit_msg>Deep watch the structure and redraw when changed<commit_after>var app = angular.module('app');

app.directive('project', function() {
  return {
    restrict: 'E',
    //scope: true,
    scope: {
      createDependency: '=',
      tree: '=',
      selectedNode: '=',
      colors: '=',
      dependency: '=',
      show: '='
    },
    template: '
' +
              '  <button class="btn btn-default" ng-click="btnAddRowClicked()"> Add row</button>' +
              '  <button class="btn btn-default" ng-click="btnRedrawClicked()">Redraw</button>' +
              '  <svg></svg>' +
              '
',
    link: function(scope, element, attrs) {
      var $scope = scope;

      var firstCommit = null;
      var selectedPath = null;
      $scope.selectedNode = null;

      var lineFunction = d3.svg.line()
        .x(function(d) { return d.x; })
        .y(function(d) { return d.y; })
        .interpolate("linear");

      $scope.btnRedrawClicked = function() {
        $scope.drawTree();
      };

      $scope.btnAddRowClicked = function() {
        $scope.tree.push({id: $scope.tree.length.toString(), parents: [], column: 0, parentsPaths: {}, color: '#5aa1be'});
        $scope.drawTree();
      };

      $scope.drawTree = function() {
        if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
          $scope.$apply();
        }
        var xGap = 30;
        var yGap = 30;
        var cols = 10;
        var radius = 8;

        var svg = d3.select(element.find('svg')[0])
          .style('border', '1px solid gray')
          .attr('width', '100%')
          .attr('height', '300px');
        svg.selectAll('*').remove();

        var linesGroup = svg.append('g');
        var lineGroup = linesGroup.selectAll('lines')
          .data($scope.tree)
          .enter()
          .append('g');

        lineGroup
          .append('path')
          .attr('d', function(path, idx) {
            var d = [];
            d.push({x: 0, y: idx * yGap + yGap});
            d.push({x: $(svg[0][0]).width(), y: idx * yGap + yGap});
            return lineFunction(d)
          })
          .attr('stroke-width', 1)
          .attr('fill', 'none')
          .attr('stroke', '#aaa')
          .on('click', function(a, row, c) {
            $scope.tree.splice(row, 1);
            $scope.drawTree();
          });

          lineGroup.selectAll('dummyCircle')
            .data(function(d, i) { return _.range(cols); })
            .enter()
            .append('circle')
              .attr('r', 5)
              .attr('stroke', '#ddd')
              .attr('fill', '#ddd')
              .attr('cx', function(c) { return c * xGap + xGap; })
              .attr('cy', function(c, i, a) { return a * yGap + yGap })
              .on('mouseenter', function() { d3.select(this).attr('fill', '#aaa'); })
              .on('mouseleave', function() { d3.select(this).attr('fill', '#ddd'); })
              .on('mousedown', function() {})
              .on('mouseup', function(item, columnIndex, rowIndex) {
                if (selectedPath) {
                  console.log(selectedPath, columnIndex, rowIndex);
                  addPathNode(selectedPath[1], columnIndex, rowIndex);
                  console.log(selectedPath[1]);
                  selectedPath = null;
                  $scope.drawTree();
                }
              })
              .on('click', function(a,b,c) {
                $scope.tree[c].column = a;
                $scope.drawTree();
              });

          var addPathNode = function(path, x, y) {
            if (y == path.path[0][1] && x > path.path[0][0]) {
              path.path.splice(1, 0, [x, y, 2]);
              return;
            }
            for (var i = 0; i < path.path.length; i++) {
              var pathItem = path.path[i];
              if (y > pathItem[1]) {
                continue;
              }
              path.path.splice(i, 0, [x, y, 1]);
              break;
            }
          };

          var nodesGroup = svg.append('g').attr('class', 'nodes');
          var nodeGroup = nodesGroup.selectAll('nodes')
            .data($scope.tree)
            .enter()
            .append('g')
            .attr('class', 'node');

          // Each paths
          var pathsGroup = nodeGroup
            .append('g')
            .attr('class', 'paths');
          pathsGroup
            .selectAll('g')
            .data(function(node) {
              return _.map(node.parentsPaths, function(pathItem, parentId) { return [node, pathItem, parentId]; });
            })
            .enter()
            .append('path')
              .attr('d', function(item) {
                var node = item[0];
                var path = item[1];
                var parentId = item[2];
                var p = $scope.tree[parentId];
                var result = [];
                _.each(path.path, function(item) {
                  var point = {
                    x: item[0] * xGap + xGap,
                    y: item[1] * yGap + yGap
                  };
                  if (item[2] == 2) {
                    point.y += 6;
                  }
                  if (item[2] == 1) {
                    point.y -= 6;
                  }
                  result.push(point);
                });
                return lineFunction(result)
              })
              .attr('stroke-width', 3)
              .attr('fill', 'none')
              .attr('stroke', function(item) {
                return item[1].color;
              })
              .on('mousedown', function(item) {
                selectedPath = item;
              })
              .on('click', function(item) {
                var node = item[0];
                var idx = _.indexOf(node.parents, item[1]);
                $scope.tree[node.id].parents.splice(idx, 1);
                $scope.drawTree();
              });

          var addPath = function(rowIndex) {
            var firstNode = $scope.tree[firstCommit];
            var lastNode = $scope.tree[rowIndex];
            $scope.tree[firstCommit].parents.push(rowIndex.toString());
            $scope.tree[firstCommit].parents = _.sortBy($scope.tree[firstCommit].parents);
            $scope.tree[firstCommit].parentsPaths[rowIndex.toString()] = {
              path: [
                [firstNode.column, parseInt(firstNode.id), 0],
                [lastNode.column, parseInt(lastNode.id), 0]],
              color: '#5aa1be'};
            firstCommit = null;
            $scope.drawTree();
          };

          // Each nodes
          nodeGroup.append('circle')
            .attr('r', radius)
            .attr('fill', function(node) { return node.color; })
            .attr('stroke', '#000')
            .attr('cx', function(node) {
              return node.column * xGap + xGap;
            })
            .attr('cy', function(node, idx) { return idx * yGap + yGap })
            .on('mouseenter', function() { d3.select(this).attr('fill', '#f00'); })
            .on('mouseleave', function(node) { d3.select(this).attr('fill', node.color); })
            .on('mousedown', function(node, rowIndex) {
              firstCommit = rowIndex;
            })
            .on('mouseup', function(node, rowIndex) {
              if (firstCommit == rowIndex) {
                return;
              }
              if (firstCommit > rowIndex) {
                firstCommit = [rowIndex, rowIndex = firstCommit][0]; // swap vars
              }
              if (!_.includes($scope.tree[firstCommit].parents, rowIndex)) {
                addPath(rowIndex);
              }
            })
            .on('click', function(node) {
              $scope.selectedNode = node;
              $scope.$apply();
            });

      };

      $scope.$watch('tree', function(newValue, oldValue) {
        $scope.drawTree();
      }, true);

    }
  }
});

app.directive('focusMe', function($timeout) {
  return {
    scope: { trigger: '@focusMe' },
    link: function(scope, element, attr) {
      var predicate = attr.focusMe || true;
      switch (predicate) {
        case "true": case "yes": case "1":
          predicate = true;
          break;
        case "false": case "no": case "0": case null:
          predicate = false;
          break;
        default:
          predicate = Boolean(predicate);
          break;
      }
      if (!predicate) {
        return;
      }
      scope.$watch('trigger', function(value) {
        $timeout(function() {
          element[0].focus();
        });
      });
    }
  };
});

app.directive('ngEnter', function() {
  return function(scope, element, attrs) {
    element.bind("keydown keypress", function(event) {
      if(event.which === 13) {
        scope.$apply(function(){
          scope.$eval(attrs.ngEnter, {'event': event});
        });
        event.preventDefault();
      }
    });
  };
});
<|endoftext|>


<commit_before>$(document).ready(function() {
    var checked_res_str=$("#checked_res_div").text();
    var checked_res_array=checked_res_str.split(",");
    for (var i = 0; i < checked_res_array.length; i++) {
        var checked_res_item=checked_res_array[i];
        $("input[value='"+checked_res_item+"']").attr("checked","true");
    }
    var get_icon_input_element = function() {
        return $('#resourceSpecificTab #div_id_url');
    };
    if ($('#tool-icon-preview').length == 0) {
        get_icon_input_element().after('Preview
' +
            '');
    }
    get_icon_input_element().keyup(function() {
        $('#tool-icon-preview').attr('src', $('#resourceSpecificTab #id_url').val());
    })
});
<commit_msg>Added comments; automatically show icon preview if database entry exists<commit_after>$(document).ready(function() {
    var checked_res_str=$("#checked_res_div").text();
    var checked_res_array=checked_res_str.split(",");
    for (var i = 0; i < checked_res_array.length; i++) {
        var checked_res_item=checked_res_array[i];
        $("input[value='"+checked_res_item+"']").attr("checked","true");
    }
    // Getter for icon_url input element
    var get_icon_input_element = function() {
        return $('#resourceSpecificTab #div_id_url');
    };
    // If the "Preview" elements don't exist, add them
    if ($('#tool-icon-preview').length == 0) {
        get_icon_input_element().after('Preview
' +
            '');
    }
    // Set a key-up event that changes the preview picture each time the icon_url input is changed
    get_icon_input_element().keyup(function() {
        $('#tool-icon-preview').attr('src', $('#resourceSpecificTab #id_url').val());
    });
    // When the page first loads, if there is a url already stored in the database, show the preview of the image
    if ($('#resourceSpecificTab #id_url').val() != "") {
        $('#tool-icon-preview').attr('src', $('#resourceSpecificTab #id_url').val());
    }
});
<|endoftext|>


<commit_before>var host = 'http://hydro.weekendhack.it'; // TODO: Move to conf
var opts = {path: '/dashboard/socket.io'}; // TODO: Move to conf

var app = angular.module("dashboardApp", ['btford.socket-io', 'ngJustGage', 'ngRoute'])
  .factory('socket', function (socketFactory) {
    return socketFactory({
      ioSocket: io.connect(host, opts)
    });
  })
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/dashboard.html'
      })
      .when('/schedule', {
        templateUrl: 'views/schedule.html',
        controller: 'ScheduleCtrl'
      })
      .when('/webcam', {
        templateUrl: 'views/webcam.html'
      })
      .when('/timelapse', {
        templateUrl: 'views/timelapse.html'
      })
      .otherwise({
        redirectTo: '/'
      });
  }]);
<commit_msg>injection minification error<commit_after>var host = 'http://hydro.weekendhack.it'; // TODO: Move to conf
var opts = {path: '/dashboard/socket.io'}; // TODO: Move to conf

var app = angular.module("dashboardApp", ['btford.socket-io', 'ngJustGage', 'ngRoute'])
  .factory('socket', ['socketFactory',function (socketFactory) {
    return socketFactory({
      ioSocket: io.connect(host, opts)
    });
  }])
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/dashboard.html'
      })
      .when('/schedule', {
        templateUrl: 'views/schedule.html',
        controller: 'ScheduleCtrl'
      })
      .when('/webcam', {
        templateUrl: 'views/webcam.html'
      })
      .when('/timelapse', {
        templateUrl: 'views/timelapse.html'
      })
      .otherwise({
        redirectTo: '/'
      });
  }]);
<|endoftext|>


<commit_before>import path from 'path';
import SeleniumHelper from '../helpers/selenium-helper';

const {
    clickText,
    clickXpath,
    findByXpath,
    getDriver,
    getLogs,
    loadUri
} = new SeleniumHelper();

const uri = path.resolve(__dirname, '../../build/index.html');

let driver;

describe('Loading scratch gui', () => {
    beforeAll(() => {
        driver = getDriver();
    });

    afterAll(async () => {
        await driver.quit();
    });

    test('The "Not Now" button sends you to scratch', async () => {
        await loadUri(uri);
        await clickText('Not Now');
        const currentUrl = await driver.getCurrentUrl();
        await expect(currentUrl).toEqual('https://scratch.mit.edu/');
    });

    describe('Loading projects by ID', () => {

        test('Load 2.0 project using import modal', async () => {
            await loadUri(uri);
            await clickText('View 2.0 Project');
            const el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
            const projectId = '96708228';
            await el.sendKeys(`scratch.mit.edu/projects/${projectId}`);
            await clickXpath('//button[@title="viewproject"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Invalid url when loading project through modal lets you try again', async () => {
            await loadUri(uri);
            await clickText('View 2.0 Project');
            let el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
            await el.sendKeys('thisisnotaurl');
            await clickXpath('//button[@title="viewproject"]');
            el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
            await el.clear();
            await el.sendKeys('scratch.mit.edu/projects/96708228');
            await clickXpath('//button[@title="viewproject"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Load a project by ID directly through url', async () => {
            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//button[@title="tryit"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Load a project by ID (fullscreen)', async () => {
            const prevSize = driver.manage()
                .window()
                .getSize();
            await new Promise(resolve => setTimeout(resolve, 2000));
            driver.manage()
                .window()
                .setSize(1920, 1080);
            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//button[@title="tryit"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Full Screen Control"]');
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            prevSize.then(value => {
                driver.manage()
                    .window()
                    .setSize(value.width, value.height);
            });
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });
    });
});
<commit_msg>Quit and restart driver to hit # URL because hashchange behavior changed<commit_after>import path from 'path';
import SeleniumHelper from '../helpers/selenium-helper';

const {
    clickText,
    clickXpath,
    findByXpath,
    getDriver,
    getLogs,
    loadUri
} = new SeleniumHelper();

const uri = path.resolve(__dirname, '../../build/index.html');

let driver;

describe('Loading scratch gui', () => {
    beforeAll(() => {
        driver = getDriver();
    });

    afterAll(async () => {
        await driver.quit();
    });

    test('The "Not Now" button sends you to scratch', async () => {
        await loadUri(uri);
        await clickText('Not Now');
        const currentUrl = await driver.getCurrentUrl();
        await expect(currentUrl).toEqual('https://scratch.mit.edu/');
    });

    describe('Loading projects by ID', () => {

        test('Load 2.0 project using import modal', async () => {
            await loadUri(uri);
            await clickText('View 2.0 Project');
            const el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
            const projectId = '96708228';
            await el.sendKeys(`scratch.mit.edu/projects/${projectId}`);
            await clickXpath('//button[@title="viewproject"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Invalid url when loading project through modal lets you try again', async () => {
            await loadUri(uri);
            await clickText('View 2.0 Project');
            let el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
            await el.sendKeys('thisisnotaurl');
            await clickXpath('//button[@title="viewproject"]');
            el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
            await el.clear();
            await el.sendKeys('scratch.mit.edu/projects/96708228');
            await clickXpath('//button[@title="viewproject"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Load a project by ID directly through url', async () => {
            await driver.quit(); // Reset driver to test hitting # url directly
            driver = getDriver();

            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//button[@title="tryit"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });

        test('Load a project by ID (fullscreen)', async () => {
            await driver.quit(); // Reset driver to test hitting # url directly
            driver = getDriver();

            const prevSize = driver.manage()
                .window()
                .getSize();
            await new Promise(resolve => setTimeout(resolve, 2000));
            driver.manage()
                .window()
                .setSize(1920, 1080);
            const projectId = '96708228';
            await loadUri(`${uri}#${projectId}`);
            await clickXpath('//button[@title="tryit"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Full Screen Control"]');
            await clickXpath('//img[@title="Go"]');
            await new Promise(resolve => setTimeout(resolve, 2000));
            await clickXpath('//img[@title="Stop"]');
            prevSize.then(value => {
                driver.manage()
                    .window()
                    .setSize(value.width, value.height);
            });
            const logs = await getLogs();
            await expect(logs).toEqual([]);
        });
    });
});
<|endoftext|>