<commit_before>'use strict';

var assign = require('object-assign')

var PLACEHOLDER = '_'

var DIGIT = '#'

function isFormatCharacter(char) {
  return char === DIGIT
}

function isValidCharacter(char, format) {
  if (format == DIGIT) { return /^\d$/.test(char) }
  throw new Error('Unknown format character: ' + format)
}

/**
 * @param {Array.<string>} value
 * @param {Array.<string>} pattern
 * @return {Array.<string>}
 */
function formatValueToPattern(value, pattern) {
  var valueBuffer = new Array(pattern.length)
  var valueIndex = 0
  for (var i = 0, l = pattern.length; i < l ; i++) {
    if (isFormatCharacter(pattern[i])) {
      valueBuffer[i] = value.length > valueIndex && isValidCharacter(value[valueIndex], pattern[i])
                       ? value[valueIndex]
                       : PLACEHOLDER
      valueIndex++
    }
    else {
      valueBuffer[i] = pattern[i]
      // Also allow the value to contain static values from the pattern by
      // advancing its index.
      if (value.length > valueIndex && value[valueIndex] === pattern[i]) {
        valueIndex++
      }
    }
  }
  return valueBuffer
}

function InputMask(options) {
  if (!(this instanceof InputMask)) { return new InputMask(options) }

  options = assign({
    pattern: null,
    selection: {start: 0, end: 0},
    value: ''
  }, options)

  if (options.pattern == null) {
    throw new Error('InputMask: you must provide a pattern.')
  }

  this.setPattern(options.pattern)
  this.setValue(options.value)
  this.setSelection(options.selection)
}

/**
 * Applies a single character of input based on the current selection.
 * @param {string} input a single character of input.
 * @return {boolean} true if a change has been made to value or selection as a
 *   result of the input, false otherwise.
 */
InputMask.prototype.input = function input(input) {
  // Ignore additional input if the cursor's at the end of the pattern
  if (this.selection.start === this.selection.end &&
      this.selection.start === this.pattern.length) {
    return false
  }

  var format = this.pattern[this.selection.start]

  // Bail out or add the character to input
  if (isFormatCharacter(format)) {
    if (!isValidCharacter(input, format)) {
      return false
    }
    this.value[this.selection.start] = input
  }
  else {
    // The user must have placed the cursor prior to, or began a selection with,
    // a static part of the pattern, so skip over it.
    this.value[this.selection.start] = format
  }

  // If multiple chatacters were selected, blank the remainder out based on the
  // pattern.
  var end = this.selection.end - 1
  while (end > this.selection.start) {
    format = this.pattern[end]
    this.value[end] = isFormatCharacter(format) ? PLACEHOLDER : format
    end--
  }

  // Advance the cursor to the next character
  this.selection.start++
  this.selection.end = this.selection.start

  // Skip over any subsequent static characters
  while (this.pattern.length > this.selection.start &&
         !isFormatCharacter(this.pattern[this.selection.start])) {
    this.value[this.selection.start] = this.pattern[this.selection.start]
    this.selection.start++
    this.selection.end++
  }

  return true
}

/**
 * Attempts to delete from the value based on the current cursor position or
 * selection.
 * @return {boolean} true if the value or selection changed as the result of
 *   backspacing, false otherwise.
 */
InputMask.prototype.backspace = function backspace() {
  // If the cursor is at the start there's nothing to do
  if (this.selection.start === 0 && this.selection.end === 0) {
    return false
  }

  var format

  // No range selected - work on the character preceding the cursor
  if (this.selection.start === this.selection.end) {
    format = this.pattern[this.selection.start - 1]
    this.value[this.selection.start - 1] = isFormatCharacter(format) ? PLACEHOLDER : format
    this.selection.start--
    this.selection.end--
  }
  // Range selected - delete characters and leave the cursor at the start of the selection
  else {
    var end = this.selection.end - 1
    while (end >= this.selection.start) {
      format = this.pattern[end]
      this.value[end] = isFormatCharacter(format) ? PLACEHOLDER : format
      end--
    }
    this.selection.end = this.selection.start
  }

  return true
}

/**
 * Attempts to paste input at the current cursor position or over the top of the
 * current selection.
 * Invalid content at any position will cause the paste to be rejected, and it
 * may contain static parts of the mask's pattern.
 * @param {string} input
 * @return {boolean} true if the paste was successful, false otherwise.
 */
InputMask.prototype.paste = function paste(input) {
  var initialValue = this.value.slice()
  var initialSelection = assign({}, this.selection)
  for (var i = 0, l = input.length; i < l; i++) {
    var valid = this.input(input.charAt(i))
    // Allow static parts of the pattern to appear in pasted input
    if (!valid) {
      if (this.selection.start > 0) {
        var format = this.pattern[this.selection.start - 1]
        if (!isFormatCharacter(format) && input.charAt(i) === format) {
          continue
        }
      }
      this.value = initialValue
      this.selection = initialSelection
      return false
    }
  }
  return true
}

InputMask.prototype.setPattern = function setPattern(pattern) {
  var patternChars = pattern.split('')
  var firstEditableIndex = null
  var lastEditableIndex = null
  for (var i = 0, l = patternChars.length; i < l; i++) {
    if (isFormatCharacter(patternChars[i])) {
      if (firstEditableIndex === null) {
        firstEditableIndex = i
      }
      lastEditableIndex = i
    }
  }
  if (firstEditableIndex === null && lastEditableIndex === null) {
    throw new Error(
      'InputMask: pattern "' + pattern + '" does not contain any editable characters.'
    )
  }
  this.pattern = patternChars
  this._firstEditableIndex = firstEditableIndex
  this._lastEditableIndex = lastEditableIndex
}

InputMask.prototype.setSelection = function setSelection(selection) {
  this.selection = assign({}, selection)
  if (this.selection.start === this.selection.end) {
    if (this.selection.start < this._firstEditableIndex) {
      this.selection.start = this.selection.end = this._firstEditableIndex
      return true
    }
    if (this.selection.end > this._lastEditableIndex + 1) {
      this.selection.start = this.selection.end = this._lastEditableIndex + 1
      return true
    }
  }
  return false
}

InputMask.prototype.getValue = function getValue() {
  return this.value.join('')
}

InputMask.prototype.setValue = function setValue(value) {
  this.value = formatValueToPattern(value.split(''), this.pattern)
}

InputMask.formatValueToPattern = formatValueToPattern

module.exports = InputMask<commit_msg>setPattern also takes a value, resetting the value to blank by default<commit_after>'use strict';

var assign = require('object-assign')

var PLACEHOLDER = '_'

var DIGIT = '#'

function isFormatCharacter(char) {
  return char === DIGIT
}

function isValidCharacter(char, format) {
  if (format == DIGIT) { return /^\d$/.test(char) }
  throw new Error('Unknown format character: ' + format)
}

/**
 * @param {Array.<string>} value
 * @param {Array.<string>} pattern
 * @return {Array.<string>}
 */
function formatValueToPattern(value, pattern) {
  var valueBuffer = new Array(pattern.length)
  var valueIndex = 0
  for (var i = 0, l = pattern.length; i < l ; i++) {
    if (isFormatCharacter(pattern[i])) {
      valueBuffer[i] = value.length > valueIndex && isValidCharacter(value[valueIndex], pattern[i])
                       ? value[valueIndex]
                       : PLACEHOLDER
      valueIndex++
    }
    else {
      valueBuffer[i] = pattern[i]
      // Also allow the value to contain static values from the pattern by
      // advancing its index.
      if (value.length > valueIndex && value[valueIndex] === pattern[i]) {
        valueIndex++
      }
    }
  }
  return valueBuffer
}

function InputMask(options) {
  if (!(this instanceof InputMask)) { return new InputMask(options) }

  options = assign({
    pattern: null,
    selection: {start: 0, end: 0},
    value: ''
  }, options)

  if (options.pattern == null) {
    throw new Error('InputMask: you must provide a pattern.')
  }

  this.setPattern(options.pattern, options.value)
  this.setSelection(options.selection)
}

/**
 * Applies a single character of input based on the current selection.
 * @param {string} input a single character of input.
 * @return {boolean} true if a change has been made to value or selection as a
 *   result of the input, false otherwise.
 */
InputMask.prototype.input = function input(input) {
  // Ignore additional input if the cursor's at the end of the pattern
  if (this.selection.start === this.selection.end &&
      this.selection.start === this.pattern.length) {
    return false
  }

  var format = this.pattern[this.selection.start]

  // Bail out or add the character to input
  if (isFormatCharacter(format)) {
    if (!isValidCharacter(input, format)) {
      return false
    }
    this.value[this.selection.start] = input
  }
  else {
    // The user must have placed the cursor prior to, or began a selection with,
    // a static part of the pattern, so skip over it.
    this.value[this.selection.start] = format
  }

  // If multiple chatacters were selected, blank the remainder out based on the
  // pattern.
  var end = this.selection.end - 1
  while (end > this.selection.start) {
    format = this.pattern[end]
    this.value[end] = isFormatCharacter(format) ? PLACEHOLDER : format
    end--
  }

  // Advance the cursor to the next character
  this.selection.start++
  this.selection.end = this.selection.start

  // Skip over any subsequent static characters
  while (this.pattern.length > this.selection.start &&
         !isFormatCharacter(this.pattern[this.selection.start])) {
    this.value[this.selection.start] = this.pattern[this.selection.start]
    this.selection.start++
    this.selection.end++
  }

  return true
}

/**
 * Attempts to delete from the value based on the current cursor position or
 * selection.
 * @return {boolean} true if the value or selection changed as the result of
 *   backspacing, false otherwise.
 */
InputMask.prototype.backspace = function backspace() {
  // If the cursor is at the start there's nothing to do
  if (this.selection.start === 0 && this.selection.end === 0) {
    return false
  }

  var format

  // No range selected - work on the character preceding the cursor
  if (this.selection.start === this.selection.end) {
    format = this.pattern[this.selection.start - 1]
    this.value[this.selection.start - 1] = isFormatCharacter(format) ? PLACEHOLDER : format
    this.selection.start--
    this.selection.end--
  }
  // Range selected - delete characters and leave the cursor at the start of the selection
  else {
    var end = this.selection.end - 1
    while (end >= this.selection.start) {
      format = this.pattern[end]
      this.value[end] = isFormatCharacter(format) ? PLACEHOLDER : format
      end--
    }
    this.selection.end = this.selection.start
  }

  return true
}

/**
 * Attempts to paste input at the current cursor position or over the top of the
 * current selection.
 * Invalid content at any position will cause the paste to be rejected, and it
 * may contain static parts of the mask's pattern.
 * @param {string} input
 * @return {boolean} true if the paste was successful, false otherwise.
 */
InputMask.prototype.paste = function paste(input) {
  var initialValue = this.value.slice()
  var initialSelection = assign({}, this.selection)
  for (var i = 0, l = input.length; i < l; i++) {
    var valid = this.input(input.charAt(i))
    // Allow static parts of the pattern to appear in pasted input
    if (!valid) {
      if (this.selection.start > 0) {
        var format = this.pattern[this.selection.start - 1]
        if (!isFormatCharacter(format) && input.charAt(i) === format) {
          continue
        }
      }
      this.value = initialValue
      this.selection = initialSelection
      return false
    }
  }
  return true
}

InputMask.prototype.setPattern = function setPattern(pattern, value) {
  var patternChars = pattern.split('')
  var firstEditableIndex = null
  var lastEditableIndex = null
  for (var i = 0, l = patternChars.length; i < l; i++) {
    if (isFormatCharacter(patternChars[i])) {
      if (firstEditableIndex === null) {
        firstEditableIndex = i
      }
      lastEditableIndex = i
    }
  }
  if (firstEditableIndex === null && lastEditableIndex === null) {
    throw new Error(
      'InputMask: pattern "' + pattern + '" does not contain any editable characters.'
    )
  }
  this.pattern = patternChars
  this._firstEditableIndex = firstEditableIndex
  this._lastEditableIndex = lastEditableIndex
  this.setValue(value || '')
}

InputMask.prototype.setSelection = function setSelection(selection) {
  this.selection = assign({}, selection)
  if (this.selection.start === this.selection.end) {
    if (this.selection.start < this._firstEditableIndex) {
      this.selection.start = this.selection.end = this._firstEditableIndex
      return true
    }
    if (this.selection.end > this._lastEditableIndex + 1) {
      this.selection.start = this.selection.end = this._lastEditableIndex + 1
      return true
    }
  }
  return false
}

InputMask.prototype.getValue = function getValue() {
  return this.value.join('')
}

InputMask.prototype.setValue = function setValue(value) {
  this.value = formatValueToPattern(value.split(''), this.pattern)
}

InputMask.formatValueToPattern = formatValueToPattern

module.exports = InputMask<|endoftext|>


<commit_before>console.log('beep boop');
console.error('foobaz');
// window.close();
<commit_msg>fix failing console test<commit_after>console.log('beep boop');
console.error('foobaz');
window.close();
<|endoftext|>


<commit_before>import { Story } from '../../models'

export async function createStory(ctx) {
  delete ctx.request.body._id
  const story = new Story(ctx.request.body)
  story.user = ctx.session.passport.user._id

  try {
    await story.save()
    ctx.body = story.toJSON()
  } catch (err) {
    ctx.throw(422, err.message)
  }
}

export async function updateStory(ctx) {
  const story = ctx.body.story

  Object.assign(story, ctx.request.body)

  await story.save()

  ctx.body = {
    success: true
  }
}

export async function getStory(ctx, next) {
  try {
    const story = await Story.find({
      _id: ctx.params._id,
      $or: [{
        sharedWith: ctx.session.passport.user._id
      }, {
        user: ctx.session.passport.user._id
      }]
    })

    if (!story.length) {
      ctx.throw(404)
    }

    ctx.body = {
      story: story[0]
    }
  } catch (err) {
    if (err === 404 || err.name === 'CastError') {
      ctx.throw(404)
    }

    ctx.throw(500)
  }

  next && next()
}

export async function getStories(ctx) {
  const story = await Story.find({
    $or: [{
      sharedWith: ctx.session.passport.user._id
    }, {
      user: ctx.session.passport.user._id
    }]
  })
  ctx.body = story
}

export async function deleteStory(ctx) {
  const story = new Story({_id: ctx.params._id})

  await story.remove()

  ctx.body = {
    success: true
  }
}
<commit_msg>Delete Storymetadata as well when a story is deleted<commit_after>import { Story, Storymetadata } from '../../models'

export async function createStory(ctx) {
  delete ctx.request.body._id
  const story = new Story(ctx.request.body)
  story.user = ctx.session.passport.user._id

  try {
    await story.save()
    ctx.body = story.toJSON()
  } catch (err) {
    ctx.throw(422, err.message)
  }
}

export async function updateStory(ctx) {
  const story = ctx.body.story

  Object.assign(story, ctx.request.body)

  await story.save()

  ctx.body = {
    success: true
  }
}

export async function getStory(ctx, next) {
  try {
    const story = await Story.find({
      _id: ctx.params._id,
      $or: [{
        sharedWith: ctx.session.passport.user._id
      }, {
        user: ctx.session.passport.user._id
      }]
    })

    if (!story.length) {
      ctx.throw(404)
    }

    ctx.body = {
      story: story[0]
    }
  } catch (err) {
    if (err === 404 || err.name === 'CastError') {
      ctx.throw(404)
    }

    ctx.throw(500)
  }

  next && next()
}

export async function getStories(ctx) {
  const story = await Story.find({
    $or: [{
      sharedWith: ctx.session.passport.user._id
    }, {
      user: ctx.session.passport.user._id
    }]
  })
  ctx.body = story
}

export async function deleteStory(ctx) {
  const story = new Story({_id: ctx.params._id})

  await story.remove()
  await Storymetadata.remove({
    story: ctx.params._id
  })

  ctx.body = {
    success: true
  }
}
<|endoftext|>


<commit_before>require.config({
    'waitSeconds': 2,
    'baseUrl': '../../../src',
    'packages': [
        {
            'name': 'ubRiaUi',
            'location': 'src',
            'main': 'main'
        },
        {
            'name': 'esui',
            'location': '../dep/esui/3.2.0-beta.2/src',
            'main': 'main'
        },
        {
            'name': 'mini-event',
            'location': '../dep/mini-event/1.0.2/src',
            'main': 'main'
        },
        {
            'name': 'underscore',
            'location': '../dep/underscore/1.5.2/src',
            'main': 'underscore'
        },
        {
            'name': 'moment',
            'location': '../dep/moment/2.7.0/src',
            'main': 'moment'
        },
        {
            'name': 'etpl',
            'location': '../dep/etpl/3.1.1/src',
            'main': 'main'
        },
        {
            'name': 'eoo',
            'location': '../dep/eoo/0.1.4/src',
            'main': 'main'
        },
        {
            'name': 'jquery',
            'location': '../dep/jquery/1.11.1/src',
            'main': 'jquery'
        },
        {
            'name': 'eicons',
            'location': '../dep/eicons/1.0.0-beta.1/src',
            'main': 'main.less'
        },
        {
            'name': 'esf',
            'location': '../dep/esf/1.0.0-rc.2/src'
        },
        {
            'name': 'est',
            'location': '../dep/est/1.3.0/src'
        },
        {
            'name': 'unit',
            'location': 'test/unit'
        }
    ],
    'paths': { 'jquery-simulate': 'test/third-party/jquery.simulate' },
    'shim': {
        'jquery-simulate': ['jquery'],
        'matchers': ['jasmine']
    }
});
<commit_msg>revert test/config.js<commit_after>require.config({
    'waitSeconds': 2,
    'baseUrl': '../../../src',
    'packages': [
        {
            'name': 'ubRiaUi',
            'location': 'src',
            'main': 'main'
        },
        {
            'name': 'esui',
            'location': '../dep/esui/3.2.0-beta.2/src',
            'main': 'main'
        },
        {
            'name': 'mini-event',
            'location': '../dep/mini-event/1.0.2/src',
            'main': 'main'
        },
        {
            'name': 'underscore',
            'location': '../dep/underscore/1.5.2/src',
            'main': 'underscore'
        },
        {
            'name': 'moment',
            'location': '../dep/moment/2.7.0/src',
            'main': 'moment'
        },
        {
            'name': 'etpl',
            'location': '../dep/etpl/3.0.1/src',
            'main': 'main'
        },
        {
            'name': 'eoo',
            'location': '../dep/eoo/0.1.4/src'
        },
        {
            'name': 'jquery',
            'location': '../dep/jquery/1.11.1/src',
            'main': 'jquery'
        },
        {
            'name': 'eicons',
            'location': '../dep/eicons/1.0.0-beta.1/src',
            'main': 'main.less'
        },
        {
            'name': 'esf',
            'location': '../dep/esf/1.0.0-rc.2/src'
        },
        {
            'name': 'est',
            'location': '../dep/est/1.3.0/src'
        },
        {
            'name': 'unit',
            'location': 'test/unit'
        }
    ],
    'paths': { 'jquery-simulate': 'test/third-party/jquery.simulate' },
    'shim': {
        'jquery-simulate': ['jquery'],
        'matchers': ['jasmine']
    }
});
<|endoftext|>


<commit_before>;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs['magellan-expedition'] = {
    name : 'magellan-expedition',

    version : '5.2.2',

    settings : {
      active_class: 'active',
      threshold: 0, // pixels from the top of the expedition for it to become fixes
      destination_threshold: 20, // pixels from the top of destination for it to be considered active
      throttle_delay: 30, // calculation throttling to increase framerate
      fixed_top: 0 // top distance in pixels assigend to the fixed element on scroll
    },

    init : function (scope, method, options) {
      Foundation.inherit(this, 'throttle');
      this.bindings(method, options);
    },

    events : function () {
      var self = this,
          S = self.S,
          settings = self.settings;

      // initialize expedition offset
      self.set_expedition_position();

      S(self.scope)
        .off('.magellan')
        .on('click.fndtn.magellan', '[' + self.add_namespace('data-magellan-arrival') + '] a[href^="#"]', function (e) {
            e.preventDefault();
            var expedition = $(this).closest('[' + self.attr_name() + ']'),
                settings = expedition.data('magellan-expedition-init');

            var hash = this.hash.split('#').join(''),
                target = $("a[name='"+hash+"']");
            if (target.length === 0) target = $('#'+hash);

            // Account for expedition height if fixed position
            var scroll_top = target.offset().top;
            scroll_top = scroll_top - expedition.outerHeight();

            $('html, body').stop().animate({
                'scrollTop': scroll_top
            }, 700, 'swing', function () {
                if(history.pushState) {
                    history.pushState(null, null, '#'+hash);
                }
                else {
                    location.hash = '#'+hash;
                }
            });
        })
        .on('scroll.fndtn.magellan', self.throttle(this.check_for_arrivals.bind(this), settings.throttle_delay))
        $(window).on('resize.fndtn.magellan', self.throttle(this.set_expedition_position.bind(this), settings.throttle_delay));
    },

    check_for_arrivals : function() {
      var self = this;
      self.update_arrivals();
      self.update_expedition_positions();
    },

    set_expedition_position : function() {
      var self = this;
      $('[' + this.attr_name() + '=fixed]', self.scope).each(function(idx, el) {
        var expedition = $(this),
            styles = expedition.attr('styles'), // save styles
            top_offset;

        expedition.attr('style', '');
        top_offset = expedition.offset().top + self.settings.threshold;

        expedition.data(self.data_attr('magellan-top-offset'), top_offset);
        expedition.attr('style', styles);
      });
    },

    update_expedition_positions : function() {
      var self = this,
          window_top_offset = $(window).scrollTop();

      $('[' + this.attr_name() + '=fixed]', self.scope).each(function() {
        var expedition = $(this),
            top_offset = expedition.data('magellan-top-offset');

        if (window_top_offset >= top_offset) {
          // Placeholder allows height calculations to be consistent even when
          // appearing to switch between fixed/non-fixed placement
          var placeholder = expedition.prev('[' + self.add_namespace('data-magellan-expedition-clone') + ']');
          if (placeholder.length === 0) {
            placeholder = expedition.clone();
            placeholder.removeAttr(self.attr_name());
            placeholder.attr(self.add_namespace('data-magellan-expedition-clone'),'');
            expedition.before(placeholder);
          }
          expedition.css({position:'fixed', top: self.settings.fixed_top});
        } else {
          expedition.prev('[' + self.add_namespace('data-magellan-expedition-clone') + ']').remove();
          expedition.attr('style','').removeClass('fixed');
        }
      });
    },

    update_arrivals : function() {
      var self = this,
          window_top_offset = $(window).scrollTop();

      $('[' + this.attr_name() + ']', self.scope).each(function() {
        var expedition = $(this),
            settings = settings = expedition.data(self.attr_name(true) + '-init'),
            offsets = self.offsets(expedition, window_top_offset),
            arrivals = expedition.find('[' + self.add_namespace('data-magellan-arrival') + ']'),
            active_item = false;
        offsets.each(function(idx, item) {
          if (item.viewport_offset >= item.top_offset) {
            var arrivals = expedition.find('[' + self.add_namespace('data-magellan-arrival') + ']');
            arrivals.not(item.arrival).removeClass(settings.active_class);
            item.arrival.addClass(settings.active_class);
            active_item = true;
            return true;
          }
        });

        if (!active_item) arrivals.removeClass(settings.active_class);
      });
    },

    offsets : function(expedition, window_offset) {
      var self = this,
          settings = expedition.data(self.attr_name(true) + '-init'),
          viewport_offset = window_offset;

      return expedition.find('[' + self.add_namespace('data-magellan-arrival') + ']').map(function(idx, el) {
        var name = $(this).data(self.data_attr('magellan-arrival')),
            dest = $('[' + self.add_namespace('data-magellan-destination') + '=' + name + ']');
        if (dest.length > 0) {
          var top_offset = dest.offset().top - settings.destination_threshold - expedition.outerHeight();
          return {
            destination : dest,
            arrival : $(this),
            top_offset : top_offset,
            viewport_offset : viewport_offset
          }
        }
      }).sort(function(a, b) {
        if (a.top_offset < b.top_offset) return -1;
        if (a.top_offset > b.top_offset) return 1;
        return 0;
      });
    },

    data_attr: function (str) {
      if (this.namespace.length > 0) {
        return this.namespace + '-' + str;
      }

      return str;
    },

    off : function () {
      this.S(this.scope).off('.magellan');
      this.S(window).off('.magellan');
    },

    reflow : function () {
      var self = this;
      // remove placeholder expeditions used for height calculation purposes
      $('[' + self.add_namespace('data-magellan-expedition-clone') + ']', self.scope).remove();
    }
  };
}(jQuery, window, window.document));
<commit_msg>Fix magellan not saving styles on position set<commit_after>;(function ($, window, document, undefined) {
  'use strict';

  Foundation.libs['magellan-expedition'] = {
    name : 'magellan-expedition',

    version : '5.2.2',

    settings : {
      active_class: 'active',
      threshold: 0, // pixels from the top of the expedition for it to become fixes
      destination_threshold: 20, // pixels from the top of destination for it to be considered active
      throttle_delay: 30, // calculation throttling to increase framerate
      fixed_top: 0 // top distance in pixels assigend to the fixed element on scroll
    },

    init : function (scope, method, options) {
      Foundation.inherit(this, 'throttle');
      this.bindings(method, options);
    },

    events : function () {
      var self = this,
          S = self.S,
          settings = self.settings;

      // initialize expedition offset
      self.set_expedition_position();

      S(self.scope)
        .off('.magellan')
        .on('click.fndtn.magellan', '[' + self.add_namespace('data-magellan-arrival') + '] a[href^="#"]', function (e) {
            e.preventDefault();
            var expedition = $(this).closest('[' + self.attr_name() + ']'),
                settings = expedition.data('magellan-expedition-init');

            var hash = this.hash.split('#').join(''),
                target = $("a[name='"+hash+"']");
            if (target.length === 0) target = $('#'+hash);

            // Account for expedition height if fixed position
            var scroll_top = target.offset().top;
            scroll_top = scroll_top - expedition.outerHeight();

            $('html, body').stop().animate({
                'scrollTop': scroll_top
            }, 700, 'swing', function () {
                if(history.pushState) {
                    history.pushState(null, null, '#'+hash);
                }
                else {
                    location.hash = '#'+hash;
                }
            });
        })
        .on('scroll.fndtn.magellan', self.throttle(this.check_for_arrivals.bind(this), settings.throttle_delay))
        $(window).on('resize.fndtn.magellan', self.throttle(this.set_expedition_position.bind(this), settings.throttle_delay));
    },

    check_for_arrivals : function() {
      var self = this;
      self.update_arrivals();
      self.update_expedition_positions();
    },

    set_expedition_position : function() {
      var self = this;
      $('[' + this.attr_name() + '=fixed]', self.scope).each(function(idx, el) {
        var expedition = $(this),
            styles = expedition.attr('style'), // save styles
            top_offset;

        expedition.attr('style', '');
        top_offset = expedition.offset().top + self.settings.threshold;

        expedition.data(self.data_attr('magellan-top-offset'), top_offset);
        expedition.attr('style', styles);
      });
    },

    update_expedition_positions : function() {
      var self = this,
          window_top_offset = $(window).scrollTop();

      $('[' + this.attr_name() + '=fixed]', self.scope).each(function() {
        var expedition = $(this),
            styles = expedition.attr('style'), // save styles
            top_offset = expedition.data('magellan-top-offset');

        if (window_top_offset >= top_offset) {
          // Placeholder allows height calculations to be consistent even when
          // appearing to switch between fixed/non-fixed placement
          var placeholder = expedition.prev('[' + self.add_namespace('data-magellan-expedition-clone') + ']');
          if (placeholder.length === 0) {
            placeholder = expedition.clone();
            placeholder.removeAttr(self.attr_name());
            placeholder.attr(self.add_namespace('data-magellan-expedition-clone'),'');
            expedition.before(placeholder);
          }
          expedition.css({position:'fixed', top: self.settings.fixed_top});
        } else {
          expedition.prev('[' + self.add_namespace('data-magellan-expedition-clone') + ']').remove();
          expedition.attr('style',styles).css('position','').css('top','').removeClass('fixed');
        }
      });
    },

    update_arrivals : function() {
      var self = this,
          window_top_offset = $(window).scrollTop();

      $('[' + this.attr_name() + ']', self.scope).each(function() {
        var expedition = $(this),
            settings = settings = expedition.data(self.attr_name(true) + '-init'),
            offsets = self.offsets(expedition, window_top_offset),
            arrivals = expedition.find('[' + self.add_namespace('data-magellan-arrival') + ']'),
            active_item = false;
        offsets.each(function(idx, item) {
          if (item.viewport_offset >= item.top_offset) {
            var arrivals = expedition.find('[' + self.add_namespace('data-magellan-arrival') + ']');
            arrivals.not(item.arrival).removeClass(settings.active_class);
            item.arrival.addClass(settings.active_class);
            active_item = true;
            return true;
          }
        });

        if (!active_item) arrivals.removeClass(settings.active_class);
      });
    },

    offsets : function(expedition, window_offset) {
      var self = this,
          settings = expedition.data(self.attr_name(true) + '-init'),
          viewport_offset = window_offset;

      return expedition.find('[' + self.add_namespace('data-magellan-arrival') + ']').map(function(idx, el) {
        var name = $(this).data(self.data_attr('magellan-arrival')),
            dest = $('[' + self.add_namespace('data-magellan-destination') + '=' + name + ']');
        if (dest.length > 0) {
          var top_offset = dest.offset().top - settings.destination_threshold - expedition.outerHeight();
          return {
            destination : dest,
            arrival : $(this),
            top_offset : top_offset,
            viewport_offset : viewport_offset
          }
        }
      }).sort(function(a, b) {
        if (a.top_offset < b.top_offset) return -1;
        if (a.top_offset > b.top_offset) return 1;
        return 0;
      });
    },

    data_attr: function (str) {
      if (this.namespace.length > 0) {
        return this.namespace + '-' + str;
      }

      return str;
    },

    off : function () {
      this.S(this.scope).off('.magellan');
      this.S(window).off('.magellan');
    },

    reflow : function () {
      var self = this;
      // remove placeholder expeditions used for height calculation purposes
      $('[' + self.add_namespace('data-magellan-expedition-clone') + ']', self.scope).remove();
    }
  };
}(jQuery, window, window.document));
<|endoftext|>


<commit_before>'use strict'

const Joi = require('joi')
const _ = require('lodash')
const async = require('async')
const request = require('request')
const pkg = require('../../package')
const mapHook = require('./map-hook')

const requiredOptions = [
  'name',
  'consumer',
  'request',
  'response'
]

const joiOptions = {
  allowUnknown: true,
  presence: 'required'
}

function validateOptions (options) {
  options = options || {}
  requiredOptions.forEach((key) => {
    if (!options.hasOwnProperty(key)) {
      throw new Error(`Invalid contract: Missing required property [${key}]`)
    }
  })
}

function createError (detail, path, res, body) {
  const message = `Contract failed: ${detail.message}`

  const err = new Error(message)
  err.message = message

  const status = `response.statusCode: ${res.statusCode}\n`

  const stringified = typeof body === 'string' ? body : JSON.stringify(body)
  const responseBody = `response.body: ${stringified.trim()}`

  err.detail = `${status}${responseBody}`

  return err
}

function getContentTypeHeader (headers) {
  const headerKeys = Object.keys(headers)
  const typeHeader = headerKeys.find(hk => hk.toLowerCase() === 'content-type')

  return typeHeader
}

function isJsonContentType (headers, typeHeader) {
  return headers[typeHeader].toLowerCase() === 'application/json'
}

function requestBodyShouldBeSerialized (_request) {
  let typeHeader = null

  if (_request.headers) { typeHeader = getContentTypeHeader(_request.headers) }
  if (typeHeader) { return isJsonContentType(_request.headers, typeHeader) }

  return Array.isArray(_request.body) || typeof _request.body === 'object'
}

var Contract = function (options) {
  validateOptions(options)
  this.name = options.name
  this.consumer = options.consumer
  this.before = options.before
  this.after = options.after

  this._request = options.request
  this._response = options.response
  this._client = request.defaults({
    headers: { 'user-agent': 'jackal/' + pkg.version }
  })

  if (options.joiOptions) {
    this.joiOptions = _.merge(joiOptions, options.joiOptions)
  }
}

Contract.prototype.validate = function (cb) {
  const schema = Joi.object().keys(this._response)

  let beforeHooks = []
  let afterHooks = []

  if (this.before) { beforeHooks = this.before.map(mapHook) }
  if (this.after) { afterHooks = this.after.map(mapHook) }

  if (requestBodyShouldBeSerialized(this._request)) {
    this._request.json = true
  }

  const execute = (done) => {
    this._client(this._request, (err, res, body) => {
      if (err) { return done(new Error(`Request failed: ${err.message}`)) }

      const result = Joi.validate(res, schema, joiOptions)
      if (result.error) {
        const detail = result.error.details[0]
        const path = detail.path
        return done(createError(detail, path, res, body))
      }

      done()
    })
  }

  const afterHooksCb = (err) => err ? cb(err) : async.series(afterHooks, cb)
  const beforeHooksCb = (err) => err ? cb(err) : execute(afterHooksCb)

  async.series(beforeHooks, beforeHooksCb)
}

module.exports = Contract
<commit_msg>Forcibly jsonifying any non primitive request bodies<commit_after>'use strict'

const Joi = require('joi')
const _ = require('lodash')
const async = require('async')
const request = require('request')
const pkg = require('../../package')
const mapHook = require('./map-hook')

const requiredOptions = [
  'name',
  'consumer',
  'request',
  'response'
]

const joiOptions = {
  allowUnknown: true,
  presence: 'required'
}

function validateOptions (options) {
  options = options || {}
  requiredOptions.forEach((key) => {
    if (!options.hasOwnProperty(key)) {
      throw new Error(`Invalid contract: Missing required property [${key}]`)
    }
  })
}

function createError (detail, path, res, body) {
  const message = `Contract failed: ${detail.message}`

  const err = new Error(message)
  err.message = message

  const status = `response.statusCode: ${res.statusCode}\n`

  const stringified = typeof body === 'string' ? body : JSON.stringify(body)
  const responseBody = `response.body: ${stringified.trim()}`

  err.detail = `${status}${responseBody}`

  return err
}

function requestBodyShouldBeSerialized (_request) {
  return Array.isArray(_request.body) || typeof _request.body === 'object'
}

var Contract = function (options) {
  validateOptions(options)
  this.name = options.name
  this.consumer = options.consumer
  this.before = options.before
  this.after = options.after

  this._request = options.request
  this._response = options.response
  this._client = request.defaults({
    headers: { 'user-agent': 'jackal/' + pkg.version }
  })

  if (options.joiOptions) {
    this.joiOptions = _.merge(joiOptions, options.joiOptions)
  }
}

Contract.prototype.validate = function (cb) {
  const schema = Joi.object().keys(this._response)

  let beforeHooks = []
  let afterHooks = []

  if (this.before) { beforeHooks = this.before.map(mapHook) }
  if (this.after) { afterHooks = this.after.map(mapHook) }

  this._request.json = requestBodyShouldBeSerialized(this._request)

  const execute = (done) => {
    this._client(this._request, (err, res, body) => {
      if (err) { return done(new Error(`Request failed: ${err.message}`)) }

      const result = Joi.validate(res, schema, joiOptions)
      if (result.error) {
        const detail = result.error.details[0]
        const path = detail.path
        return done(createError(detail, path, res, body))
      }

      done()
    })
  }

  const afterHooksCb = (err) => err ? cb(err) : async.series(afterHooks, cb)
  const beforeHooksCb = (err) => err ? cb(err) : execute(afterHooksCb)

  async.series(beforeHooks, beforeHooksCb)
}

module.exports = Contract
<|endoftext|>


<commit_before><commit_msg>refactor(rules): simplify 'use-first-last' rule<commit_after><|endoftext|>


<commit_before>import EventEmitter from 'events';
import Consumer from 'sqs-consumer';
import Producer from 'sqs-producer';
import AWS from 'aws-sdk';

import ReceiptType from './ReceiptType';

export default class SqsBrokerFacade extends EventEmitter{

  constructor(options) {

    super();

    this._options = options;

    this._sqs = new AWS.SQS({
      region: options.region,
      accessKeyId: options.accessKeyId,
      secretAccessKey: options.secretAccessKey
    });

    this._producers = {};

  }

  _ensureQueue(name) {

    return new Promise((resolve, reject) => {

      var params = {
        QueueName: name
      };

      this._sqs.createQueue(params, function(err, data) {
        if (err) return reject(err);
        resolve(data.QueueUrl);
      });

    });

  }

  _validateRequestMessage(sqsMessage) {
    //TODO: validate message attributes and payload using 'joi' lib
    return true;
  }

  _handleRequestMessage(message, done) {

    this.emit('info', 'Handling new request message');

    let isValid = this._validateRequestMessage(message);

    if (!isValid) {
      this.emit('info', 'Invalid queue message');
      return;
    }

    let request = this._buildRequestDto(message, done);
    this.emit('request', request);

  }

  _buildRequestDto(message, done){
    let request;

    switch (message.MessageAttributes.reefDialect.StringValue) {
      case 'reef-v1-query':
        request = {
          uid: message.MessageAttributes.requestUid.StringValue,
          reefDialect: message.MessageAttributes.reefDialect.StringValue,
          queryType: message.MessageAttributes.queryType.StringValue,
          replyToDomain: message.MessageAttributes.replyToDomain.StringValue,
          replyToLane: message.MessageAttributes.replyToLane.StringValue,
          payload: JSON.parse(message.Body),
          acknowledge: done
      };
        break;

      case 'reef-v1-command':
        request = {
          uid: message.MessageAttributes.requestUid.StringValue,
          reefDialect: message.MessageAttributes.reefDialect.StringValue,
          commandType: message.MessageAttributes.commandType.StringValue,
          replyToDomain: message.MessageAttributes.replyToDomain.StringValue,
          replyToLane: message.MessageAttributes.replyToLane.StringValue,
          receiptType: message.MessageAttributes.receiptType ? message.MessageAttributes.receiptType.StringValue : ReceiptType.EXPECT_RECEIPT,
          payload: JSON.parse(message.Body),
          acknowledge: done
      };
        break;

      default:
          this.emit('error', 'Unrecognized reefDialect');
          return;
    }

    return request;
  }

  async _setupRequestConsumer(domain, lane) {

    let queueUrl = await this._ensureQueue(`${domain}-${lane}-req`);

    let consumer = Consumer.create({
      sqs: this._sqs,
      queueUrl: queueUrl,
      batchSize: 10,
      messageAttributeNames: ['All'],
      handleMessage: (message, done) => { this._handleRequestMessage(message, done); }
    });

    consumer.on('error', function (err) {
      this.emit('error', err ? err.message : "UNKNOWN_ERROR");
    });

    return consumer;

  }

  async _setupResponseProducer(domain, lane) {

    let queueName = `${domain}-${lane}-res`;

    if( this._producers[queueName] ){
        return Promise.resolve(this._producers[queueName]);
    }

    this._producers[queueName] = new Promise( async (resolve, reject) => {

        let queueUrl = await this._ensureQueue(queueName);

        let producer = Producer.create({
          sqs: this._sqs,
          queueUrl: queueUrl
        });

        resolve(producer);

    });

  }

  async setup() {

    this._requestConsumer = await this._setupRequestConsumer(this._options.serviceDomain, this._options.serviceLane);

  }

  start() {

    this._requestConsumer.start();

  }

  stop() {

    this._requestConsumer.stop();

  }

  async enqueueResponse(response) {

    let message = {
      id: response.uid,
      body: JSON.stringify(response.payload),
      messageAttributes: {
        reefDialect: { DataType: 'String', StringValue: response.reefDialect },
        requestUid: { DataType: 'String', StringValue: response.requestUid },
        status: { DataType: 'String', StringValue: response.status }
      }
    };

    let responseProducer = await this._setupResponseProducer(response.domain, response.lane);

    return new Promise((resolve, reject) => {
      responseProducer.send([message], function(err) {
        if (err) reject(err);
        resolve();
      });
    });

  }

}
<commit_msg>Fixed bug when first us of a producer returned undefined<commit_after>import EventEmitter from 'events';
import Consumer from 'sqs-consumer';
import Producer from 'sqs-producer';
import AWS from 'aws-sdk';

import ReceiptType from './ReceiptType';

export default class SqsBrokerFacade extends EventEmitter{

  constructor(options) {

    super();

    this._options = options;

    this._sqs = new AWS.SQS({
      region: options.region,
      accessKeyId: options.accessKeyId,
      secretAccessKey: options.secretAccessKey
    });

    this._producers = {};

  }

  _ensureQueue(name) {

    return new Promise((resolve, reject) => {

      var params = {
        QueueName: name
      };

      this._sqs.createQueue(params, function(err, data) {
        if (err) return reject(err);
        resolve(data.QueueUrl);
      });

    });

  }

  _validateRequestMessage(sqsMessage) {
    //TODO: validate message attributes and payload using 'joi' lib
    return true;
  }

  _handleRequestMessage(message, done) {

    this.emit('info', 'Handling new request message');

    let isValid = this._validateRequestMessage(message);

    if (!isValid) {
      this.emit('info', 'Invalid queue message');
      return;
    }

    let request = this._buildRequestDto(message, done);
    this.emit('request', request);

  }

  _buildRequestDto(message, done){
    let request;

    switch (message.MessageAttributes.reefDialect.StringValue) {
      case 'reef-v1-query':
        request = {
          uid: message.MessageAttributes.requestUid.StringValue,
          reefDialect: message.MessageAttributes.reefDialect.StringValue,
          queryType: message.MessageAttributes.queryType.StringValue,
          replyToDomain: message.MessageAttributes.replyToDomain.StringValue,
          replyToLane: message.MessageAttributes.replyToLane.StringValue,
          payload: JSON.parse(message.Body),
          acknowledge: done
      };
        break;

      case 'reef-v1-command':
        request = {
          uid: message.MessageAttributes.requestUid.StringValue,
          reefDialect: message.MessageAttributes.reefDialect.StringValue,
          commandType: message.MessageAttributes.commandType.StringValue,
          replyToDomain: message.MessageAttributes.replyToDomain.StringValue,
          replyToLane: message.MessageAttributes.replyToLane.StringValue,
          receiptType: message.MessageAttributes.receiptType ? message.MessageAttributes.receiptType.StringValue : ReceiptType.EXPECT_RECEIPT,
          payload: JSON.parse(message.Body),
          acknowledge: done
      };
        break;

      default:
          this.emit('error', 'Unrecognized reefDialect');
          return;
    }

    return request;
  }

  async _setupRequestConsumer(domain, lane) {

    let queueUrl = await this._ensureQueue(`${domain}-${lane}-req`);

    let consumer = Consumer.create({
      sqs: this._sqs,
      queueUrl: queueUrl,
      batchSize: 10,
      messageAttributeNames: ['All'],
      handleMessage: (message, done) => { this._handleRequestMessage(message, done); }
    });

    consumer.on('error', function (err) {
      this.emit('error', err ? err.message : "UNKNOWN_ERROR");
    });

    return consumer;

  }

  async _setupResponseProducer(domain, lane) {

    let queueName = `${domain}-${lane}-res`;

    if( !this._producers[queueName] ){

        this._producers[queueName] = new Promise( async (resolve, reject) => {

            let queueUrl = await this._ensureQueue(queueName);

            let producer = Producer.create({
              sqs: this._sqs,
              queueUrl: queueUrl
            });

            resolve(producer);

        });
    }

    return this._producers[queueName];

  }

  async setup() {

    this._requestConsumer = await this._setupRequestConsumer(this._options.serviceDomain, this._options.serviceLane);

  }

  start() {

    this._requestConsumer.start();

  }

  stop() {

    this._requestConsumer.stop();

  }

  async enqueueResponse(response) {

    let message = {
      id: response.uid,
      body: JSON.stringify(response.payload),
      messageAttributes: {
        reefDialect: { DataType: 'String', StringValue: response.reefDialect },
        requestUid: { DataType: 'String', StringValue: response.requestUid },
        status: { DataType: 'String', StringValue: response.status }
      }
    };

    let responseProducer = await this._setupResponseProducer(response.domain, response.lane);

    return new Promise((resolve, reject) => {
      responseProducer.send([message], function(err) {
        if (err) reject(err);
        resolve();
      });
    });

  }

}
<|endoftext|>


<commit_before>'use strict';

const webpack = require('webpack');
const define  = require('../define');

const WebpackChunkHash = require('webpack-chunk-hash');
const WebpackManifestPlugin = require('webpack-manifest-plugin');
const ChunkManifestPlugin = require('webpack-plugin-chunk-manifest');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');

// const bundles = {
//   'vendor-mweb': [
//     'app/mobile/polyfills.js',
//     'intl',
//     'normalizr',
//     'react-dom',
//     'react-redux',
//     'react-router-dom',
//     'react',
//     'redux'
//   ],
//   'entryChunk-webpack': 'app/mobile/runtime.js',
//   'entryChunk-mobile': 'app/mobile/index.js'
// };

const plugins = [
    new WebpackManifestPlugin({
        basePath: define.rs_output_path,
        fileName: "webpack-manifest.json"
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
        filename: 'js/vendor.[hash:5].js',
        minChunks(module) {
            return module.context && module.context.indexOf('node_modules') >= 0;
        }
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'runtime',
        minChunks: Infinity
    }),

    // new webpack.optimize.CommonsChunkPlugin({
    //     name: 'vendor',
    //     filename: 'js/vendor.[hash:5].js',
    //     minChunks: Infinity,
    //     chunks: ['entryChunk-mobile']
    // }),
    // new webpack.optimize.CommonsChunkPlugin({
    //     name: 'entryChunk-webpack',
    //     minChunks: Infinity,
    //     chunks: ['vendor-mweb']
    // }),
    // new webpack.optimize.CommonsChunkPlugin({
    //     children: true,
    //     name: 'entryChunk-mobile',
    //     minChunks: (module, count) => {
    //         return module.resource && (isCommonLib(resource) || count >= 3);
    //     }
    // }),

    new webpack.HashedModuleIdsPlugin(),
    new WebpackChunkHash(),
    new ChunkManifestPlugin({
        filename: 'chunk-manifest.json'
    }),
    new ExtractCssChunks({
        filename: define.rs_production ? 'css/[name].[contenthash:5].css' : '[name].css',
        allChunks: true,
    }),
    new ScriptExtHtmlWebpackPlugin({
        sync: /vendor/,
        inline: 'runtime',
        preload: ['vendor', 'app'],
        defaultAttribute: 'async'
    })
];

module.exports.config = plugins;
<commit_msg>update<commit_after>'use strict';

const webpack = require('webpack');
const define  = require('../define');

const WebpackChunkHash = require('webpack-chunk-hash');
const WebpackManifestPlugin = require('webpack-manifest-plugin');
const ChunkManifestPlugin = require('webpack-plugin-chunk-manifest');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');

// const bundles = {
//   'vendor-mweb': [
//     'app/mobile/polyfills.js',
//     'intl',
//     'normalizr',
//     'react-dom',
//     'react-redux',
//     'react-router-dom',
//     'react',
//     'redux'
//   ],
//   'entryChunk-webpack': 'app/mobile/runtime.js',
//   'entryChunk-mobile': 'app/mobile/index.js'
// };

const plugins = [
    new WebpackManifestPlugin({
        basePath: define.rs_output_path,
        fileName: "webpack-manifest.json"
    }),
    new WebpackChunkHash(),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'vendor',
        filename: 'js/vendor.[hash:5].js',
        minChunks(m) {
            return m.context && m.context.indexOf('node_modules') >= 0;
        }
    }),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'runtime',
        chunks: ['vendor'],
        minChunks: Infinity
    }),

    // new webpack.optimize.CommonsChunkPlugin({
    //     name: 'vendor',
    //     filename: 'js/vendor.[hash:5].js',
    //     minChunks: Infinity,
    //     chunks: ['entryChunk-mobile']
    // }),
    // new webpack.optimize.CommonsChunkPlugin({
    //     name: 'entryChunk-webpack',
    //     minChunks: Infinity,
    //     chunks: ['vendor-mweb']
    // }),
    // new webpack.optimize.CommonsChunkPlugin({
    //     children: true,
    //     name: 'entryChunk-mobile',
    //     minChunks: (module, count) => {
    //         return module.resource && (isCommonLib(resource) || count >= 3);
    //     }
    // }),
    new ChunkManifestPlugin({
        filename: 'chunk-manifest.json'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new ExtractCssChunks({
        filename: define.rs_production ? 'css/[name].[contenthash:5].css' : '[name].css',
        allChunks: true,
    }),
    new ScriptExtHtmlWebpackPlugin({
        sync: /vendor/,
        inline: 'runtime',
        preload: ['vendor', 'app'],
        defaultAttribute: 'async'
    })
];

module.exports.config = plugins;
<|endoftext|>


<commit_before>foam.CLASS({
    package: 'foam.nanos.u2.navigation',
    name: 'SideNavigation',
    extends: 'foam.u2.View',

    documentation: 'Side navigation bar',

    implements: [
        'foam.mlang.Expressions'
      ],
       requires: [
        'foam.nanos.menu.Menu'
      ],
       imports: [
        'menuDAO'
      ],
       css: `
        ^ .side-nav {
          height: 100%;
          width: 200px;
          position
          z-index: 1;
          top: 0;
          left: 0;
          overflow-x: hidden;
          background-color: white;
          display: inline-block;
        }
        ^ .nav-row {
          display: block;
        }
        ^ .side-nav a {
          display: inline-block;
          margin: 8px 8px 8px 8px;
          text-decoration: none;
          font-size: 20px;
          transition: 0.3s;
        }
        ^ .side-nav a:hover {
          color: #f1f1f1;
        }
        ^ .accordion-card a {
          font-size: 16px;
          margin: 8px 8px 8px 50px;
        }
        ^ .accordion-card-hide {
          display: none;
        }
        ^ .accordion-card-show {
          display: block !important;
        }
        ^ .accordion-button {
          display: inline-block;
          border: none;
          padding: 0;
          margin: 8px 8px;
          font-size: 20px;
          overflow: hidden;
          text-decoration: none;
          text-align: left;
          cursor: pointer;
          white-space: nowrap;
        }
        ^ .accordion-button:focus {
          outline: 0;
        }
      `,
       properties: [
        {
          name: 'accordionCardShowDict',
          value: {}
        },
        {
          class: 'Boolean',
          name: 'accordionCardShow',
          value: true
        },
        {
          class: 'String',
          name: 'menuName',
        }
      ],
       methods: [
        function initE() {
          var Menu = this.Menu;
          var self = this;
          var dao = this.menuDAO.orderBy(Menu.ORDER)
              .where(this.EQ(Menu.PARENT, this.menuName));
              this.addClass(this.myClass())
              .start().addClass('side-nav')
                .select(dao, function(menu) {
                  self.accordionCardShowDict[menu.id] = true;
                  return this.E()
                    .call(function() {
                      var self2 = this;
                      this
                        .start('a').addClass('menuItem')
                          .add(menu.label)
                          .on('click', function() {
                            menu.children.select().then(function(temp) {
                              // Only display submenu if array length is longer than 0
                              temp.array.length === 0 ?
                                menu.launch(self2.__context__, self2) :
                                self.accordianToggle(menu.id);
                            });
                          })
                        .end();
                       // Genearete submenu
                      var X = this.__subContext__;
                      self.menuDAO.where(ctrl.EQ(Menu.PARENT, menu.id)).select(
                        function(submenu) {
                          var accordianSlot = self.accordionCardShowDict$.map(
                            function( keypair ) {
                              return keypair[submenu.parent];
                            }
                          );
                          self2.start()
                            .addClass('accordion-card')
                            .addClass('accordion-card-hide')
                            .enableClass('accordion-card-show',
                            accordianSlot)
                            .call(function() {
                              this.start('a').add(submenu.label)
                              .on('click', function() {
                                submenu.launch_(X, self2);
                              }).end();
                            })
                          .end();
                        }
                      );
                    });
                })
              .end();
          },
           function accordianToggle(menuId) {
            var oldDict = this.accordionCardShowDict;
            oldDict[menuId] = ! oldDict[menuId];
            // accordianSlot won't be triggered if removed the next line
            this.accordionCardShowDict = undefined;
            this.accordionCardShowDict = oldDict;
          }
        ]
      });<commit_msg>fixed indentation and syntax<commit_after>foam.CLASS({
    package: 'foam.nanos.u2.navigation',
    name: 'SideNavigation',
    extends: 'foam.u2.View',

    documentation: 'Side navigation bar',

    implements: [
        'foam.mlang.Expressions'
      ],
       requires: [
        'foam.nanos.menu.Menu'
      ],
       imports: [
        'menuDAO'
      ],
       css: `
        ^ .side-nav {
          height: 100%;
          width: 200px;
          position
          z-index: 1;
          top: 0;
          left: 0;
          overflow-x: hidden;
          background-color: white;
          display: inline-block;
        }
        ^ .nav-row {
          display: block;
        }
        ^ .side-nav a {
          display: inline-block;
          margin: 8px 8px 8px 8px;
          text-decoration: none;
          font-size: 20px;
          transition: 0.3s;
        }
        ^ .side-nav a:hover {
          color: #f1f1f1;
        }
        ^ .accordion-card a {
          font-size: 16px;
          margin: 8px 8px 8px 50px;
        }
        ^ .accordion-card-hide {
          display: none;
        }
        ^ .accordion-card-show {
          display: block !important;
        }
        ^ .accordion-button {
          display: inline-block;
          border: none;
          padding: 0;
          margin: 8px 8px;
          font-size: 20px;
          overflow: hidden;
          text-decoration: none;
          text-align: left;
          cursor: pointer;
          white-space: nowrap;
        }
        ^ .accordion-button:focus {
          outline: 0;
        }
      `,
       properties: [
        {
          name: 'accordionCardShowDict',
          value: {}
        },
        {
          class: 'Boolean',
          name: 'accordionCardShow',
          value: true
        },
        {
          class: 'String',
          name: 'menuName',
        }
      ],
       methods: [
        function initE() {
          var Menu = this.Menu;
          var self = this;
          var dao = this.menuDAO.orderBy(Menu.ORDER)
              .where(this.EQ(Menu.PARENT, this.menuName));
              this.addClass(this.myClass())
              .start().addClass('side-nav')
                .select(dao, function(menu) {
                  self.accordionCardShowDict[menu.id] = true;
                  return this.E()
                    .call(function() {
                      var self2 = this;
                      this
                        .start('a').addClass('menuItem')
                          .add(menu.label)
                          .on('click', function() {
                            menu.children.select().then(function(temp) {
                              // Only display submenu if array length is longer than 0
                              temp.array.length === 0 ?
                                menu.launch(self2.__context__, self2) :
                                self.accordianToggle(menu.id);
                            });
                          })
                        .end();
                       // Genearete submenu
                      var X = this.__subContext__;
                      self.menuDAO.where(ctrl.EQ(Menu.PARENT, menu.id)).select(
                        function(submenu) {
                          var accordianSlot = self.accordionCardShowDict$.map(
                            function( keypair ) {
                              return keypair[submenu.parent];
                            }
                          );
                          self2.start()
                            .addClass('accordion-card')
                            .addClass('accordion-card-hide')
                            .enableClass('accordion-card-show',
                            accordianSlot)
                            .call(function() {
                              this.start('a').add(submenu.label)
                              .on('click', function() {
                                submenu.launch(X, self2);
                              }).end();
                            })
                          .end();
                        }
                      );
                    });
                })
              .end();
          },
           function accordianToggle(menuId) {
            var oldDict = this.accordionCardShowDict;
            oldDict[menuId] = ! oldDict[menuId];
            // accordianSlot won't be triggered if removed the next line
            this.accordionCardShowDict = undefined;
            this.accordionCardShowDict = oldDict;
          }
        ]
      });<|endoftext|>


<commit_before>/* Foundation v2.2 http://foundation.zurb.com */
jQuery(document).ready(function ($) {

  // $('#questionForm').submit(questionSubmitted);
  //
  // function questionSubmitted() {
  //   var form = $(this);
  //   var action = form.attr('action');
  //
  //
  //   return false;
  // }

  /* Use this js doc for all application specific JS */

  /* TABS --------------------------------- */
  /* Remove if you don't need :) */

  function activateTab($tab) {
    var $activeTab = $tab.closest('dl').find('a.active'), contentLocation = $tab.attr("href") + 'Tab';

    //Make Tab Active
    $activeTab.removeClass('active');
    $tab.addClass('active');

    //Show Tab Content
    $(contentLocation).closest('.tabs-content').children('li').hide();
    $(contentLocation).css('display', 'block');
  }

  $('dl.tabs').each(function () {
    //Get all tabs
    var tabs = $(this).children('dd').children('a');
    tabs.click(function (e) {
      activateTab($(this));
    });
  });

  if (window.location.hash) {
    activateTab($('a[href="' + window.location.hash + '"]'));
  }

  /* ALERT BOXES ------------ */
  $(".alert-box").delegate("a.close", "click", function (event) {
    event.preventDefault();
    $(this).closest(".alert-box").fadeOut(function (event) {
      $(this).remove();
    });
  });

  /* PLACEHOLDER FOR FORMS ------------- */
  /* Remove this and jquery.placeholder.min.js if you don't need :) */

  $('input, textarea').placeholder();

  /* TOOLTIPS ------------ */
  $(this).tooltips();

  var options = {
    url:'twilio_sendsms.php',
    success: questionsSubmitted;
  };

  $('#send-sms').ajaxForm(options);

  function questionsSubmitted() {
    if(console && console.log) {
      console.log('questions submitted');
    }
  }

  /* DROPDOWN NAV ------------- */

  var lockNavBar = false;
  $('.nav-bar a.flyout-toggle').live('click', function (e) {
    e.preventDefault();
    var flyout = $(this).siblings('.flyout');
    if (lockNavBar === false) {
      $('.nav-bar .flyout').not(flyout).slideUp(500);
      flyout.slideToggle(500, function () {
        lockNavBar = false;
      });
    }
    lockNavBar = true;
  });
  if (Modernizr.touch) {
    $('.nav-bar>li.has-flyout>a.main').css({
      'padding-right':'75px'
    });
    $('.nav-bar>li.has-flyout>a.flyout-toggle').css({
      'border-left':'1px dashed #eee'
    });
  } else {
    $('.nav-bar>li.has-flyout').hover(function () {
      $(this).children('.flyout').show();
    }, function () {
      $(this).children('.flyout').hide();
    })
  }

  /* DISABLED BUTTONS ------------- */
  /* Gives elements with a class of 'disabled' a return: false; */

});
<commit_msg>fixing ajax submit syntax error<commit_after>/* Foundation v2.2 http://foundation.zurb.com */
jQuery(document).ready(function ($) {

  // $('#questionForm').submit(questionSubmitted);
  //
  // function questionSubmitted() {
  //   var form = $(this);
  //   var action = form.attr('action');
  //
  //
  //   return false;
  // }

  /* Use this js doc for all application specific JS */

  /* TABS --------------------------------- */
  /* Remove if you don't need :) */

  function activateTab($tab) {
    var $activeTab = $tab.closest('dl').find('a.active'), contentLocation = $tab.attr("href") + 'Tab';

    //Make Tab Active
    $activeTab.removeClass('active');
    $tab.addClass('active');

    //Show Tab Content
    $(contentLocation).closest('.tabs-content').children('li').hide();
    $(contentLocation).css('display', 'block');
  }

  $('dl.tabs').each(function () {
    //Get all tabs
    var tabs = $(this).children('dd').children('a');
    tabs.click(function (e) {
      activateTab($(this));
    });
  });

  if (window.location.hash) {
    activateTab($('a[href="' + window.location.hash + '"]'));
  }

  /* ALERT BOXES ------------ */
  $(".alert-box").delegate("a.close", "click", function (event) {
    event.preventDefault();
    $(this).closest(".alert-box").fadeOut(function (event) {
      $(this).remove();
    });
  });

  /* PLACEHOLDER FOR FORMS ------------- */
  /* Remove this and jquery.placeholder.min.js if you don't need :) */

  $('input, textarea').placeholder();

  /* TOOLTIPS ------------ */
  $(this).tooltips();

  var options = {
    url:'twilio_sendsms.php',
    success: questionsSubmitted
  };

  $('#send-sms').ajaxForm(options);

  function questionsSubmitted() {
    if(console && console.log) {
      console.log('questions submitted');
    }
  }

  /* DROPDOWN NAV ------------- */

  var lockNavBar = false;
  $('.nav-bar a.flyout-toggle').live('click', function (e) {
    e.preventDefault();
    var flyout = $(this).siblings('.flyout');
    if (lockNavBar === false) {
      $('.nav-bar .flyout').not(flyout).slideUp(500);
      flyout.slideToggle(500, function () {
        lockNavBar = false;
      });
    }
    lockNavBar = true;
  });
  if (Modernizr.touch) {
    $('.nav-bar>li.has-flyout>a.main').css({
      'padding-right':'75px'
    });
    $('.nav-bar>li.has-flyout>a.flyout-toggle').css({
      'border-left':'1px dashed #eee'
    });
  } else {
    $('.nav-bar>li.has-flyout').hover(function () {
      $(this).children('.flyout').show();
    }, function () {
      $(this).children('.flyout').hide();
    })
  }

  /* DISABLED BUTTONS ------------- */
  /* Gives elements with a class of 'disabled' a return: false; */

});
<|endoftext|>


<commit_before>import { setupTest } from 'ember-qunit';
import { module, test } from 'qunit';

import setupMirage from '../helpers/setup-mirage';
import fetch from 'fetch';

module('Mirage | Keywords', function(hooks) {
  setupTest(hooks);
  setupMirage(hooks);

  module('GET /api/v1/crates', function() {
    test('empty case', async function(assert) {
      let response = await fetch('/api/v1/crates');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        crates: [],
        meta: {
          total: 0,
        },
      });
    });

    test('returns a paginated crates list', async function(assert) {
      this.server.create('crate', { name: 'rand' });

      let response = await fetch('/api/v1/crates');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        crates: [
          {
            id: 'rand',
            badges: [],
            categories: [],
            created_at: '2010-06-16T21:30:45Z',
            description: 'This is the description for the crate called "rand"',
            documentation: null,
            downloads: 0,
            homepage: null,
            keywords: [],
            links: {
              owner_team: '/api/v1/crates/rand/owner_team',
              owner_user: '/api/v1/crates/rand/owner_user',
              reverse_dependencies: '/api/v1/crates/rand/reverse_dependencies',
              version_downloads: '/api/v1/crates/rand/downloads',
              versions: '/api/v1/crates/rand/versions',
            },
            max_version: '1.0.0',
            name: 'rand',
            newest_version: '1.0.0',
            repository: null,
            updated_at: '2017-02-24T12:34:56Z',
            versions: [],
          },
        ],
        meta: {
          total: 1,
        },
      });
    });

    test('never returns more than 10 results', async function(assert) {
      this.server.createList('crate', 25);

      let response = await fetch('/api/v1/crates');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 10);
      assert.equal(responsePayload.meta.total, 25);
    });

    test('supports `page` and `per_page` parameters', async function(assert) {
      this.server.createList('crate', 25, {
        name: i => `crate-${String(i + 1).padStart(2, '0')}`,
      });

      let response = await fetch('/api/v1/crates?page=2&per_page=5');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 5);
      assert.deepEqual(
        responsePayload.crates.map(it => it.id),
        ['crate-06', 'crate-07', 'crate-08', 'crate-09', 'crate-10'],
      );
      assert.equal(responsePayload.meta.total, 25);
    });

    test('supports a `letter` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar' });
      this.server.create('crate', { name: 'BAZ' });

      let response = await fetch('/api/v1/crates?letter=b');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 2);
      assert.deepEqual(
        responsePayload.crates.map(it => it.id),
        ['bar', 'BAZ'],
      );
      assert.equal(responsePayload.meta.total, 2);
    });

    test('supports a `q` parameter', async function(assert) {
      this.server.create('crate', { name: '123456' });
      this.server.create('crate', { name: '00123' });
      this.server.create('crate', { name: '87654' });

      let response = await fetch('/api/v1/crates?q=123');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 2);
      assert.deepEqual(
        responsePayload.crates.map(it => it.id),
        ['123456', '00123'],
      );
      assert.equal(responsePayload.meta.total, 2);
    });

    test('supports a `user_id` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar', _owner_users: [42] });
      this.server.create('crate', { name: 'baz', _owner_users: [13] });

      let response = await fetch('/api/v1/crates?user_id=42');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 1);
      assert.equal(responsePayload.crates[0].id, 'bar');
      assert.equal(responsePayload.meta.total, 1);
    });

    test('supports a `team_id` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar', _owner_teams: [42] });
      this.server.create('crate', { name: 'baz', _owner_teams: [13] });

      let response = await fetch('/api/v1/crates?team_id=42');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 1);
      assert.equal(responsePayload.crates[0].id, 'bar');
      assert.equal(responsePayload.meta.total, 1);
    });

    test('supports a `team_id` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar', _owner_teams: [42] });
      this.server.create('crate', { name: 'baz', _owner_teams: [13] });

      let response = await fetch('/api/v1/crates?team_id=42');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 1);
      assert.equal(responsePayload.crates[0].id, 'bar');
      assert.equal(responsePayload.meta.total, 1);
    });
  });

  module('GET /api/v1/crates/:id', function() {
    test('returns 404 for unknown crates', async function(assert) {
      let response = await fetch('/api/v1/crates/foo');
      assert.equal(response.status, 404);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, { errors: [{ detail: 'Not Found' }] });
    });

    test('returns a crate object for known crates', async function(assert) {
      this.server.create('crate', { name: 'rand' });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        categories: [],
        crate: {
          badges: [],
          categories: [],
          created_at: '2010-06-16T21:30:45Z',
          description: 'This is the description for the crate called "rand"',
          documentation: null,
          downloads: 0,
          homepage: null,
          id: 'rand',
          keywords: [],
          links: {
            owner_team: '/api/v1/crates/rand/owner_team',
            owner_user: '/api/v1/crates/rand/owner_user',
            reverse_dependencies: '/api/v1/crates/rand/reverse_dependencies',
            version_downloads: '/api/v1/crates/rand/downloads',
            versions: '/api/v1/crates/rand/versions',
          },
          max_version: '1.0.0',
          name: 'rand',
          newest_version: '1.0.0',
          repository: null,
          updated_at: '2017-02-24T12:34:56Z',
          versions: [],
        },
        keywords: [],
        versions: [],
      });
    });

    test('includes related versions', async function(assert) {
      this.server.create('crate', { name: 'rand' });
      this.server.create('version', { crate: 'rand', num: '1.0.0' });
      this.server.create('version', { crate: 'rand', num: '1.1.0' });
      this.server.create('version', { crate: 'rand', num: '1.2.0' });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload.versions, [
        {
          id: '1',
          crate: 'rand',
          crate_size: 0,
          created_at: '2010-06-16T21:30:45Z',
          dl_path: '/api/v1/crates/rand/1.0.0/download',
          downloads: 0,
          license: 'MIT/Apache-2.0',
          links: {
            authors: '/api/v1/crates/rand/1.0.0/authors',
            dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
            version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
          },
          num: '1.0.0',
          updated_at: '2017-02-24T12:34:56Z',
          yanked: false,
        },
        {
          id: '2',
          crate: 'rand',
          crate_size: 162963,
          created_at: '2010-06-16T21:30:45Z',
          dl_path: '/api/v1/crates/rand/1.1.0/download',
          downloads: 3702,
          license: 'MIT',
          links: {
            authors: '/api/v1/crates/rand/1.1.0/authors',
            dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
            version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
          },
          num: '1.1.0',
          updated_at: '2017-02-24T12:34:56Z',
          yanked: false,
        },
        {
          id: '3',
          crate: 'rand',
          crate_size: 325926,
          created_at: '2010-06-16T21:30:45Z',
          dl_path: '/api/v1/crates/rand/1.2.0/download',
          downloads: 7404,
          license: 'Apache-2.0',
          links: {
            authors: '/api/v1/crates/rand/1.2.0/authors',
            dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
            version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
          },
          num: '1.2.0',
          updated_at: '2017-02-24T12:34:56Z',
          yanked: false,
        },
      ]);
    });

    test('includes related categories', async function(assert) {
      this.server.create('category', { category: 'no-std' });
      this.server.create('category', { category: 'cli' });
      this.server.create('crate', { name: 'rand', categories: ['no-std'] });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload.categories, [
        {
          id: 'no-std',
          category: 'no-std',
          crates_cnt: 0,
          created_at: '2010-06-16T21:30:45Z',
          description: 'This is the description for the category called "no-std"',
          slug: 'no-std',
        },
      ]);
    });

    test('includes related keywords', async function(assert) {
      this.server.create('keyword', { keyword: 'no-std' });
      this.server.create('keyword', { keyword: 'cli' });
      this.server.create('crate', { name: 'rand', keywords: ['no-std'] });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload.keywords, [
        {
          crates_cnt: 0,
          id: 'no-std',
          keyword: 'no-std',
        },
      ]);
    });
  });
});
<commit_msg>Add tests for `GET /api/v1/crates/:id/versions` mirage request handler<commit_after>import { setupTest } from 'ember-qunit';
import { module, test } from 'qunit';

import setupMirage from '../helpers/setup-mirage';
import fetch from 'fetch';

module('Mirage | Keywords', function(hooks) {
  setupTest(hooks);
  setupMirage(hooks);

  module('GET /api/v1/crates', function() {
    test('empty case', async function(assert) {
      let response = await fetch('/api/v1/crates');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        crates: [],
        meta: {
          total: 0,
        },
      });
    });

    test('returns a paginated crates list', async function(assert) {
      this.server.create('crate', { name: 'rand' });

      let response = await fetch('/api/v1/crates');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        crates: [
          {
            id: 'rand',
            badges: [],
            categories: [],
            created_at: '2010-06-16T21:30:45Z',
            description: 'This is the description for the crate called "rand"',
            documentation: null,
            downloads: 0,
            homepage: null,
            keywords: [],
            links: {
              owner_team: '/api/v1/crates/rand/owner_team',
              owner_user: '/api/v1/crates/rand/owner_user',
              reverse_dependencies: '/api/v1/crates/rand/reverse_dependencies',
              version_downloads: '/api/v1/crates/rand/downloads',
              versions: '/api/v1/crates/rand/versions',
            },
            max_version: '1.0.0',
            name: 'rand',
            newest_version: '1.0.0',
            repository: null,
            updated_at: '2017-02-24T12:34:56Z',
            versions: [],
          },
        ],
        meta: {
          total: 1,
        },
      });
    });

    test('never returns more than 10 results', async function(assert) {
      this.server.createList('crate', 25);

      let response = await fetch('/api/v1/crates');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 10);
      assert.equal(responsePayload.meta.total, 25);
    });

    test('supports `page` and `per_page` parameters', async function(assert) {
      this.server.createList('crate', 25, {
        name: i => `crate-${String(i + 1).padStart(2, '0')}`,
      });

      let response = await fetch('/api/v1/crates?page=2&per_page=5');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 5);
      assert.deepEqual(
        responsePayload.crates.map(it => it.id),
        ['crate-06', 'crate-07', 'crate-08', 'crate-09', 'crate-10'],
      );
      assert.equal(responsePayload.meta.total, 25);
    });

    test('supports a `letter` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar' });
      this.server.create('crate', { name: 'BAZ' });

      let response = await fetch('/api/v1/crates?letter=b');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 2);
      assert.deepEqual(
        responsePayload.crates.map(it => it.id),
        ['bar', 'BAZ'],
      );
      assert.equal(responsePayload.meta.total, 2);
    });

    test('supports a `q` parameter', async function(assert) {
      this.server.create('crate', { name: '123456' });
      this.server.create('crate', { name: '00123' });
      this.server.create('crate', { name: '87654' });

      let response = await fetch('/api/v1/crates?q=123');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 2);
      assert.deepEqual(
        responsePayload.crates.map(it => it.id),
        ['123456', '00123'],
      );
      assert.equal(responsePayload.meta.total, 2);
    });

    test('supports a `user_id` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar', _owner_users: [42] });
      this.server.create('crate', { name: 'baz', _owner_users: [13] });

      let response = await fetch('/api/v1/crates?user_id=42');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 1);
      assert.equal(responsePayload.crates[0].id, 'bar');
      assert.equal(responsePayload.meta.total, 1);
    });

    test('supports a `team_id` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar', _owner_teams: [42] });
      this.server.create('crate', { name: 'baz', _owner_teams: [13] });

      let response = await fetch('/api/v1/crates?team_id=42');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 1);
      assert.equal(responsePayload.crates[0].id, 'bar');
      assert.equal(responsePayload.meta.total, 1);
    });

    test('supports a `team_id` parameter', async function(assert) {
      this.server.create('crate', { name: 'foo' });
      this.server.create('crate', { name: 'bar', _owner_teams: [42] });
      this.server.create('crate', { name: 'baz', _owner_teams: [13] });

      let response = await fetch('/api/v1/crates?team_id=42');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.equal(responsePayload.crates.length, 1);
      assert.equal(responsePayload.crates[0].id, 'bar');
      assert.equal(responsePayload.meta.total, 1);
    });
  });

  module('GET /api/v1/crates/:id', function() {
    test('returns 404 for unknown crates', async function(assert) {
      let response = await fetch('/api/v1/crates/foo');
      assert.equal(response.status, 404);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, { errors: [{ detail: 'Not Found' }] });
    });

    test('returns a crate object for known crates', async function(assert) {
      this.server.create('crate', { name: 'rand' });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        categories: [],
        crate: {
          badges: [],
          categories: [],
          created_at: '2010-06-16T21:30:45Z',
          description: 'This is the description for the crate called "rand"',
          documentation: null,
          downloads: 0,
          homepage: null,
          id: 'rand',
          keywords: [],
          links: {
            owner_team: '/api/v1/crates/rand/owner_team',
            owner_user: '/api/v1/crates/rand/owner_user',
            reverse_dependencies: '/api/v1/crates/rand/reverse_dependencies',
            version_downloads: '/api/v1/crates/rand/downloads',
            versions: '/api/v1/crates/rand/versions',
          },
          max_version: '1.0.0',
          name: 'rand',
          newest_version: '1.0.0',
          repository: null,
          updated_at: '2017-02-24T12:34:56Z',
          versions: [],
        },
        keywords: [],
        versions: [],
      });
    });

    test('includes related versions', async function(assert) {
      this.server.create('crate', { name: 'rand' });
      this.server.create('version', { crate: 'rand', num: '1.0.0' });
      this.server.create('version', { crate: 'rand', num: '1.1.0' });
      this.server.create('version', { crate: 'rand', num: '1.2.0' });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload.versions, [
        {
          id: '1',
          crate: 'rand',
          crate_size: 0,
          created_at: '2010-06-16T21:30:45Z',
          dl_path: '/api/v1/crates/rand/1.0.0/download',
          downloads: 0,
          license: 'MIT/Apache-2.0',
          links: {
            authors: '/api/v1/crates/rand/1.0.0/authors',
            dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
            version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
          },
          num: '1.0.0',
          updated_at: '2017-02-24T12:34:56Z',
          yanked: false,
        },
        {
          id: '2',
          crate: 'rand',
          crate_size: 162963,
          created_at: '2010-06-16T21:30:45Z',
          dl_path: '/api/v1/crates/rand/1.1.0/download',
          downloads: 3702,
          license: 'MIT',
          links: {
            authors: '/api/v1/crates/rand/1.1.0/authors',
            dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
            version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
          },
          num: '1.1.0',
          updated_at: '2017-02-24T12:34:56Z',
          yanked: false,
        },
        {
          id: '3',
          crate: 'rand',
          crate_size: 325926,
          created_at: '2010-06-16T21:30:45Z',
          dl_path: '/api/v1/crates/rand/1.2.0/download',
          downloads: 7404,
          license: 'Apache-2.0',
          links: {
            authors: '/api/v1/crates/rand/1.2.0/authors',
            dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
            version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
          },
          num: '1.2.0',
          updated_at: '2017-02-24T12:34:56Z',
          yanked: false,
        },
      ]);
    });

    test('includes related categories', async function(assert) {
      this.server.create('category', { category: 'no-std' });
      this.server.create('category', { category: 'cli' });
      this.server.create('crate', { name: 'rand', categories: ['no-std'] });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload.categories, [
        {
          id: 'no-std',
          category: 'no-std',
          crates_cnt: 0,
          created_at: '2010-06-16T21:30:45Z',
          description: 'This is the description for the category called "no-std"',
          slug: 'no-std',
        },
      ]);
    });

    test('includes related keywords', async function(assert) {
      this.server.create('keyword', { keyword: 'no-std' });
      this.server.create('keyword', { keyword: 'cli' });
      this.server.create('crate', { name: 'rand', keywords: ['no-std'] });

      let response = await fetch('/api/v1/crates/rand');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload.keywords, [
        {
          crates_cnt: 0,
          id: 'no-std',
          keyword: 'no-std',
        },
      ]);
    });
  });

  module('GET /api/v1/crates/:id/versions', function() {
    test('returns 404 for unknown crates', async function(assert) {
      let response = await fetch('/api/v1/crates/foo/versions');
      assert.equal(response.status, 404);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, { errors: [{ detail: 'Not Found' }] });
    });

    test('empty case', async function(assert) {
      this.server.create('crate', { name: 'rand' });

      let response = await fetch('/api/v1/crates/rand/versions');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        versions: [],
      });
    });

    test('returns all versions belonging to the specified crate', async function(assert) {
      this.server.create('crate', { name: 'rand' });
      this.server.create('version', { crate: 'rand', num: '1.0.0' });
      this.server.create('version', { crate: 'rand', num: '1.1.0' });
      this.server.create('version', { crate: 'rand', num: '1.2.0' });

      let response = await fetch('/api/v1/crates/rand/versions');
      assert.equal(response.status, 200);

      let responsePayload = await response.json();
      assert.deepEqual(responsePayload, {
        versions: [
          {
            id: '1',
            crate: 'rand',
            crate_size: 0,
            created_at: '2010-06-16T21:30:45Z',
            dl_path: '/api/v1/crates/rand/1.0.0/download',
            downloads: 0,
            license: 'MIT/Apache-2.0',
            links: {
              authors: '/api/v1/crates/rand/1.0.0/authors',
              dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
              version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
            },
            num: '1.0.0',
            updated_at: '2017-02-24T12:34:56Z',
            yanked: false,
          },
          {
            id: '2',
            crate: 'rand',
            crate_size: 162963,
            created_at: '2010-06-16T21:30:45Z',
            dl_path: '/api/v1/crates/rand/1.1.0/download',
            downloads: 3702,
            license: 'MIT',
            links: {
              authors: '/api/v1/crates/rand/1.1.0/authors',
              dependencies: '/api/v1/crates/rand/1.1.0/dependencies',
              version_downloads: '/api/v1/crates/rand/1.1.0/downloads',
            },
            num: '1.1.0',
            updated_at: '2017-02-24T12:34:56Z',
            yanked: false,
          },
          {
            id: '3',
            crate: 'rand',
            crate_size: 325926,
            created_at: '2010-06-16T21:30:45Z',
            dl_path: '/api/v1/crates/rand/1.2.0/download',
            downloads: 7404,
            license: 'Apache-2.0',
            links: {
              authors: '/api/v1/crates/rand/1.2.0/authors',
              dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
              version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
            },
            num: '1.2.0',
            updated_at: '2017-02-24T12:34:56Z',
            yanked: false,
          },
        ],
      });
    });
  });
});
<|endoftext|>