<commit_before>/**
 * なでしこ3字句解析のためのルール
 */

const kanakanji = /^[\u3005\u4E00-\u9FCF_a-zA-Z0-9ァ-ヶー]+/
const nakoJosiList = require('./nako_josi_list')
const josiRE = nakoJosiList.josiRE
const removeJosiMap = nakoJosiList.removeJosiMap
const hira = /^[ぁ-ん]/
const allHiragana = /^[ぁ-ん]+$/
const wordHasIjoIka = /^.+(以上|以下|超|未満)$/
const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|px|dot|pt|em|b|mb|kb|gb)/

const errorRead = (ch) => {
  return function () { throw new Error('突然の『' + ch + '』があります。') }
}

module.exports = {
  rules: [
    // 上から順にマッチさせていく
    { name: 'ここまで', pattern: /^;;;/ }, // #925
    { name: 'eol', pattern: /^\n/ },
    { name: 'eol', pattern: /^;/ },
    // eslint-disable-next-line no-control-regex
    { name: 'space', pattern: /^(\x20|\x09|・)+/ }, // #877,#1015
    { name: 'comma', pattern: /^,/ },
    { name: 'line_comment', pattern: /^#[^\n]*/ },
    { name: 'line_comment', pattern: /^\/\/[^\n]*/ },
    { name: 'range_comment', pattern: /^\/\*/, cbParser: cbRangeComment },
    { name: 'def_test', pattern: /^●テスト:/ },
    { name: 'def_func', pattern: /^●/ },
    // 数値の判定 --- この後nako_lexerにて単位を読む処理が入る(#994)
    { name: 'number', pattern: /^0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^0[oO][0-7]+(_[0-7]+)*/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^0[bB][0-1]+(_[0-1]+)*/, readJosi: true, cb: parseNumber },
    // 下の三つは小数点が挟まっている場合、小数点から始まっている場合、小数点がない場合の十進法の数値にマッチします
    { name: 'number', pattern: /^\d+(_\d+)*\.(\d+(_\d+)*)?([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^\.\d+(_\d+)*([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^\d+(_\d+)*([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
    { name: 'ここから', pattern: /^(ここから),?/ },
    { name: 'ここまで', pattern: /^(ここまで|💧)/ },
    { name: 'もし', pattern: /^もしも?/ },
    // 「ならば」は助詞として定義している
    { name: '違えば', pattern: /^違(えば)?/ },
    // 「回」「間」「繰返」「反復」「抜」「続」「戻」「代入」「条件分岐」などは replaceWord で word から変換
    // @see nako_reserved_words.js
    { name: 'shift_r0', pattern: /^>>>/ },
    { name: 'shift_r', pattern: /^>>/ },
    { name: 'shift_l', pattern: /^<</ },
    { name: '===', pattern: /^===/ }, // #999
    { name: '!==', pattern: /^!==/ }, // #999
    { name: 'gteq', pattern: /^(≧|>=|=>)/ },
    { name: 'lteq', pattern: /^(≦|<=|=<)/ },
    { name: 'noteq', pattern: /^(≠|<>|!=)/ },
    { name: '←', pattern: /^(←|<--)/ }, // 関数呼び出し演算子 #891 #899
    { name: 'eq', pattern: /^=/ },
    { name: 'line_comment', pattern: /^!(インデント構文|ここまでだるい)[^\n]*/ },
    { name: 'not', pattern: /^!/ },
    { name: 'gt', pattern: /^>/ },
    { name: 'lt', pattern: /^</ },
    { name: 'and', pattern: /^(かつ|&&)/ },
    { name: 'or', pattern: /^(または|或いは|あるいは|\|\|)/ },
    { name: '@', pattern: /^@/ },
    { name: '+', pattern: /^\+/ },
    { name: '-', pattern: /^-/ },
    { name: '*', pattern: /^(×|\*)/ },
    { name: '/', pattern: /^(÷|\/)/ },
    { name: '%', pattern: /^%/ },
    { name: '^', pattern: /^\^/ },
    { name: '&', pattern: /^&/ },
    { name: '[', pattern: /^\[/ },
    { name: ']', pattern: /^]/, readJosi: true },
    { name: '(', pattern: /^\(/ },
    { name: ')', pattern: /^\)/, readJosi: true },
    { name: '|', pattern: /^\|/ },
    { name: 'string', pattern: /^🌿/, cbParser: src => cbString('🌿', '🌿', src) },
    { name: 'string_ex', pattern: /^🌴/, cbParser: src => cbString('🌴', '🌴', src) },
    { name: 'string_ex', pattern: /^「/, cbParser: src => cbString('「', '」', src) },
    { name: 'string', pattern: /^『/, cbParser: src => cbString('『', '』', src) },
    { name: 'string_ex', pattern: /^“/, cbParser: src => cbString('“', '”', src) },
    { name: 'string_ex', pattern: /^"/, cbParser: src => cbString('"', '"', src) },
    { name: 'string', pattern: /^'/, cbParser: src => cbString('\'', '\'', src) },
    { name: '」', pattern: /^」/, cbParser: errorRead('」') }, // error
    { name: '』', pattern: /^』/, cbParser: errorRead('』') }, // error
    { name: 'func', pattern: /^\{関数\},?/ },
    { name: '{', pattern: /^\{/ },
    { name: '}', pattern: /^\}/, readJosi: true },
    { name: ':', pattern: /^:/ },
    { name: '_eol', pattern: /^_\s*\n/ },
    { name: 'dec_lineno', pattern: /^‰/ },
    // 絵文字変数 = (絵文字)英数字*
    { name: 'word', pattern: /^[\uD800-\uDBFF][\uDC00-\uDFFF][_a-zA-Z0-9]*/, readJosi: true },
    { name: 'word', pattern: /^[\u1F60-\u1F6F][_a-zA-Z0-9]*/, readJosi: true }, // 絵文字
    { name: 'word', pattern: /^《.+?》/, readJosi: true }, // 《特別名前トークン》(#672)
    // 単語句
    {
      name: 'word',
      pattern: /^[_a-zA-Z\u3005\u4E00-\u9FCFぁ-んァ-ヶ]/,
      cbParser: cbWordParser
    }
  ],
  trimOkurigana,
  unitRE
}

// eslint-disable-next-line no-unused-vars
function parseInt2 (s) {
  const ss = s.substring(2)
  return parseInt(ss, 2)
}
// eslint-disable-next-line no-unused-vars
function parseInt8 (s) {
  const ss = s.substring(2)
  return parseInt(ss, 8)
}

function cbRangeComment (src) {
  let res = ''
  const josi = ''
  let numEOL = 0
  src = src.substr(2) // skip /*
  const i = src.indexOf('*/')
  if (i < 0) { // not found
    res = src
    src = ''
  } else {
    res = src.substr(0, i)
    src = src.substr(i + 2)
  }
  // 改行を数える
  for (let i = 0; i < res.length; i++) { if (res.charAt(i) === '\n') { numEOL++ } }

  res = res.replace(/(^\s+|\s+$)/, '') // trim
  return { src: src, res: res, josi: josi, numEOL: numEOL }
}

/**
 * @param {string} src
 */
function cbWordParser (src, isTrimOkurigana = true) {
  /*
    kanji    = [\u3005\u4E00-\u9FCF]
    hiragana = [ぁ-ん]
    katakana = [ァ-ヶー]
    emoji    = [\u1F60-\u1F6F]
    uni_extra = [\uD800-\uDBFF] [\uDC00-\uDFFF]
    alphabet = [_a-zA-Z]
    numchars = [0-9]
  */
  let res = ''
  let josi = ''
  while (src !== '') {
    if (res.length > 0) {
      // 助詞の判定
      const j = josiRE.exec(src)
      if (j) {
        josi = j[0]
        src = src.substr(j[0].length)
        // 助詞の直後にある「,」を飛ばす #877
        if (src.charAt(0) === ',') { src = src.substr(1) }
        break
      }
    }
    // カタカナ漢字英数字か？
    const m = kanakanji.exec(src)
    if (m) {
      res += m[0]
      src = src.substr(m[0].length)
      continue
    }
    // ひらがな？
    const h = hira.test(src)
    if (h) {
      res += src.charAt(0)
      src = src.substr(1)
      continue
    }
    break // other chars
  }
  // 「間」の特殊ルール (#831)
  // 「等しい間」や「一致する間」なら「間」をsrcに戻す。ただし「システム時間」はそのままにする。
  if (/[ぁ-ん]間$/.test(res)) {
    src = res.charAt(res.length - 1) + src
    res = res.slice(0, -1)
  }
  // 「以上」「以下」「超」「未満」 #918
  const ii = wordHasIjoIka.exec(res)
  if (ii) {
    src = ii[1] + josi + src
    josi = ''
    res = res.substr(0, res.length - ii[1].length)
  }
  // 助詞「こと」「である」「です」などは「＊＊すること」のように使うので削除 #936 #939 #974
  if (removeJosiMap[josi]) { josi = '' }

  // 漢字カタカナ英語から始まる語句 --- 送り仮名を省略
  if (isTrimOkurigana) {
    res = trimOkurigana(res)
  }
  // 助詞だけの語句の場合
  if (res === '' && josi !== '') {
    res = josi
    josi = ''
  }
  return { src: src, res: res, josi: josi, numEOL: 0 }
}

function cbString (beginTag, closeTag, src) {
  let res = ''
  let josi = ''
  let numEOL = 0
  src = src.substr(beginTag.length) // skip beginTag
  const i = src.indexOf(closeTag)
  if (i < 0) { // not found
    res = src
    src = ''
  } else {
    res = src.substr(0, i)
    src = src.substr(i + closeTag.length)
    // res の中に beginTag があればエラーにする #953
    if (res.indexOf(beginTag) >= 0) {
      if (beginTag === '『') {
        throw new Error('「『」で始めた文字列の中に「『」を含めることはできません。')
      } else {
        throw new Error(`『${beginTag}』で始めた文字列の中に『${beginTag}』を含めることはできません。`)
      }
    }
  }
  // 文字列直後の助詞を取得
  const j = josiRE.exec(src)
  if (j) {
    josi = j[0]
    src = src.substr(j[0].length)
    // 助詞の後のカンマ #877
    if (src.charAt(0) === ',') { src = src.substr(1) }
  }
  // 助詞「こと」「である」「です」などは「＊＊すること」のように使うので削除 #936 #939 #974
  if (removeJosiMap[josi]) { josi = '' }

  // 改行を数える
  for (let i = 0; i < res.length; i++) { if (res.charAt(i) === '\n') { numEOL++ } }

  return { src: src, res: res, josi: josi, numEOL: numEOL }
}

function trimOkurigana (s) {
  // ひらがなから始まらない場合、送り仮名を削除。(例)置換する
  if (!hira.test(s)) {
    return s.replace(/[ぁ-ん]+/g, '')
  }
  // 全てひらがな？ (例) どうぞ
  if (allHiragana.test(s)) { return s }
  // 末尾のひらがなのみ (例)お願いします →お願
  return s.replace(/[ぁ-ん]+$/g, '')
}

function parseNumber (n) {
  return Number(n.replace(/_/g, ''))
}
<commit_msg>助詞の前後に空白があるときエラーになる問題を修正 #1079<commit_after>/**
 * なでしこ3字句解析のためのルール
 */

const kanakanji = /^[\u3005\u4E00-\u9FCF_a-zA-Z0-9ァ-ヶー]+/
const nakoJosiList = require('./nako_josi_list')
const josiRE = nakoJosiList.josiRE
const removeJosiMap = nakoJosiList.removeJosiMap
const hira = /^[ぁ-ん]/
const allHiragana = /^[ぁ-ん]+$/
const wordHasIjoIka = /^.+(以上|以下|超|未満)$/
const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|px|dot|pt|em|b|mb|kb|gb)/

const errorRead = (ch) => {
  return function () { throw new Error('突然の『' + ch + '』があります。') }
}

module.exports = {
  rules: [
    // 上から順にマッチさせていく
    { name: 'ここまで', pattern: /^;;;/ }, // #925
    { name: 'eol', pattern: /^\n/ },
    { name: 'eol', pattern: /^;/ },
    // eslint-disable-next-line no-control-regex
    { name: 'space', pattern: /^(\x20|\x09|・)+/ }, // #877,#1015
    { name: 'comma', pattern: /^,/ },
    { name: 'line_comment', pattern: /^#[^\n]*/ },
    { name: 'line_comment', pattern: /^\/\/[^\n]*/ },
    { name: 'range_comment', pattern: /^\/\*/, cbParser: cbRangeComment },
    { name: 'def_test', pattern: /^●テスト:/ },
    { name: 'def_func', pattern: /^●/ },
    // 数値の判定 --- この後nako_lexerにて単位を読む処理が入る(#994)
    { name: 'number', pattern: /^0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^0[oO][0-7]+(_[0-7]+)*/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^0[bB][0-1]+(_[0-1]+)*/, readJosi: true, cb: parseNumber },
    // 下の三つは小数点が挟まっている場合、小数点から始まっている場合、小数点がない場合の十進法の数値にマッチします
    { name: 'number', pattern: /^\d+(_\d+)*\.(\d+(_\d+)*)?([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^\.\d+(_\d+)*([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
    { name: 'number', pattern: /^\d+(_\d+)*([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
    { name: 'ここから', pattern: /^(ここから),?/ },
    { name: 'ここまで', pattern: /^(ここまで|💧)/ },
    { name: 'もし', pattern: /^もしも?/ },
    // 「ならば」は助詞として定義している
    { name: '違えば', pattern: /^違(えば)?/ },
    // 「回」「間」「繰返」「反復」「抜」「続」「戻」「代入」「条件分岐」などは replaceWord で word から変換
    // @see nako_reserved_words.js
    { name: 'shift_r0', pattern: /^>>>/ },
    { name: 'shift_r', pattern: /^>>/ },
    { name: 'shift_l', pattern: /^<</ },
    { name: '===', pattern: /^===/ }, // #999
    { name: '!==', pattern: /^!==/ }, // #999
    { name: 'gteq', pattern: /^(≧|>=|=>)/ },
    { name: 'lteq', pattern: /^(≦|<=|=<)/ },
    { name: 'noteq', pattern: /^(≠|<>|!=)/ },
    { name: '←', pattern: /^(←|<--)/ }, // 関数呼び出し演算子 #891 #899
    { name: 'eq', pattern: /^=/ },
    { name: 'line_comment', pattern: /^!(インデント構文|ここまでだるい)[^\n]*/ },
    { name: 'not', pattern: /^!/ },
    { name: 'gt', pattern: /^>/ },
    { name: 'lt', pattern: /^</ },
    { name: 'and', pattern: /^(かつ|&&)/ },
    { name: 'or', pattern: /^(または|或いは|あるいは|\|\|)/ },
    { name: '@', pattern: /^@/ },
    { name: '+', pattern: /^\+/ },
    { name: '-', pattern: /^-/ },
    { name: '*', pattern: /^(×|\*)/ },
    { name: '/', pattern: /^(÷|\/)/ },
    { name: '%', pattern: /^%/ },
    { name: '^', pattern: /^\^/ },
    { name: '&', pattern: /^&/ },
    { name: '[', pattern: /^\[/ },
    { name: ']', pattern: /^]/, readJosi: true },
    { name: '(', pattern: /^\(/ },
    { name: ')', pattern: /^\)/, readJosi: true },
    { name: '|', pattern: /^\|/ },
    { name: 'string', pattern: /^🌿/, cbParser: src => cbString('🌿', '🌿', src) },
    { name: 'string_ex', pattern: /^🌴/, cbParser: src => cbString('🌴', '🌴', src) },
    { name: 'string_ex', pattern: /^「/, cbParser: src => cbString('「', '」', src) },
    { name: 'string', pattern: /^『/, cbParser: src => cbString('『', '』', src) },
    { name: 'string_ex', pattern: /^“/, cbParser: src => cbString('“', '”', src) },
    { name: 'string_ex', pattern: /^"/, cbParser: src => cbString('"', '"', src) },
    { name: 'string', pattern: /^'/, cbParser: src => cbString('\'', '\'', src) },
    { name: '」', pattern: /^」/, cbParser: errorRead('」') }, // error
    { name: '』', pattern: /^』/, cbParser: errorRead('』') }, // error
    { name: 'func', pattern: /^\{関数\},?/ },
    { name: '{', pattern: /^\{/ },
    { name: '}', pattern: /^\}/, readJosi: true },
    { name: ':', pattern: /^:/ },
    { name: '_eol', pattern: /^_\s*\n/ },
    { name: 'dec_lineno', pattern: /^‰/ },
    // 絵文字変数 = (絵文字)英数字*
    { name: 'word', pattern: /^[\uD800-\uDBFF][\uDC00-\uDFFF][_a-zA-Z0-9]*/, readJosi: true },
    { name: 'word', pattern: /^[\u1F60-\u1F6F][_a-zA-Z0-9]*/, readJosi: true }, // 絵文字
    { name: 'word', pattern: /^《.+?》/, readJosi: true }, // 《特別名前トークン》(#672)
    // 単語句
    {
      name: 'word',
      pattern: /^[_a-zA-Z\u3005\u4E00-\u9FCFぁ-んァ-ヶ]/,
      cbParser: cbWordParser
    }
  ],
  trimOkurigana,
  unitRE
}

// eslint-disable-next-line no-unused-vars
function parseInt2 (s) {
  const ss = s.substring(2)
  return parseInt(ss, 2)
}
// eslint-disable-next-line no-unused-vars
function parseInt8 (s) {
  const ss = s.substring(2)
  return parseInt(ss, 8)
}

function cbRangeComment (src) {
  let res = ''
  const josi = ''
  let numEOL = 0
  src = src.substr(2) // skip /*
  const i = src.indexOf('*/')
  if (i < 0) { // not found
    res = src
    src = ''
  } else {
    res = src.substr(0, i)
    src = src.substr(i + 2)
  }
  // 改行を数える
  for (let i = 0; i < res.length; i++) { if (res.charAt(i) === '\n') { numEOL++ } }

  res = res.replace(/(^\s+|\s+$)/, '') // trim
  return { src: src, res: res, josi: josi, numEOL: numEOL }
}

/**
 * @param {string} src
 */
function cbWordParser (src, isTrimOkurigana = true) {
  /*
    kanji    = [\u3005\u4E00-\u9FCF]
    hiragana = [ぁ-ん]
    katakana = [ァ-ヶー]
    emoji    = [\u1F60-\u1F6F]
    uni_extra = [\uD800-\uDBFF] [\uDC00-\uDFFF]
    alphabet = [_a-zA-Z]
    numchars = [0-9]
  */
  let res = ''
  let josi = ''
  while (src !== '') {
    if (res.length > 0) {
      // 助詞の判定
      const j = josiRE.exec(src)
      if (j) {
        josi = j[0].replace(/^\s+/, '')
        src = src.substr(j[0].length)
        // 助詞の直後にある「,」を飛ばす #877
        if (src.charAt(0) === ',') { src = src.substr(1) }
        break
      }
    }
    // カタカナ漢字英数字か？
    const m = kanakanji.exec(src)
    if (m) {
      res += m[0]
      src = src.substr(m[0].length)
      continue
    }
    // ひらがな？
    const h = hira.test(src)
    if (h) {
      res += src.charAt(0)
      src = src.substr(1)
      continue
    }
    break // other chars
  }
  // 「間」の特殊ルール (#831)
  // 「等しい間」や「一致する間」なら「間」をsrcに戻す。ただし「システム時間」はそのままにする。
  if (/[ぁ-ん]間$/.test(res)) {
    src = res.charAt(res.length - 1) + src
    res = res.slice(0, -1)
  }
  // 「以上」「以下」「超」「未満」 #918
  const ii = wordHasIjoIka.exec(res)
  if (ii) {
    src = ii[1] + josi + src
    josi = ''
    res = res.substr(0, res.length - ii[1].length)
  }
  // 助詞「こと」「である」「です」などは「＊＊すること」のように使うので削除 #936 #939 #974
  if (removeJosiMap[josi]) { josi = '' }

  // 漢字カタカナ英語から始まる語句 --- 送り仮名を省略
  if (isTrimOkurigana) {
    res = trimOkurigana(res)
  }
  // 助詞だけの語句の場合
  if (res === '' && josi !== '') {
    res = josi
    josi = ''
  }
  return { src: src, res: res, josi: josi, numEOL: 0 }
}

function cbString (beginTag, closeTag, src) {
  let res = ''
  let josi = ''
  let numEOL = 0
  src = src.substr(beginTag.length) // skip beginTag
  const i = src.indexOf(closeTag)
  if (i < 0) { // not found
    res = src
    src = ''
  } else {
    res = src.substr(0, i)
    src = src.substr(i + closeTag.length)
    // res の中に beginTag があればエラーにする #953
    if (res.indexOf(beginTag) >= 0) {
      if (beginTag === '『') {
        throw new Error('「『」で始めた文字列の中に「『」を含めることはできません。')
      } else {
        throw new Error(`『${beginTag}』で始めた文字列の中に『${beginTag}』を含めることはできません。`)
      }
    }
  }
  // 文字列直後の助詞を取得
  const j = josiRE.exec(src)
  if (j) {
    josi = j[0].replace(/^\s+/, '')
    src = src.substr(j[0].length)
    // 助詞の後のカンマ #877
    if (src.charAt(0) === ',') { src = src.substr(1) }
  }
  // 助詞「こと」「である」「です」などは「＊＊すること」のように使うので削除 #936 #939 #974
  if (removeJosiMap[josi]) { josi = '' }

  // 改行を数える
  for (let i = 0; i < res.length; i++) { if (res.charAt(i) === '\n') { numEOL++ } }

  return { src: src, res: res, josi: josi, numEOL: numEOL }
}

function trimOkurigana (s) {
  // ひらがなから始まらない場合、送り仮名を削除。(例)置換する
  if (!hira.test(s)) {
    return s.replace(/[ぁ-ん]+/g, '')
  }
  // 全てひらがな？ (例) どうぞ
  if (allHiragana.test(s)) { return s }
  // 末尾のひらがなのみ (例)お願いします →お願
  return s.replace(/[ぁ-ん]+$/g, '')
}

function parseNumber (n) {
  return Number(n.replace(/_/g, ''))
}
<|endoftext|>


<commit_before>const API_VERSION = 'v2'
const PER_PAGE = 20
const basePath = () => `${ENV.AUTH_DOMAIN}/api`

function getAPIPath(relPath, queryParams = {}) {
  let path = `${basePath()}/${API_VERSION}/${relPath}`
  const queryArr = []
  for (const param in queryParams) {
    if (queryParams.hasOwnProperty(param)) {
      queryArr.push(`${param}=${queryParams[param]}`)
    }
  }
  if (queryArr.length) {
    path = `${path}?${queryArr.join('&')}`
  }
  return path
}

// Assets
export function s3CredentialsPath() {
  return {
    path: getAPIPath('assets/credentials'),
  }
}
// Authentication
export function accessTokens() {
  return {
    path: `${basePath()}/oauth/token`,
  }
}
export function forgotPassword() {
  return {
    path: getAPIPath('forgot-password'),
  }
}
// Current User Profile
export function profilePath() {
  return {
    path: getAPIPath('profile'),
  }
}

export function profileAvailableToggles() {
  return {
    path: getAPIPath('profile/available_toggles'),
  }
}
// Onboarding
export function awesomePeoplePath() {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath('discover/users/onboarding', params),
    params,
  }
}
export function communitiesPath() {
  const params = { name: 'onboarding', per_page: PER_PAGE }
  return {
    path: getAPIPath('interest_categories/members', params),
    params,
  }
}
export function relationshipBatchPath() {
  return {
    path: getAPIPath('relationships/batches'),
  }
}
// Discover
export function discoverUsers(type) {
  const params = { per_page: PER_PAGE, include_recent_posts: true }
  return {
    path: getAPIPath(`discover/users/${type}`, params),
    params,
  }
}
// Streams
export function friendStream() {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath('streams/friend', params),
    params,
  }
}
export function noiseStream() {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath('streams/noise', params),
    params,
  }
}
// Posts
export function postDetail(idOrToken) {
  const params = { comment_count: PER_PAGE }
  return {
    path: getAPIPath(`posts/${idOrToken}`, params),
    pagingPath: 'comments',
    params,
  }
}
export function lovePost(post) {
  return {
    path: getAPIPath(`posts/${post.id}/loves`),
  }
}
export function unlovePost(post) {
  return {
    path: getAPIPath(`posts/${post.id}/love`),
  }
}
export function deletePost(post) {
  return {
    path: getAPIPath(`posts/${post.id}`),
  }
}
export function flagPost(post, kind) {
  return {
    path: getAPIPath(`posts/${post.id}/flag/${kind}`),
  }
}
export function postLovers(post) {
  const params = { per_page: 10 }
  return {
    path: getAPIPath(`posts/${post.id}/lovers`),
    params,
  }
}
export function postReposters(post) {
  const params = { per_page: 10 }
  return {
    path: getAPIPath(`posts/${post.id}/reposters`),
    params,
  }
}
export function createPost(repostId) {
  const params = {}
  if (repostId) { params.repost_id = repostId }
  return {
    path: getAPIPath('posts', params),
    params,
  }
}
export function postPreviews() {
  return {
    path: getAPIPath('post_previews'),
  }
}
export function userAutocompleter(word) {
  return {
    path: getAPIPath('users/autocomplete', { terms: word.replace(/@|:/ig, '') }),
  }
}
export function loadEmojis() {
  return {
    path: `${ENV.AUTH_DOMAIN}/emojis.json`,
  }
}
// Comments
export function commentsForPost(post) {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath(`posts/${post.id}/comments`, params),
    params,
  }
}
export function deleteComment(comment) {
  return {
    path: getAPIPath(`posts/${comment.postId}/comments/${comment.id}`),
  }
}
export function flagComment(comment, kind) {
  return {
    path: getAPIPath(`posts/${comment.postId}/comments/${comment.id}/flag/${kind}`),
  }
}
// Users
export function userDetail(idOrUsername) {
  const params = { post_count: false }
  return {
    path: getAPIPath(`users/${idOrUsername}`, params),
    params,
  }
}
export function userResources(idOrUsername, resource) {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath(`users/${idOrUsername}/${resource}`, params),
    params,
  }
}
// Search
export function searchPosts(params) {
  return {
    path: getAPIPath('posts', params),
    params,
  }
}
export function searchUsers(params) {
  return {
    path: getAPIPath('users', params),
    params,
  }
}
// Notifications
export function notifications(params = {}) {
  const newParams = { per_page: PER_PAGE, ...params }
  return {
    path: getAPIPath('notifications', newParams),
    newParams,
  }
}

// AVAILABILITY
export function availability() {
  return {
    path: getAPIPath('availability'),
  }
}

// INVITE
export function invite() {
  return {
    path: getAPIPath('invitations', { per_page: 100 }),
  }
}

// RELATIONSHIPS
export function relationshipAdd(userId, priority) {
  return {
    path: getAPIPath(`users/${userId}/add/${priority}`),
  }
}

export { API_VERSION, getAPIPath, PER_PAGE }

<commit_msg>Set pagination to ten comments<commit_after>const API_VERSION = 'v2'
const PER_PAGE = 20
const basePath = () => `${ENV.AUTH_DOMAIN}/api`

function getAPIPath(relPath, queryParams = {}) {
  let path = `${basePath()}/${API_VERSION}/${relPath}`
  const queryArr = []
  for (const param in queryParams) {
    if (queryParams.hasOwnProperty(param)) {
      queryArr.push(`${param}=${queryParams[param]}`)
    }
  }
  if (queryArr.length) {
    path = `${path}?${queryArr.join('&')}`
  }
  return path
}

// Assets
export function s3CredentialsPath() {
  return {
    path: getAPIPath('assets/credentials'),
  }
}
// Authentication
export function accessTokens() {
  return {
    path: `${basePath()}/oauth/token`,
  }
}
export function forgotPassword() {
  return {
    path: getAPIPath('forgot-password'),
  }
}
// Current User Profile
export function profilePath() {
  return {
    path: getAPIPath('profile'),
  }
}

export function profileAvailableToggles() {
  return {
    path: getAPIPath('profile/available_toggles'),
  }
}
// Onboarding
export function awesomePeoplePath() {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath('discover/users/onboarding', params),
    params,
  }
}
export function communitiesPath() {
  const params = { name: 'onboarding', per_page: PER_PAGE }
  return {
    path: getAPIPath('interest_categories/members', params),
    params,
  }
}
export function relationshipBatchPath() {
  return {
    path: getAPIPath('relationships/batches'),
  }
}
// Discover
export function discoverUsers(type) {
  const params = { per_page: PER_PAGE, include_recent_posts: true }
  return {
    path: getAPIPath(`discover/users/${type}`, params),
    params,
  }
}
// Streams
export function friendStream() {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath('streams/friend', params),
    params,
  }
}
export function noiseStream() {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath('streams/noise', params),
    params,
  }
}
// Posts
export function postDetail(idOrToken) {
  const params = { comment_count: PER_PAGE }
  return {
    path: getAPIPath(`posts/${idOrToken}`, params),
    pagingPath: 'comments',
    params,
  }
}
export function lovePost(post) {
  return {
    path: getAPIPath(`posts/${post.id}/loves`),
  }
}
export function unlovePost(post) {
  return {
    path: getAPIPath(`posts/${post.id}/love`),
  }
}
export function deletePost(post) {
  return {
    path: getAPIPath(`posts/${post.id}`),
  }
}
export function flagPost(post, kind) {
  return {
    path: getAPIPath(`posts/${post.id}/flag/${kind}`),
  }
}
export function postLovers(post) {
  const params = { per_page: 10 }
  return {
    path: getAPIPath(`posts/${post.id}/lovers`),
    params,
  }
}
export function postReposters(post) {
  const params = { per_page: 10 }
  return {
    path: getAPIPath(`posts/${post.id}/reposters`),
    params,
  }
}
export function createPost(repostId) {
  const params = {}
  if (repostId) { params.repost_id = repostId }
  return {
    path: getAPIPath('posts', params),
    params,
  }
}
export function postPreviews() {
  return {
    path: getAPIPath('post_previews'),
  }
}
export function userAutocompleter(word) {
  return {
    path: getAPIPath('users/autocomplete', { terms: word.replace(/@|:/ig, '') }),
  }
}
export function loadEmojis() {
  return {
    path: `${ENV.AUTH_DOMAIN}/emojis.json`,
  }
}
// Comments
export function commentsForPost(post) {
  const params = { per_page: 10 }
  return {
    path: getAPIPath(`posts/${post.id}/comments`, params),
    params,
  }
}
export function deleteComment(comment) {
  return {
    path: getAPIPath(`posts/${comment.postId}/comments/${comment.id}`),
  }
}
export function flagComment(comment, kind) {
  return {
    path: getAPIPath(`posts/${comment.postId}/comments/${comment.id}/flag/${kind}`),
  }
}
// Users
export function userDetail(idOrUsername) {
  const params = { post_count: false }
  return {
    path: getAPIPath(`users/${idOrUsername}`, params),
    params,
  }
}
export function userResources(idOrUsername, resource) {
  const params = { per_page: PER_PAGE }
  return {
    path: getAPIPath(`users/${idOrUsername}/${resource}`, params),
    params,
  }
}
// Search
export function searchPosts(params) {
  return {
    path: getAPIPath('posts', params),
    params,
  }
}
export function searchUsers(params) {
  return {
    path: getAPIPath('users', params),
    params,
  }
}
// Notifications
export function notifications(params = {}) {
  const newParams = { per_page: PER_PAGE, ...params }
  return {
    path: getAPIPath('notifications', newParams),
    newParams,
  }
}

// AVAILABILITY
export function availability() {
  return {
    path: getAPIPath('availability'),
  }
}

// INVITE
export function invite() {
  return {
    path: getAPIPath('invitations', { per_page: 100 }),
  }
}

// RELATIONSHIPS
export function relationshipAdd(userId, priority) {
  return {
    path: getAPIPath(`users/${userId}/add/${priority}`),
  }
}

export { API_VERSION, getAPIPath, PER_PAGE }

<|endoftext|>


<commit_before>/*!
    Copyright (C) 2015 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: ivan@reciprocitylabs.com
    Maintained By: ivan@reciprocitylabs.com
*/

(function (can, $) {
  can.Component.extend({
    tag: 'relevant-filter',
    template: can.view(GGRC.mustache_path + '/mapper/relevant_filter.mustache'),
    scope: {
      relevant_menu_item: '@',
      show_all: '@',
      addFilter: function () {
        var menu = this.attr('menu');

        if (this.attr('relevant_menu_item') === 'parent' &&
             Number(this.attr('panel_number')) !== 0 &&
             !this.attr('has_parent')) {
          menu.unshift({
            title_singular: 'Previous objects',
            model_singular: '__previous__'
          });
        }

        this.attr('relevant').push({
          value: false,
          filter: new can.Map(),
          menu: menu,
          model_name: menu[0].model_singular
        });
      },
      menu: can.compute(function () {
        var type = this.attr('type');
        var showAll = /true/i.test(this.attr('show_all'));
        var isAll = type === 'AllObject';
        var mappings;
        var models;

        if (showAll) {
          // find all widget types and manually add Cycle since it's missing
          // convert names to CMS models and prune invalid (undefined)
          models = ['Cycle'];
          models = Array.prototype.concat.apply(models,
              _.values(GGRC.tree_view.base_widgets_by_type));
          models = _.difference(_.unique(models),
                               ['CycleTaskEntry', 'CycleTaskGroupObject']);
          models = _.map(models, function (mapping) {
            return CMS.Models[mapping];
          });
          return _.sortBy(_.compact(models), 'model_singular');
        }
        if (this.attr('search_only') && isAll) {
          mappings = GGRC.tree_view.base_widgets_by_type;
        } else {
          type = isAll ? GGRC.page_model.type : this.attr('type');
          mappings = GGRC.Mappings.get_canonical_mappings_for(type);
        }

        return _.sortBy(_.compact(_.map(_.keys(mappings), function (mapping) {
          return CMS.Models[mapping];
        })), 'model_singular');
      })
    },
    events: {
      init: function () {
        this.setRelevant();
      },
      setRelevant: function () {
        this.scope.attr('relevant').replace([]);
        can.each(this.scope.attr('relevantTo') || [], function (item) {
          var model = CMS.Models[item.type].cache[item.id];
          this.scope.attr('relevant').push({
            value: true,
            filter: model,
            menu: this.scope.attr('menu'),
            model_name: model.constructor.shortName
          });
        }, this);
      },
      '.ui-autocomplete-input autocomplete:select': function (el, ev, data) {
        var index = el.data('index');
        var panel = this.scope.attr('relevant')[index];

        panel.attr('filter', data.item);
        panel.attr('value', true);
      },
      '.remove_filter click': function (el) {
        this.scope.attr('relevant').splice(el.data('index'), 1);
      },
      '{scope.relevant} change': function (list, item, which, type, val, old) {
        this.scope.attr('has_parent',
                        _.findWhere(this.scope.attr('relevant'),
                        {model_name: '__previous__'}));
        if (!/model_name/gi.test(which)) {
          return;
        }
        item.target.attr('filter', new can.Map());
        item.target.attr('value', false);
      }
    }
  });
})(window.can, window.can.$);
<commit_msg>Make 'relevant' options dependant on object type<commit_after>/*!
    Copyright (C) 2015 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: ivan@reciprocitylabs.com
    Maintained By: ivan@reciprocitylabs.com
*/

(function (can, $) {
  can.Component.extend({
    tag: 'relevant-filter',
    template: can.view(GGRC.mustache_path + '/mapper/relevant_filter.mustache'),
    scope: {
      relevant_menu_item: '@',
      show_all: '@',
      addFilter: function () {
        var menu = this.attr('menu');

        if (this.attr('relevant_menu_item') === 'parent' &&
             Number(this.attr('panel_number')) !== 0 &&
             !this.attr('has_parent')) {
          menu.unshift({
            title_singular: 'Previous objects',
            model_singular: '__previous__'
          });
        }

        this.attr('relevant').push({
          value: false,
          filter: new can.Map(),
          menu: menu,
          model_name: menu[0].model_singular
        });
      },
      menu: can.compute(function () {
        var type = this.attr('type');
        var mappings;
        var models;
        if (/true/i.test(this.attr('show_all'))) {
          // find all widget types and manually add Cycle since it's missing
          // convert names to CMS models and prune invalid (undefined)
          models = ['Cycle'];
          models = Array.prototype.concat.apply(models,
              _.values(GGRC.tree_view.base_widgets_by_type));
          models = _.difference(_.unique(models),
                               ['CycleTaskEntry', 'CycleTaskGroupObject']);
          models = _.map(models, function (mapping) {
            return CMS.Models[mapping];
          });
          return _.sortBy(_.compact(models), 'model_singular');
        }

        if (type === 'AllObject') {
          type = GGRC.page_model.type;
        }
        mappings = GGRC.Mappings.get_canonical_mappings_for(type);
        return _.sortBy(_.compact(_.map(_.keys(mappings), function (mapping) {
          return CMS.Models[mapping];
        })), 'model_singular');
      })
    },
    events: {
      init: function () {
        this.setRelevant();
      },
      setRelevant: function () {
        this.scope.attr('relevant').replace([]);
        can.each(this.scope.attr('relevantTo') || [], function (item) {
          var model = CMS.Models[item.type].cache[item.id];
          this.scope.attr('relevant').push({
            value: true,
            filter: model,
            menu: this.scope.attr('menu'),
            model_name: model.constructor.shortName
          });
        }, this);
      },
      '.ui-autocomplete-input autocomplete:select': function (el, ev, data) {
        var index = el.data('index');
        var panel = this.scope.attr('relevant')[index];

        panel.attr('filter', data.item);
        panel.attr('value', true);
      },
      '.remove_filter click': function (el) {
        this.scope.attr('relevant').splice(el.data('index'), 1);
      },
      '{scope.relevant} change': function (list, item, which, type, val, old) {
        this.scope.attr('has_parent',
                        _.findWhere(this.scope.attr('relevant'),
                        {model_name: '__previous__'}));
        if (!/model_name/gi.test(which)) {
          return;
        }
        item.target.attr('filter', new can.Map());
        item.target.attr('value', false);
      }
    }
  });
})(window.can, window.can.$);
<|endoftext|>


<commit_before>import webpack from 'webpack'
import path from 'path'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import AssetsPlugin from 'assets-webpack-plugin'
import ProgressBarPlugin from 'progress-bar-webpack-plugin'
import autoprefixer from 'autoprefixer'
import webpackBaseConfig from './webpack.config.base.babel'

export default {
  ...webpackBaseConfig,

  entry: [
    path.join(process.cwd(), 'src/app/styles/global/app.scss'),
    path.join(process.cwd(), '.noxt/app/app.prod.js'),
  ],

  output: {
    ...webpackBaseConfig.output,
    publicPath: '/build/',
    filename: '[name]-[chunkhash].js',
    chunkFilename: '[name]-[chunkhash].chunk.js',
  },

  module: {
    ...webpackBaseConfig.module,
    rules: [
      ...webpackBaseConfig.module.rules,
      {
        test: /\.js$/,
        exclude: /node_modules|\.git/,
        loader: 'babel-loader',
        options: {
          babelrc: false,
          presets: [
            [
              'es2015',
              {
                modules: false,
                loose: true,
              },
            ],
            'react',
            'stage-0',
          ],
          plugins: [
            'lodash',
            'transform-decorators-legacy',
            'transform-react-constant-elements',
            'transform-react-remove-prop-types',
            'transform-react-pure-class-to-function',
            ["module-resolver", {
              "root": ["./src"],
              "alias": {
                "components": "./src/app/components",
                "containers": "./src/app/containers",
                "pages": "./src/app/pages",
                "utils": "./src/app/utils",
                "styles": "./src/app/styles"
              }
            }]
          ],
        },
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: 'style-loader',
          loader: 'css-loader',
        }),
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: 'style-loader',
          loader: [
            {
              loader: 'css-loader',
              query: {
                modules: true,
                importLoaders: 2,
                localIdentName: '[name]__[local]___[hash:base64:5]',
                minimize: true,
              },
            },
            'postcss-loader',
            {
              loader: 'sass-loader',
              query: {
                includePaths: [path.join(process.cwd(), 'src/app/styles')],
              },
            },
          ],
        }),
      },
      {
        test: /\.(jpg|png|gif)$/,
        loaders: [
          'file-loader',
          'image-webpack-loader?{progressive:true, optimizationLevel: 7, interlaced: false, pngquant:{quality: "65-90", speed: 4}}',
        ],
      },
    ],
  },

  plugins: [
    ...webpackBaseConfig.plugins,
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
        BROWSER: JSON.stringify(true),
      },
    }),
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    }),
    new AssetsPlugin({
      filename: 'assets.json',
      path: path.join(process.cwd(), 'static'),
      prettyPrint: true,
    }),
    new ExtractTextPlugin({
      filename: '[name]-[contenthash].css',
      allChunks: true,
    }),
    new webpack.LoaderOptionsPlugin({
      test: /\.scss$/,
      options: {
        context: process.cwd(),
        postcss: [
          autoprefixer({ browsers: ['last 2 versions', 'IE > 10'] }),
        ],
      },
    }),
    new webpack.DllReferencePlugin({
      context: process.cwd(),
      manifest: require('../../static/build/react-manifest.json'),
    }),
    new ProgressBarPlugin(),
  ],
}
<commit_msg>addd missing alias config for production<commit_after>import webpack from 'webpack'
import path from 'path'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import AssetsPlugin from 'assets-webpack-plugin'
import ProgressBarPlugin from 'progress-bar-webpack-plugin'
import autoprefixer from 'autoprefixer'
import webpackBaseConfig from './webpack.config.base.babel'

export default {
  ...webpackBaseConfig,

  entry: [
    path.join(process.cwd(), 'src/app/styles/global/app.scss'),
    path.join(process.cwd(), '.noxt/app/app.prod.js'),
  ],

  output: {
    ...webpackBaseConfig.output,
    publicPath: '/build/',
    filename: '[name]-[chunkhash].js',
    chunkFilename: '[name]-[chunkhash].chunk.js',
  },

  module: {
    ...webpackBaseConfig.module,
    rules: [
      ...webpackBaseConfig.module.rules,
      {
        test: /\.js$/,
        exclude: /node_modules|\.git/,
        loader: 'babel-loader',
        options: {
          babelrc: false,
          presets: [
            [
              'es2015',
              {
                modules: false,
                loose: true,
              },
            ],
            'react',
            'stage-0',
          ],
          plugins: [
            'lodash',
            'transform-decorators-legacy',
            'transform-react-constant-elements',
            'transform-react-remove-prop-types',
            'transform-react-pure-class-to-function',
            ["module-resolver", {
              "root": ["./src"],
              "alias": {
                "noxt": "./.noxt/",
                "components": "./src/app/components",
                "containers": "./src/app/containers",
                "pages": "./src/app/pages",
                "utils": "./src/app/utils",
                "styles": "./src/app/styles"
              }
            }]
          ],
        },
      },
      {
        test: /\.css$/,
        exclude: /node_modules/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: 'style-loader',
          loader: 'css-loader',
        }),
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        loader: ExtractTextPlugin.extract({
          fallbackLoader: 'style-loader',
          loader: [
            {
              loader: 'css-loader',
              query: {
                modules: true,
                importLoaders: 2,
                localIdentName: '[name]__[local]___[hash:base64:5]',
                minimize: true,
              },
            },
            'postcss-loader',
            {
              loader: 'sass-loader',
              query: {
                includePaths: [path.join(process.cwd(), 'src/app/styles')],
              },
            },
          ],
        }),
      },
      {
        test: /\.(jpg|png|gif)$/,
        loaders: [
          'file-loader',
          'image-webpack-loader?{progressive:true, optimizationLevel: 7, interlaced: false, pngquant:{quality: "65-90", speed: 4}}',
        ],
      },
    ],
  },

  plugins: [
    ...webpackBaseConfig.plugins,
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
        BROWSER: JSON.stringify(true),
      },
    }),
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false,
      },
    }),
    new AssetsPlugin({
      filename: 'assets.json',
      path: path.join(process.cwd(), 'static'),
      prettyPrint: true,
    }),
    new ExtractTextPlugin({
      filename: '[name]-[contenthash].css',
      allChunks: true,
    }),
    new webpack.LoaderOptionsPlugin({
      test: /\.scss$/,
      options: {
        context: process.cwd(),
        postcss: [
          autoprefixer({ browsers: ['last 2 versions', 'IE > 10'] }),
        ],
      },
    }),
    new webpack.DllReferencePlugin({
      context: process.cwd(),
      manifest: require('../../static/build/react-manifest.json'),
    }),
    new ProgressBarPlugin(),
  ],
}
<|endoftext|>


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

var assert = require("assert"),
    sinon = require('sinon'),
    _ = require("underscore"),
    Backbone = require("backbone"),
    grouped_collection = require('../backbone.grouped-collection'),
    collection;

Backbone.VirtualCollection = require('backbone-virtual-collection');
Backbone.buildGroupedCollection = grouped_collection.buildGroupedCollection;

describe('Backbone.GroupedCollection', function () {
  describe('#buildGroupedCollection', function () {
    beforeEach(function () {
      collection = new Backbone.Collection([
        {name: 'Bob', club: 'Penguins'},
        {name: 'Frank', club: 'Penguins'},
        {name: 'Jimmy', club: 'Panthers'}
      ]);
    });

    it('should return an instance of GroupCollection', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert(gc instanceof grouped_collection.GroupCollection);
    });

    it('uses the GroupCollection class if passed the parameter', function () {
      var MyGroupCollection = Backbone.Collection.extend({}),
      gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        },
        GroupCollection: MyGroupCollection
      });
      assert(gc instanceof MyGroupCollection);
    });

    it('uses the GroupModel class if passed the parameter', function () {
      var MyGroupModel = Backbone.Model.extend({}),
      gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        },
        GroupModel: MyGroupModel
      });
      gc.each(function (group) {
        assert(group instanceof MyGroupModel);
      });
    });

    it('groups models according to the groupBy function', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.deepEqual(gc.pluck('id').sort(), ['Penguins', 'Panthers'].sort());
      assert.equal(gc.get('Penguins').vc.length, 2);
      assert.equal(gc.get('Panthers').vc.length, 1);
    });

    it('handles adds to the parent collection correctly', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.get('Panthers').vc.length, 1);
      collection.add({name: 'Hillary', club: 'Panthers'});
      assert.equal(gc.get('Panthers').vc.length, 2);
    });

    it('creates a new group if necesary', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      collection.add([{club: 'Penguins'}, {club: 'Rats'}]);
      assert.equal(gc.length, 3);
      assert.equal(gc.get('Rats').vc.length, 1);
    });

    it('handles remove from the parent collection correctly', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.get('Penguins').vc.length, 2);
      collection.remove(collection.findWhere({name: 'Bob'}));
      assert.equal(gc.get('Penguins').vc.length, 1);
    });

    it('removes a group when it is empty', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      collection.remove(collection.findWhere({club: 'Panthers'}));
      assert.equal(collection.get('Panthers'), undefined);
    });
    it('handles reset of the parent collection correctly', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.get('Penguins').vc.length, 2);

      collection.reset([{club: 'Whales'}, {club: 'Whales'}, {club: 'Cats'}]);

      assert.equal(gc.get('Whales').vc.length, 2);
      assert.equal(gc.get('Cats').vc.length, 1);
      assert.equal(gc.length, 2);
    });

    it('handles changes to the models, what impact their grouping', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });

      collection.findWhere({name: 'Frank'}).set({club: 'HODOR'});

      assert.equal(gc.get('Panthers').vc.length, 1);
      assert.equal(gc.get('Penguins').vc.length, 1);
      assert.equal(gc.get('HODOR').vc.length, 1);
    });
  });
});
<commit_msg>Added test<commit_after>/*global it, describe, before, beforeEach*/

var assert = require("assert"),
    sinon = require('sinon'),
    _ = require("underscore"),
    Backbone = require("backbone"),
    grouped_collection = require('../backbone.grouped-collection'),
    collection;

Backbone.VirtualCollection = require('backbone-virtual-collection');
Backbone.buildGroupedCollection = grouped_collection.buildGroupedCollection;

describe('Backbone.GroupedCollection', function () {
  describe('#buildGroupedCollection', function () {
    beforeEach(function () {
      collection = new Backbone.Collection([
        {name: 'Bob', club: 'Penguins'},
        {name: 'Frank', club: 'Penguins'},
        {name: 'Jimmy', club: 'Panthers'}
      ]);
    });

    it('should return an instance of GroupCollection', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert(gc instanceof grouped_collection.GroupCollection);
    });

    it('uses the GroupCollection class if passed the parameter', function () {
      var MyGroupCollection = Backbone.Collection.extend({}),
      gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        },
        GroupCollection: MyGroupCollection
      });
      assert(gc instanceof MyGroupCollection);
    });

    it('uses the GroupModel class if passed the parameter', function () {
      var MyGroupModel = Backbone.Model.extend({}),
      gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        },
        GroupModel: MyGroupModel
      });
      gc.each(function (group) {
        assert(group instanceof MyGroupModel);
      });
    });

    it('groups models according to the groupBy function', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.deepEqual(gc.pluck('id').sort(), ['Penguins', 'Panthers'].sort());
      assert.equal(gc.get('Penguins').vc.length, 2);
      assert.equal(gc.get('Panthers').vc.length, 1);
    });

    it('handles adds to the parent collection correctly', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.get('Panthers').vc.length, 1);
      collection.add({name: 'Hillary', club: 'Panthers'});
      assert.equal(gc.get('Panthers').vc.length, 2);
    });

    it('creates a new group if necesary', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      collection.add([{club: 'Penguins'}, {club: 'Rats'}]);
      assert.equal(gc.length, 3);
      assert.equal(gc.get('Rats').vc.length, 1);
    });

    it('handles remove from the parent collection correctly', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.get('Penguins').vc.length, 2);
      collection.remove(collection.findWhere({name: 'Bob'}));
      assert.equal(gc.get('Penguins').vc.length, 1);
    });

    it('removes a group when it is empty', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.length, 2);
      collection.remove(collection.findWhere({club: 'Panthers'}));
      assert.equal(collection.get('Panthers'), undefined);
      assert.equal(gc.length, 1);
    });

    it('removes a group when it does not match the grouping condition anymore', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.length, 2);
      collection.get(collection.findWhere({club: 'Panthers'})).set({club: 'Penguins'});
      assert.equal(gc.length, 1);
    });

    it('handles reset of the parent collection correctly', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });
      assert.equal(gc.get('Penguins').vc.length, 2);

      collection.reset([{club: 'Whales'}, {club: 'Whales'}, {club: 'Cats'}]);

      assert.equal(gc.get('Whales').vc.length, 2);
      assert.equal(gc.get('Cats').vc.length, 1);
      assert.equal(gc.length, 2);
    });

    it('handles changes to the models, what impact their grouping', function () {
      var gc = Backbone.buildGroupedCollection({
        collection: collection,
        groupBy: function (model) {
          return model.get('club');
        }
      });

      collection.findWhere({name: 'Frank'}).set({club: 'HODOR'});

      assert.equal(gc.get('Panthers').vc.length, 1);
      assert.equal(gc.get('Penguins').vc.length, 1);
      assert.equal(gc.get('HODOR').vc.length, 1);
    });
  });
});
<|endoftext|>


<commit_before>/*global _:true */
/*jshint maxcomplexity:13*/

'use strict';
var fs = require('fs');
var _ = require('underscore');
var generateGraph = require('./graph.js');

function logResults(buffer, s) {
    buffer.push(s);
    console.log(s);
}

/**
 * Append some text to the log file.
 * @param filename name of the file to append text to
 * @param text content to be appended
 */
function appendToFile(filename, text) {
    // Write file content blocking the nodeJs execution loop
    fs.appendFileSync(filename, text + '\n');
}

/**
 * Write results separated with comma into csv file
 * @param filename name of the file to append the results to
 * @param results memory measurement result object
 */
function writeResults(filename, results) {
    var data = results.jsHeapSizeLimit + ','  + results.usedJSHeapSize + ',' + results.totalJSHeapSize;
    appendToFile(filename, data);
}

/**
 * Initialize memory measurement of a given test case.
 * Returns an object with convenience methods to trigger memory measurement and garbage collection.
 * Results of these operations will automatically be logged into the files specified by options.
 *
 * @param test Testcase instance to be measured
 * @param options plain object containing settings whether log, csv or png graph file should be created
 *              Default options object: {writeLogFile: true, writeCsvFile: true, generateGraph: true}
 * @param time Start time string for use in filenames
 * @param graphData array of objects containing the line name and the corresponding array of values: eg. [{name: 'totalJSHeapSize', values: []},{name: 'usedJSHeapSize', values: []}]
 */
module.exports.startMemoryMeasurement = function(test, options, time, graphData){
    return (function () {
        options = options || {};

        _.defaults(options, {
            writeLogFile: true,
            writeCsvFile: true,
			generateGraph: true
        });

        var memoryResult, initialResult;

        var filename = 'M_' + test.description.replace(/\W/gi, '-') + '_' + time + '.txt';
        var csvFilename = 'M_' + test.description.replace(/\W/gi, '-') + '_' + time + '.csv';

        if (options.writeLogFile) {
            console.log('Started memory test, logging to file ' + filename);
            appendToFile(filename, 'Memory Measurement Start (' + new Date().toISOString() + ')');
        }

        if (options.writeCsvFile) {
            console.log('Started memory test, logging to CSV file ' + csvFilename);
            appendToFile(csvFilename, 'jsHeapSizeLimit,usedJSHeapSize,totalJSHeapSize');
        }

        test.after(function(){
            if (options.writeLogFile) {
                appendToFile(filename, 'Memory Measurement End (' + new Date().toISOString() + ')');
            }
        });

        return {
            measureMemory: function(isLast){
                return module.exports.measureMemory(isLast ? initialResult : memoryResult, isLast, filename, csvFilename, options, graphData).then(function (result) {
                    if (_.isUndefined(initialResult)){
                        initialResult = result;
                    }

                    memoryResult = result;

                    return result;
                });
            },
            callGarbageCollection: function(){
                return module.exports.callGarbageCollection(filename, options);
            }
        };
    }());
};

/**
 * Execute a given task multiple times and measure memory usage after each iteration.
 *
 *
 * @param test Testcase instance to be measured
 * @param iterations Number how often testFn should be executed
 * @param testFn Task to be performed multiple times, e.g. clicking a button.
 *              The first parameter this function receives is the current iteration count.
 * @param options Plain object specifying behavior
 *                Default options object:
 *                {
 *                  initialPostGcSleep: 2500, // Milliseconds to sleep after GC before iterations start
 *                  finalPostTestSleep: 1500, // Milliseconds to sleep before final GC after all iterations happened
 *                  finalPostGcSleep: 4500, // Milliseconds to sleep between last GC and final memory measurement
 *                  writeLogFile: true, // Boolean whether a txt-log file should be created
 *                  writeCsvFile: true // Boolean whether a csv file with raw memory usage values should be created
 *                  generateGraph: true // Boolean whether to generate memory results graph in a PNG file
 *                  graphWidth: 800 // integer value for graph width
 *                  graphHeight: 600 // integer value for graph height
 *                }
 */
module.exports.runTestFunction = function(test, iterations, testFn, options) {
    options = options || {};

    _.defaults(options, {
        initialPostGcSleep: 2500,
        finalPostTestSleep: 1500,
        finalPostGcSleep: 4500,
        writeLogFile: true,
        writeCsvFile: true,
		generateGraph: true,
        graphWidth: 800,
        graphHeight: 600
    });

    var time = new Date().toISOString().replace(/\W/gi, '-');
    var graphFilename = 'M_' + test.description.replace(/\W/gi, '-') + '_' + time + '.png';

    var graphData;

    if (graphFilename && options.generateGraph) {
        console.log('Started memory test. At the end of the test a graph will be generated in file ' + graphFilename);
    }

    if (options.generateGraph) {
       graphData = [{name: 'totalJSHeapSize', values: []},{name: 'usedJSHeapSize', values: []}];
    }

    var memoryMeasure = module.exports.startMemoryMeasurement(test, options, time, graphData);

    var runAndMeasure = function (iterationNo) {
        return function(){
            testFn.call(this, iterationNo);
            memoryMeasure.measureMemory();
        }
    };

    memoryMeasure.callGarbageCollection().then(function () {
        console.log('Initial GC done, sleeping for ' + options.initialPostGcSleep + ' ms');
        browser.sleep(options.initialPostGcSleep);
        memoryMeasure.measureMemory();
    });

    // run the iterations and measure memory increments.
    for (var i = 0; iterations > i; i++) {
        browser.driver.call(runAndMeasure(i));
    }

    // allow browser to settle down, call garbage collection and measure final memory.
    browser.sleep(options.finalPostTestSleep);
    memoryMeasure.callGarbageCollection().then(function () {
        console.log('Final GC done, sleeping for ' + options.finalPostGcSleep + ' ms');
        browser.sleep(options.finalPostGcSleep);
        memoryMeasure.measureMemory(true).then(function() {
            if (graphFilename && options.generateGraph) {
                generateGraph.generateGraphImage(graphFilename, graphData, options.graphWidth, options.graphHeight);
            }
        });
    });

};

/**
 * measureMemory, a function to allow measuring of currently used memory. Works only in Chrome with special parameters.
 * @param comparisonMeasurement - optional to allow comparison with previous memory reading.
 * @param isLast - optional flag to add log output indicating that this is the last reading.
 * @param filename - optional name of file to write log output to.
 * @param csvFilename - optional name of file to write CSV output to.
 * @param options - optional object containing the following properties (default true): writeLogFile, writeCsvFile, generateGraph
 * @param graphData - array of 2 objects containing the memory results.
 * @returns {promise} the promise is resolved by .then
 *  for example:  var promise = html5.memory.measureMemory();

 promise.then(function (result) {

                            myResult = result;

 */
module.exports.measureMemory = function (comparisonMeasurement, isLast, filename, csvFilename, options, graphData) {
    options = options || {};

    _.defaults(options, {
        writeLogFile: true,
        writeCsvFile: true,
		generateGraph: true
    });

    var d = protractor.promise.defer();
    browser.driver.executeScript(function () {
        return window.performance.memory;
    }).then(function (result) {
        var buffer = [];
        if (!comparisonMeasurement) {
            if (options.writeLogFile) {
                logResults(buffer, '-----Initial memory sizes ---- ');
                logResults(buffer, 'Heap limit size: ' + result.jsHeapSizeLimit);
                logResults(buffer, 'Used heap size : ' + result.usedJSHeapSize);
                logResults(buffer, 'Total heap size: ' + result.totalJSHeapSize);
                logResults(buffer, '------------------------------------ ');
            }
        } else {
            if (isLast) {
                if (options.writeLogFile) {
                    logResults(buffer, '* Final memory comparison to initial sizes *');
                }
            } else {
                if (options.writeLogFile) {
                    logResults(buffer, '-----Comparing memory sizes ---- ');
                }
            }
            //compare to passed result
            var fl, fh , ft;

            fl = result.jsHeapSizeLimit - comparisonMeasurement.jsHeapSizeLimit;
            fh = result.usedJSHeapSize - comparisonMeasurement.usedJSHeapSize;
            ft = result.totalJSHeapSize - comparisonMeasurement.totalJSHeapSize;

            if (options.writeLogFile) {
                logResults(buffer, 'Heap limit change: ' + (fl > 0 ? '+' : '') + fl + ' (now at ' + result.jsHeapSizeLimit + ')');
                logResults(buffer, 'Used heap change ' + (fh > 0 ? '+' : '') + fh + ' (now at ' + result.usedJSHeapSize + ')');
                logResults(buffer, 'Total heap change: ' + (ft > 0 ? '+' : '') + ft + ' (now at ' + result.totalJSHeapSize + ')');

                if ((fl === 0) && (fh === 0) && (ft === 0)) {
                    logResults(buffer, '!!! No change in memory usage !!!');
                }
            }
        }

        if (filename && options.writeLogFile) {
            appendToFile(filename, buffer.join('\n'));
        }

        if (csvFilename && options.writeCsvFile) {
            writeResults(csvFilename, result);
        }

		if (options.generateGraph) {
			// Push results to data object
			graphData[0].values.push(result.totalJSHeapSize);
			graphData[1].values.push(result.usedJSHeapSize);
        }

        d.fulfill(result);
    });
    return d.promise;
};

/**
 * Trigger a garbage collection in Chrome browser.
 *
 * @param filename optional filename to log messages to
 * @param options Plain object containing a property `writeLogFile` whether messages should be logged into file (true) or not (false)
 * @returns {promise} Promise to be resolved after GC triggered
 */
module.exports.callGarbageCollection = function (filename, options) {
    options = options || {};

    _.defaults(options, {
        writeLogFile: true
    });

    var d = protractor.promise.defer();
    browser.driver.executeScript(function () {
        return window.gc();
    }).then(function (result) {
        var buffer = [];
        logResults(buffer, 'Garbage collection invoked');
        if (filename && options.writeLogFile) {
            appendToFile(filename, buffer.join('\n'));
        }
        d.fulfill(result);
    });
    return d.promise;
};
<commit_msg>Fixed formatting<commit_after>/*global _:true */
/*jshint maxcomplexity:13*/

'use strict';
var fs = require('fs');
var _ = require('underscore');
var generateGraph = require('./graph.js');

function logResults(buffer, s) {
    buffer.push(s);
    console.log(s);
}

/**
 * Append some text to the log file.
 * @param filename name of the file to append text to
 * @param text content to be appended
 */
function appendToFile(filename, text) {
    // Write file content blocking the nodeJs execution loop
    fs.appendFileSync(filename, text + '\n');
}

/**
 * Write results separated with comma into csv file
 * @param filename name of the file to append the results to
 * @param results memory measurement result object
 */
function writeResults(filename, results) {
    var data = results.jsHeapSizeLimit + ','  + results.usedJSHeapSize + ',' + results.totalJSHeapSize;
    appendToFile(filename, data);
}

/**
 * Initialize memory measurement of a given test case.
 * Returns an object with convenience methods to trigger memory measurement and garbage collection.
 * Results of these operations will automatically be logged into the files specified by options.
 *
 * @param test Testcase instance to be measured
 * @param options plain object containing settings whether log, csv or png graph file should be created
 *              Default options object: {writeLogFile: true, writeCsvFile: true, generateGraph: true}
 * @param time Start time string for use in filenames
 * @param graphData array of objects containing the line name and the corresponding array of values: eg. [{name: 'totalJSHeapSize', values: []},{name: 'usedJSHeapSize', values: []}]
 */
module.exports.startMemoryMeasurement = function(test, options, time, graphData){
    return (function () {
        options = options || {};

        _.defaults(options, {
            writeLogFile: true,
            writeCsvFile: true,
			generateGraph: true
        });

        var memoryResult, initialResult;

        var filename = 'M_' + test.description.replace(/\W/gi, '-') + '_' + time + '.txt';
        var csvFilename = 'M_' + test.description.replace(/\W/gi, '-') + '_' + time + '.csv';

        if (options.writeLogFile) {
            console.log('Started memory test, logging to file ' + filename);
            appendToFile(filename, 'Memory Measurement Start (' + new Date().toISOString() + ')');
        }

        if (options.writeCsvFile) {
            console.log('Started memory test, logging to CSV file ' + csvFilename);
            appendToFile(csvFilename, 'jsHeapSizeLimit,usedJSHeapSize,totalJSHeapSize');
        }

        test.after(function(){
            if (options.writeLogFile) {
                appendToFile(filename, 'Memory Measurement End (' + new Date().toISOString() + ')');
            }
        });

        return {
            measureMemory: function(isLast){
                return module.exports.measureMemory(isLast ? initialResult : memoryResult, isLast, filename, csvFilename, options, graphData).then(function (result) {
                    if (_.isUndefined(initialResult)){
                        initialResult = result;
                    }

                    memoryResult = result;

                    return result;
                });
            },
            callGarbageCollection: function(){
                return module.exports.callGarbageCollection(filename, options);
            }
        };
    }());
};

/**
 * Execute a given task multiple times and measure memory usage after each iteration.
 *
 *
 * @param test Testcase instance to be measured
 * @param iterations Number how often testFn should be executed
 * @param testFn Task to be performed multiple times, e.g. clicking a button.
 *              The first parameter this function receives is the current iteration count.
 * @param options Plain object specifying behavior
 *                Default options object:
 *                {
 *                  initialPostGcSleep: 2500, // Milliseconds to sleep after GC before iterations start
 *                  finalPostTestSleep: 1500, // Milliseconds to sleep before final GC after all iterations happened
 *                  finalPostGcSleep: 4500, // Milliseconds to sleep between last GC and final memory measurement
 *                  writeLogFile: true, // Boolean whether a txt-log file should be created
 *                  writeCsvFile: true // Boolean whether a csv file with raw memory usage values should be created
 *                  generateGraph: true // Boolean whether to generate memory results graph in a PNG file
 *                  graphWidth: 800 // integer value for graph width
 *                  graphHeight: 600 // integer value for graph height
 *                }
 */
module.exports.runTestFunction = function(test, iterations, testFn, options) {
    options = options || {};

    _.defaults(options, {
        initialPostGcSleep: 2500,
        finalPostTestSleep: 1500,
        finalPostGcSleep: 4500,
        writeLogFile: true,
        writeCsvFile: true,
		generateGraph: true,
        graphWidth: 800,
        graphHeight: 600
    });

    var time = new Date().toISOString().replace(/\W/gi, '-');
    var graphFilename = 'M_' + test.description.replace(/\W/gi, '-') + '_' + time + '.png';

    var graphData;

    if (graphFilename && options.generateGraph) {
        console.log('Started memory test. At the end of the test a graph will be generated in file ' + graphFilename);
    }

    if (options.generateGraph) {
       graphData = [{name: 'totalJSHeapSize', values: []},{name: 'usedJSHeapSize', values: []}];
    }

    var memoryMeasure = module.exports.startMemoryMeasurement(test, options, time, graphData);

    var runAndMeasure = function (iterationNo) {
        return function(){
            testFn.call(this, iterationNo);
            memoryMeasure.measureMemory();
        };
    };

    memoryMeasure.callGarbageCollection().then(function () {
        console.log('Initial GC done, sleeping for ' + options.initialPostGcSleep + ' ms');
        browser.sleep(options.initialPostGcSleep);
        memoryMeasure.measureMemory();
    });

    // run the iterations and measure memory increments.
    for (var i = 0; iterations > i; i++) {
        browser.driver.call(runAndMeasure(i));
    }

    // allow browser to settle down, call garbage collection and measure final memory.
    browser.sleep(options.finalPostTestSleep);
    memoryMeasure.callGarbageCollection().then(function () {
        console.log('Final GC done, sleeping for ' + options.finalPostGcSleep + ' ms');
        browser.sleep(options.finalPostGcSleep);
        memoryMeasure.measureMemory(true).then(function() {
            if (graphFilename && options.generateGraph) {
                generateGraph.generateGraphImage(graphFilename, graphData, options.graphWidth, options.graphHeight);
            }
        });
    });
};

/**
 * measureMemory, a function to allow measuring of currently used memory. Works only in Chrome with special parameters.
 * @param comparisonMeasurement - optional to allow comparison with previous memory reading.
 * @param isLast - optional flag to add log output indicating that this is the last reading.
 * @param filename - optional name of file to write log output to.
 * @param csvFilename - optional name of file to write CSV output to.
 * @param options - optional object containing the following properties (default true): writeLogFile, writeCsvFile, generateGraph
 * @param graphData - array of 2 objects containing the memory results.
 * @returns {promise} the promise is resolved by .then
 *  for example:  var promise = html5.memory.measureMemory();

 promise.then(function (result) {

                            myResult = result;

 */
module.exports.measureMemory = function (comparisonMeasurement, isLast, filename, csvFilename, options, graphData) {
    options = options || {};

    _.defaults(options, {
        writeLogFile: true,
        writeCsvFile: true,
		generateGraph: true
    });

    var d = protractor.promise.defer();
    browser.driver.executeScript(function () {
        return window.performance.memory;
    }).then(function (result) {
        var buffer = [];
        if (!comparisonMeasurement) {
            if (options.writeLogFile) {
                logResults(buffer, '-----Initial memory sizes ---- ');
                logResults(buffer, 'Heap limit size: ' + result.jsHeapSizeLimit);
                logResults(buffer, 'Used heap size : ' + result.usedJSHeapSize);
                logResults(buffer, 'Total heap size: ' + result.totalJSHeapSize);
                logResults(buffer, '------------------------------------ ');
            }
        } else {
            if (isLast) {
                if (options.writeLogFile) {
                    logResults(buffer, '* Final memory comparison to initial sizes *');
                }
            } else {
                if (options.writeLogFile) {
                    logResults(buffer, '-----Comparing memory sizes ---- ');
                }
            }
            //compare to passed result
            var fl, fh , ft;

            fl = result.jsHeapSizeLimit - comparisonMeasurement.jsHeapSizeLimit;
            fh = result.usedJSHeapSize - comparisonMeasurement.usedJSHeapSize;
            ft = result.totalJSHeapSize - comparisonMeasurement.totalJSHeapSize;

            if (options.writeLogFile) {
                logResults(buffer, 'Heap limit change: ' + (fl > 0 ? '+' : '') + fl + ' (now at ' + result.jsHeapSizeLimit + ')');
                logResults(buffer, 'Used heap change ' + (fh > 0 ? '+' : '') + fh + ' (now at ' + result.usedJSHeapSize + ')');
                logResults(buffer, 'Total heap change: ' + (ft > 0 ? '+' : '') + ft + ' (now at ' + result.totalJSHeapSize + ')');

                if ((fl === 0) && (fh === 0) && (ft === 0)) {
                    logResults(buffer, '!!! No change in memory usage !!!');
                }
            }
        }

        if (filename && options.writeLogFile) {
            appendToFile(filename, buffer.join('\n'));
        }

        if (csvFilename && options.writeCsvFile) {
            writeResults(csvFilename, result);
        }

		if (options.generateGraph) {
			// Push results to data object
			graphData[0].values.push(result.totalJSHeapSize);
			graphData[1].values.push(result.usedJSHeapSize);
        }

        d.fulfill(result);
    });
    return d.promise;
};

/**
 * Trigger a garbage collection in Chrome browser.
 *
 * @param filename optional filename to log messages to
 * @param options Plain object containing a property `writeLogFile` whether messages should be logged into file (true) or not (false)
 * @returns {promise} Promise to be resolved after GC triggered
 */
module.exports.callGarbageCollection = function (filename, options) {
    options = options || {};

    _.defaults(options, {
        writeLogFile: true
    });

    var d = protractor.promise.defer();
    browser.driver.executeScript(function () {
        return window.gc();
    }).then(function (result) {
        var buffer = [];
        logResults(buffer, 'Garbage collection invoked');
        if (filename && options.writeLogFile) {
            appendToFile(filename, buffer.join('\n'));
        }
        d.fulfill(result);
    });
    return d.promise;
};
<|endoftext|>


<commit_before>import webpack from 'webpack'
import cssnano from 'cssnano'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import config from '../config'
import _debug from 'debug'
import CreatePackageJsonPlugin from '../bin/create-package-json'

const debug = _debug('app:webpack:config')
const paths = config.utils_paths
const {__DEV__, __PROD__, __TEST__} = config.globals

const packageName = require(paths.base('package.json')).name

debug('Create configuration.')
const webpackConfig = {
  name: 'client',
  target: 'web',
  devtool: config.compiler_devtool,
  resolve: {
    root: paths.client(),
    extensions: ['', '.js', '.jsx', '.json']
  },
  module: {},
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM'
  }
}
// ------------------------------------
// Entry Points
// ------------------------------------
const APP_ENTRY_PATH = paths.client('main.js')

webpackConfig.entry = {
  app: __DEV__
    ? [APP_ENTRY_PATH, `webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`]
    : [APP_ENTRY_PATH],
}

// ------------------------------------
// Bundle Output
// ------------------------------------
webpackConfig.output = {
  filename: `index.js`,
  path: paths.dist(),
  publicPath: config.compiler_public_path
}
if (__PROD__) {
  webpackConfig.output.publicPath = `/nice2/node_modules/${packageName}/`
}

// ------------------------------------
// Plugins
// ------------------------------------
webpackConfig.plugins = [
  new webpack.DefinePlugin(config.globals)
]

if (__DEV__) {
  webpackConfig.plugins.push(
    new HtmlWebpackPlugin({
      template: paths.client('index.html'),
      hash: false,
      filename: 'index.html',
      inject: 'body',
      minify: {
        collapseWhitespace: true
      }
    })
  )

  debug('Enable plugins for live development (HMR, NoErrors).')
  webpackConfig.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  )
} else if (__PROD__) {
  debug('Enable plugins for production (OccurenceOrder, Dedupe & UglifyJS).')
  webpackConfig.plugins.push(
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        unused: true,
        dead_code: true,
        warnings: false
      }
    }),
    new CreatePackageJsonPlugin({
      sourcePackageFile: paths.base('package.json'),
      targetDir: paths.dist()
    })
  )
}

// ------------------------------------
// Pre-Loaders
// ------------------------------------
/*
[ NOTE ]
We no longer use eslint-loader due to it severely impacting build
times for larger projects. `npm run lint` still exists to aid in
deploy processes (such as with CI), and it's recommended that you
use a linting plugin for your IDE in place of this loader.

If you do wish to continue using the loader, you can uncomment
the code below and run `npm i --save-dev eslint-loader`. This code
will be removed in a future release.

webpackConfig.module.preLoaders = [{
  test: /\.(js|jsx)$/,
  loader: 'eslint',
  exclude: /node_modules/
}]

webpackConfig.eslint = {
  configFile: paths.base('.eslintrc'),
  emitWarning: __DEV__
}
*/

// ------------------------------------
// Loaders
// ------------------------------------
// JavaScript / JSON
webpackConfig.module.loaders = [{
  test: /\.(js|jsx)$/,
  exclude: /node_modules/,
  loader: 'babel',
  query: {
    cacheDirectory: true,
    plugins: ['transform-runtime'],
    presets: ['es2015', 'react', 'stage-0'],
    env: {
      production: {
        plugins: [
          'transform-react-remove-prop-types',
          'transform-react-constant-elements'
        ]
      }
    }
  }
},
{
  test: /\.json$/,
  loader: 'json'
}]

// ------------------------------------
// Style Loaders
// ------------------------------------
// We use cssnano with the postcss loader, so we tell
// css-loader not to duplicate minimization.
const BASE_CSS_LOADER = 'css?sourceMap&-minimize'

// Add any packge names here whose styles need to be treated as CSS modules.
// These paths will be combined into a single regex.
const PATHS_TO_TREAT_AS_CSS_MODULES = [
  // 'react-toolbox', (example)
]

// If config has CSS modules enabled, treat this project's styles as CSS modules.
if (config.compiler_css_modules) {
  PATHS_TO_TREAT_AS_CSS_MODULES.push(
    paths.client().replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&')
  )
}

const isUsingCSSModules = !!PATHS_TO_TREAT_AS_CSS_MODULES.length
const cssModulesRegex = new RegExp(`(${PATHS_TO_TREAT_AS_CSS_MODULES.join('|')})`)

// Loaders for styles that need to be treated as CSS modules.
if (isUsingCSSModules) {
  const cssModulesLoader = [
    BASE_CSS_LOADER,
    'modules',
    'importLoaders=1',
    'localIdentName=[name]__[local]___[hash:base64:5]'
  ].join('&')

  webpackConfig.module.loaders.push({
    test: /\.scss$/,
    include: cssModulesRegex,
    loaders: [
      'style',
      cssModulesLoader,
      'postcss',
      'sass?sourceMap'
    ]
  })

  webpackConfig.module.loaders.push({
    test: /\.css$/,
    include: cssModulesRegex,
    loaders: [
      'style',
      cssModulesLoader,
      'postcss'
    ]
  })
}

// Loaders for files that should not be treated as CSS modules.
const excludeCSSModules = isUsingCSSModules ? cssModulesRegex : false
webpackConfig.module.loaders.push({
  test: /\.scss$/,
  exclude: excludeCSSModules,
  loaders: [
    'style',
    BASE_CSS_LOADER,
    'postcss',
    'sass?sourceMap'
  ]
})
webpackConfig.module.loaders.push({
  test: /\.css$/,
  exclude: excludeCSSModules,
  loaders: [
    'style',
    BASE_CSS_LOADER,
    'postcss'
  ]
})

// ------------------------------------
// Style Configuration
// ------------------------------------
webpackConfig.sassLoader = {
  includePaths: paths.client('styles')
}

webpackConfig.postcss = [
  cssnano({
    autoprefixer: {
      add: true,
      remove: true,
      browsers: ['last 2 versions']
    },
    discardComments: {
      removeAll: true
    },
    discardUnused: false,
    mergeIdents: false,
    reduceIdents: false,
    safe: true,
    sourcemap: true
  })
]

// File loaders
/* eslint-disable */
webpackConfig.module.loaders.push(
  { test: /\.woff(\?.*)?$/,  loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
  { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
  { test: /\.otf(\?.*)?$/,   loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' },
  { test: /\.ttf(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
  { test: /\.eot(\?.*)?$/,   loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
  { test: /\.svg(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
  { test: /\.(png|jpg)$/,    loader: 'url?limit=8192' }
)
/* eslint-enable */

// ------------------------------------
// Finalize Configuration
// ------------------------------------
// when we don't know the public path (we know it only when HMR is enabled [in development]) we
// need to use the extractTextPlugin to fix this issue:
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
if (!__DEV__) {
  debug('Apply ExtractTextPlugin to CSS loaders.')
  webpackConfig.module.loaders.filter((loader) =>
    loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0]))
  ).forEach((loader) => {
    const [first, ...rest] = loader.loaders
    loader.loader = ExtractTextPlugin.extract(first, rest.join('!'))
    Reflect.deleteProperty(loader, 'loaders')
  })

  webpackConfig.plugins.push(
    new ExtractTextPlugin('app.css', {
      allChunks: true
    })
  )
}

export default webpackConfig
<commit_msg>Fix tests: only declare React as external if not test env<commit_after>import webpack from 'webpack'
import cssnano from 'cssnano'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import config from '../config'
import _debug from 'debug'
import CreatePackageJsonPlugin from '../bin/create-package-json'

const debug = _debug('app:webpack:config')
const paths = config.utils_paths
const {__DEV__, __PROD__, __TEST__} = config.globals

const packageName = require(paths.base('package.json')).name

debug('Create configuration.')
const webpackConfig = {
  name: 'client',
  target: 'web',
  devtool: config.compiler_devtool,
  resolve: {
    root: paths.client(),
    extensions: ['', '.js', '.jsx', '.json']
  },
  module: {}
}

if (!__TEST__) {
  webpackConfig.externals = {
    react: 'React',
    'react-dom': 'ReactDOM'
  }
}

// ------------------------------------
// Entry Points
// ------------------------------------
const APP_ENTRY_PATH = paths.client('main.js')

webpackConfig.entry = {
  app: __DEV__
    ? [APP_ENTRY_PATH, `webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`]
    : [APP_ENTRY_PATH],
}

// ------------------------------------
// Bundle Output
// ------------------------------------
webpackConfig.output = {
  filename: `index.js`,
  path: paths.dist(),
  publicPath: config.compiler_public_path
}
if (__PROD__) {
  webpackConfig.output.publicPath = `/nice2/node_modules/${packageName}/`
}

// ------------------------------------
// Plugins
// ------------------------------------
webpackConfig.plugins = [
  new webpack.DefinePlugin(config.globals)
]

if (__DEV__) {
  webpackConfig.plugins.push(
    new HtmlWebpackPlugin({
      template: paths.client('index.html'),
      hash: false,
      filename: 'index.html',
      inject: 'body',
      minify: {
        collapseWhitespace: true
      }
    })
  )

  debug('Enable plugins for live development (HMR, NoErrors).')
  webpackConfig.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  )
} else if (__PROD__) {
  debug('Enable plugins for production (OccurenceOrder, Dedupe & UglifyJS).')
  webpackConfig.plugins.push(
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        unused: true,
        dead_code: true,
        warnings: false
      }
    }),
    new CreatePackageJsonPlugin({
      sourcePackageFile: paths.base('package.json'),
      targetDir: paths.dist()
    })
  )
}

// ------------------------------------
// Pre-Loaders
// ------------------------------------
/*
[ NOTE ]
We no longer use eslint-loader due to it severely impacting build
times for larger projects. `npm run lint` still exists to aid in
deploy processes (such as with CI), and it's recommended that you
use a linting plugin for your IDE in place of this loader.

If you do wish to continue using the loader, you can uncomment
the code below and run `npm i --save-dev eslint-loader`. This code
will be removed in a future release.

webpackConfig.module.preLoaders = [{
  test: /\.(js|jsx)$/,
  loader: 'eslint',
  exclude: /node_modules/
}]

webpackConfig.eslint = {
  configFile: paths.base('.eslintrc'),
  emitWarning: __DEV__
}
*/

// ------------------------------------
// Loaders
// ------------------------------------
// JavaScript / JSON
webpackConfig.module.loaders = [{
  test: /\.(js|jsx)$/,
  exclude: /node_modules/,
  loader: 'babel',
  query: {
    cacheDirectory: true,
    plugins: ['transform-runtime'],
    presets: ['es2015', 'react', 'stage-0'],
    env: {
      production: {
        plugins: [
          'transform-react-remove-prop-types',
          'transform-react-constant-elements'
        ]
      }
    }
  }
},
{
  test: /\.json$/,
  loader: 'json'
}]

// ------------------------------------
// Style Loaders
// ------------------------------------
// We use cssnano with the postcss loader, so we tell
// css-loader not to duplicate minimization.
const BASE_CSS_LOADER = 'css?sourceMap&-minimize'

// Add any packge names here whose styles need to be treated as CSS modules.
// These paths will be combined into a single regex.
const PATHS_TO_TREAT_AS_CSS_MODULES = [
  // 'react-toolbox', (example)
]

// If config has CSS modules enabled, treat this project's styles as CSS modules.
if (config.compiler_css_modules) {
  PATHS_TO_TREAT_AS_CSS_MODULES.push(
    paths.client().replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&')
  )
}

const isUsingCSSModules = !!PATHS_TO_TREAT_AS_CSS_MODULES.length
const cssModulesRegex = new RegExp(`(${PATHS_TO_TREAT_AS_CSS_MODULES.join('|')})`)

// Loaders for styles that need to be treated as CSS modules.
if (isUsingCSSModules) {
  const cssModulesLoader = [
    BASE_CSS_LOADER,
    'modules',
    'importLoaders=1',
    'localIdentName=[name]__[local]___[hash:base64:5]'
  ].join('&')

  webpackConfig.module.loaders.push({
    test: /\.scss$/,
    include: cssModulesRegex,
    loaders: [
      'style',
      cssModulesLoader,
      'postcss',
      'sass?sourceMap'
    ]
  })

  webpackConfig.module.loaders.push({
    test: /\.css$/,
    include: cssModulesRegex,
    loaders: [
      'style',
      cssModulesLoader,
      'postcss'
    ]
  })
}

// Loaders for files that should not be treated as CSS modules.
const excludeCSSModules = isUsingCSSModules ? cssModulesRegex : false
webpackConfig.module.loaders.push({
  test: /\.scss$/,
  exclude: excludeCSSModules,
  loaders: [
    'style',
    BASE_CSS_LOADER,
    'postcss',
    'sass?sourceMap'
  ]
})
webpackConfig.module.loaders.push({
  test: /\.css$/,
  exclude: excludeCSSModules,
  loaders: [
    'style',
    BASE_CSS_LOADER,
    'postcss'
  ]
})

// ------------------------------------
// Style Configuration
// ------------------------------------
webpackConfig.sassLoader = {
  includePaths: paths.client('styles')
}

webpackConfig.postcss = [
  cssnano({
    autoprefixer: {
      add: true,
      remove: true,
      browsers: ['last 2 versions']
    },
    discardComments: {
      removeAll: true
    },
    discardUnused: false,
    mergeIdents: false,
    reduceIdents: false,
    safe: true,
    sourcemap: true
  })
]

// File loaders
/* eslint-disable */
webpackConfig.module.loaders.push(
  { test: /\.woff(\?.*)?$/,  loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
  { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
  { test: /\.otf(\?.*)?$/,   loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' },
  { test: /\.ttf(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
  { test: /\.eot(\?.*)?$/,   loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
  { test: /\.svg(\?.*)?$/,   loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
  { test: /\.(png|jpg)$/,    loader: 'url?limit=8192' }
)
/* eslint-enable */

// ------------------------------------
// Finalize Configuration
// ------------------------------------
// when we don't know the public path (we know it only when HMR is enabled [in development]) we
// need to use the extractTextPlugin to fix this issue:
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
if (!__DEV__) {
  debug('Apply ExtractTextPlugin to CSS loaders.')
  webpackConfig.module.loaders.filter((loader) =>
    loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0]))
  ).forEach((loader) => {
    const [first, ...rest] = loader.loaders
    loader.loader = ExtractTextPlugin.extract(first, rest.join('!'))
    Reflect.deleteProperty(loader, 'loaders')
  })

  webpackConfig.plugins.push(
    new ExtractTextPlugin('app.css', {
      allChunks: true
    })
  )
}

export default webpackConfig
<|endoftext|>