<commit_before>/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(createSpecFilter|createStartFn)" }] */

'use strict'

// Save link to native Date object
// before it might be mocked by the user
var _Date = Date

/**
 * Decision maker for whether a stack entry is considered external to jasmine and karma.
 * @param  {String}  entry Error stack entry.
 * @return {Boolean}       True if external, False otherwise.
 */
function isExternalStackEntry (entry) {
  return !!entry &&
  // entries related to jasmine and karma-jasmine:
  !/\/(jasmine-core|karma-jasmine)\//.test(entry) &&
  // karma specifics, e.g. "at http://localhost:7018/karma.js:185"
  !/\/(karma.js|context.html):/.test(entry)
}

/**
 * Returns relevant stack entries.
 * @param  {Array} stack frames
 * @return {Array}        A list of relevant stack entries.
 */
function getRelevantStackFrom (stack) {
  var filteredStack = []
  var relevantStack = []

  for (var i = 0; i < stack.length; i += 1) {
    if (isExternalStackEntry(stack[i])) {
      filteredStack.push(stack[i])
    }
  }

  // If the filtered stack is empty, i.e. the error originated entirely from within jasmine or karma, then the whole stack
  // should be relevant.
  if (filteredStack.length === 0) {
    filteredStack = stack
  }

  for (i = 0; i < filteredStack.length; i += 1) {
    if (filteredStack[i]) {
      relevantStack.push(filteredStack[i])
    }
  }

  return relevantStack
}

/**
 * Custom formatter for a failed step.
 *
 * Different browsers report stack trace in different ways. This function
 * attempts to provide a concise, relevant error message by removing the
 * unnecessary stack traces coming from the testing framework itself as well
 * as possible repetition.
 *
 * @see    https://github.com/karma-runner/karma-jasmine/issues/60
 * @param  {Object} step Step object with stack and message properties.
 * @return {String}      Formatted step.
 */
function formatFailedStep (step) {
  var relevantMessage = []
  var relevantStack = []

  // Safari/Firefox seems to have no stack trace,
  // so we just return the error message and if available
  // construct a stacktrace out of filename and lineno:
  if (!step.stack) {
    if (step.filename) {
      var stackframe = step.filename
      if (step.lineno) {
        stackframe = stackframe + ':' + step.lineno
      }
      relevantStack.push(stackframe)
    }
    relevantMessage.push(step.message)
    return relevantMessage.concat(relevantStack).join('\n')
  }

  // Remove the message prior to processing the stack to prevent issues like
  // https://github.com/karma-runner/karma-jasmine/issues/79
  var stackframes = step.stack.split('\n')
  var messageOnStack = null
  if (stackframes[0].indexOf(step.message) !== -1) {
    // Remove the message if it is in the stack string (eg Chrome)
    messageOnStack = stackframes.shift()
  }
  // Filter frames
  var relevantStackFrames = getRelevantStackFrom(stackframes)
  if (messageOnStack) {
    // Put the message back if we removed it.
    relevantStackFrames.unshift(messageOnStack)
  } else {
    // The stack did not have the step.message so add it.
    relevantStackFrames.unshift(step.message)
  }

  return relevantStackFrames.join('\n')
}

function debugUrl (description) {
  // A link to re-run just one failed test case.
  return window.location.origin + '/debug.html?spec=' + encodeURIComponent(description)
}

function SuiteNode (name, parent) {
  this.name = name
  this.parent = parent
  this.children = []

  this.addChild = function (name) {
    var suite = new SuiteNode(name, this)
    this.children.push(suite)
    return suite
  }
}

function processSuite (suite, pointer) {
  var child
  var childPointer

  for (var i = 0; i < suite.children.length; i++) {
    child = suite.children[i]

    if (child.children) {
      childPointer = pointer[child.description] = { _: [] }
      processSuite(child, childPointer)
    } else {
      if (!pointer._) {
        pointer._ = []
      }
      pointer._.push(child.description)
    }
  }
}

function getAllSpecNames (topSuite) {
  var specNames = {}

  processSuite(topSuite, specNames)

  return specNames
}

/**
 * Very simple reporter for Jasmine.
 */
function KarmaReporter (tc, jasmineEnv) {
  var currentSuite = new SuiteNode()

  var startTimeCurrentSpec = new _Date().getTime()

  function handleGlobalErrors (result) {
    if (result.failedExpectations && result.failedExpectations.length) {
      var message = 'An error was thrown in afterAll'
      var steps = result.failedExpectations
      for (var i = 0, l = steps.length; i < l; i++) {
        message += '\n' + formatFailedStep(steps[i])
      }

      tc.error(message)
    }
  }

  /**
   * Jasmine 2.0 dispatches the following events:
   *
   *  - jasmineStarted
   *  - jasmineDone
   *  - suiteStarted
   *  - suiteDone
   *  - specStarted
   *  - specDone
   */

  this.jasmineStarted = function (data) {
    // TODO(vojta): Do not send spec names when polling.
    tc.info({
      total: data.totalSpecsDefined,
      specs: getAllSpecNames(jasmineEnv.topSuite())
    })
  }

  this.jasmineDone = function (result) {
    result = result || {}

    // Any errors in top-level afterAll blocks are given here.
    handleGlobalErrors(result)

    tc.complete({
      order: result.order,
      coverage: window.__coverage__
    })
  }

  this.suiteStarted = function (result) {
    currentSuite = currentSuite.addChild(result.description)
  }

  this.suiteDone = function (result) {
    // In the case of xdescribe, only "suiteDone" is fired.
    // We need to skip that.
    if (result.description !== currentSuite.name) {
      return
    }

    // Any errors in afterAll blocks are given here, except for top-level
    // afterAll blocks.
    handleGlobalErrors(result)

    currentSuite = currentSuite.parent
  }

  this.specStarted = function () {
    startTimeCurrentSpec = new _Date().getTime()
  }

  this.specDone = function (specResult) {
    var skipped = specResult.status === 'disabled' || specResult.status === 'pending' || specResult.status === 'excluded'
    var result = {
      fullName: specResult.fullName,
      description: specResult.description,
      id: specResult.id,
      log: [],
      skipped: skipped,
      disabled: specResult.status === 'disabled' || specResult.status === 'excluded',
      pending: specResult.status === 'pending',
      success: specResult.failedExpectations.length === 0,
      suite: [],
      time: skipped ? 0 : new _Date().getTime() - startTimeCurrentSpec,
      executedExpectationsCount: specResult.failedExpectations.length + specResult.passedExpectations.length
    }

    // generate ordered list of (nested) suite names
    var suitePointer = currentSuite
    while (suitePointer.parent) {
      result.suite.unshift(suitePointer.name)
      suitePointer = suitePointer.parent
    }

    if (!result.success) {
      var steps = specResult.failedExpectations
      for (var i = 0, l = steps.length; i < l; i++) {
        result.log.push(formatFailedStep(steps[i]))
      }

      // Report the name of fhe failing spec so the reporter can emit a debug url.
      result.debug_url = debugUrl(specResult.fullName)
    }

    // When failSpecWithNoExpectations is true, Jasmine will report specs without expectations as failed
    if (result.executedExpectationsCount === 0 && specResult.status === 'failed') {
      result.success = false
      result.log.push('Spec has no expectations')
    }

    tc.result(result)
    delete specResult.startTime
  }
}

/**
 * Extract grep option from karma config
 * @param {[Array|string]} clientArguments The karma client arguments
 * @return {string} The value of grep option by default empty string
 */
var getGrepOption = function (clientArguments) {
  var grepRegex = /^--grep=(.*)$/

  if (Object.prototype.toString.call(clientArguments) === '[object Array]') {
    var indexOfGrep = indexOf(clientArguments, '--grep')

    if (indexOfGrep !== -1) {
      return clientArguments[indexOfGrep + 1]
    }

    return map(filter(clientArguments, function (arg) {
      return grepRegex.test(arg)
    }), function (arg) {
      return arg.replace(grepRegex, '$1')
    })[0] || ''
  } else if (typeof clientArguments === 'string') {
    var match = /--grep=([^=]+)/.exec(clientArguments)

    return match ? match[1] : ''
  }
}

var createRegExp = function (filter) {
  filter = filter || ''
  if (filter === '') {
    return new RegExp() // to match all
  }

  var regExp = /^[/](.*)[/]([gmixXsuUAJD]*)$/ // pattern to check whether the string is RegExp pattern

  var parts = regExp.exec(filter)
  if (parts === null) {
    return new RegExp(filter.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')) // escape functional symbols
  }

  var patternExpression = parts[1]
  var patternSwitches = parts[2]
  return new RegExp(patternExpression, patternSwitches)
}

function getGrepSpecsToRun (clientConfig, specs) {
  var grepOption = getGrepOption(clientConfig.args)
  if (grepOption) {
    var regExp = createRegExp(grepOption)
    return filter(specs, function specFilter (spec) {
      return regExp.test(spec.getFullName())
    })
  }
}

function parseQueryParams (location) {
  var params = {}
  if (location && Object.prototype.hasOwnProperty.call(location, 'search')) {
    var pairs = location.search.substr(1).split('&')
    for (var i = 0; i < pairs.length; i++) {
      var keyValue = pairs[i].split('=')
      params[decodeURIComponent(keyValue[0])] =
          decodeURIComponent(keyValue[1])
    }
  }
  return params
}

function getId (s) {
  return s.id
}

function getSpecsByName (specs, name) {
  specs = specs.filter(function (s) {
    return s.name === name
  })
  if (specs.length === 0) {
    throw new Error('No spec found with name: "' + name + '"')
  }
  return specs
}

function getDebugSpecToRun (location, specs) {
  var queryParams = parseQueryParams(location)
  var spec = queryParams.spec
  if (spec) {
    // A single spec has been requested by name for debugging.
    return getSpecsByName(specs, spec)
  }
}

function getSpecsToRunForCurrentShard (specs, shardIndex, totalShards) {
  if (specs.length < totalShards) {
    throw new Error(
      'More shards (' + totalShards + ') than test specs (' + specs.length +
      ')')
  }

  // Just do a simple sharding strategy of dividing the number of specs
  // equally.
  var firstSpec = Math.floor(specs.length * shardIndex / totalShards)
  var lastSpec = Math.floor(specs.length * (shardIndex + 1) / totalShards)
  return specs.slice(firstSpec, lastSpec)
}

function getShardedSpecsToRun (specs, clientConfig) {
  var shardIndex = clientConfig.shardIndex
  var totalShards = clientConfig.totalShards
  if (shardIndex != null && totalShards != null) {
    // Sharded mode - Run only the subset of the specs corresponding to the
    // current shard.
    return getSpecsToRunForCurrentShard(
      specs, Number(shardIndex), Number(totalShards))
  }
}

/**
 * Create jasmine spec filter
 * @param {Object} clientConfig karma config
 * @param {!Object} jasmineEnv
 */
var KarmaSpecFilter = function (clientConfig, jasmineEnv) {
  /**
   * Walk the test suite tree depth first and collect all test specs
   * @param {!Object} jasmineEnv
   * @return {!Array<string>} All possible tests.
   */
  function getAllSpecs (jasmineEnv) {
    var specs = []
    var stack = [jasmineEnv.topSuite()]
    var currentNode
    while ((currentNode = stack.pop())) {
      if (currentNode.children) {
        // jasmine.Suite
        stack = stack.concat(currentNode.children)
      } else if (currentNode.id) {
        // jasmine.Spec
        specs.unshift(currentNode)
      }
    }

    return specs
  }

  /**
   * Filter the specs with URL search params and config.
   * @param {!Object} location property 'search' from URL.
   * @param {!Object} clientConfig karma client config
   * @param {!Object} jasmineEnv
   * @return {!Array<string>}
   */
  function getSpecsToRun (location, clientConfig, jasmineEnv) {
    var specs = getAllSpecs(jasmineEnv).map(function (spec) {
      spec.name = spec.getFullName()
      return spec
    })

    if (!specs || !specs.length) {
      return []
    }

    return getGrepSpecsToRun(clientConfig, specs) ||
          getDebugSpecToRun(location, specs) ||
          getShardedSpecsToRun(specs, clientConfig) ||
          specs
  }

  this.specIdsToRun =
    getSpecsToRun(window.location, clientConfig, jasmineEnv).map(getId)

  this.matches = function (spec) {
    return this.specIdsToRun.indexOf(spec.id) !== -1
  }
}

/**
 * Configure jasmine specFilter
 *
 * This function is invoked from the wrapper.
 * @see  adapter.wrapper
 *
 * @param {Object} config The karma config
 * @param {Object} jasmineEnv jasmine environment object
 */
var createSpecFilter = function (config, jasmineEnv) {
  var karmaSpecFilter = new KarmaSpecFilter(config, jasmineEnv)

  var specFilter = function (spec) {
    return karmaSpecFilter.matches(spec)
  }

  return specFilter
}

/**
 * Karma starter function factory.
 *
 * This function is invoked from the wrapper.
 * @see  adapter.wrapper
 *
 * @param  {Object}   karma        Karma runner instance.
 * @param  {Object}   [jasmineEnv] Optional Jasmine environment for testing.
 * @return {Function}              Karma starter function.
 */
function createStartFn (karma, jasmineEnv) {
  // This function will be assigned to `window.__karma__.start`:
  return function () {
    var clientConfig = karma.config || {}
    var jasmineConfig = clientConfig.jasmine || {}

    jasmineEnv = jasmineEnv || window.jasmine.getEnv()

    jasmineConfig.specFilter = createSpecFilter(clientConfig, jasmineEnv)

    jasmineEnv.configure(jasmineConfig)

    window.jasmine.DEFAULT_TIMEOUT_INTERVAL = jasmineConfig.timeoutInterval ||
      window.jasmine.DEFAULT_TIMEOUT_INTERVAL
    jasmineEnv.addReporter(new KarmaReporter(karma, jasmineEnv))
    jasmineEnv.execute()
  }
}

function indexOf (collection, find, i /* opt */) {
  if (collection.indexOf) {
    return collection.indexOf(find, i)
  }

  if (i === undefined) { i = 0 }
  if (i < 0) { i += collection.length }
  if (i < 0) { i = 0 }
  for (var n = collection.length; i < n; i++) {
    if (i in collection && collection[i] === find) {
      return i
    }
  }
  return -1
}

function filter (collection, filter, that /* opt */) {
  if (collection.filter) {
    return collection.filter(filter, that)
  }

  var other = []
  var v
  for (var i = 0, n = collection.length; i < n; i++) {
    if (i in collection && filter.call(that, v = collection[i], i, collection)) {
      other.push(v)
    }
  }
  return other
}

function map (collection, mapper, that /* opt */) {
  if (collection.map) {
    return collection.map(mapper, that)
  }

  var other = new Array(collection.length)
  for (var i = 0, n = collection.length; i < n; i++) {
    if (i in collection) {
      other[i] = mapper.call(that, collection[i], i, collection)
    }
  }
  return other
}
<commit_msg>fix(adapter): do not emit debug url unless in a browser (#263)<commit_after>/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(createSpecFilter|createStartFn)" }] */

'use strict'

// Save link to native Date object
// before it might be mocked by the user
var _Date = Date

/**
 * Decision maker for whether a stack entry is considered external to jasmine and karma.
 * @param  {String}  entry Error stack entry.
 * @return {Boolean}       True if external, False otherwise.
 */
function isExternalStackEntry (entry) {
  return !!entry &&
  // entries related to jasmine and karma-jasmine:
  !/\/(jasmine-core|karma-jasmine)\//.test(entry) &&
  // karma specifics, e.g. "at http://localhost:7018/karma.js:185"
  !/\/(karma.js|context.html):/.test(entry)
}

/**
 * Returns relevant stack entries.
 * @param  {Array} stack frames
 * @return {Array}        A list of relevant stack entries.
 */
function getRelevantStackFrom (stack) {
  var filteredStack = []
  var relevantStack = []

  for (var i = 0; i < stack.length; i += 1) {
    if (isExternalStackEntry(stack[i])) {
      filteredStack.push(stack[i])
    }
  }

  // If the filtered stack is empty, i.e. the error originated entirely from within jasmine or karma, then the whole stack
  // should be relevant.
  if (filteredStack.length === 0) {
    filteredStack = stack
  }

  for (i = 0; i < filteredStack.length; i += 1) {
    if (filteredStack[i]) {
      relevantStack.push(filteredStack[i])
    }
  }

  return relevantStack
}

/**
 * Custom formatter for a failed step.
 *
 * Different browsers report stack trace in different ways. This function
 * attempts to provide a concise, relevant error message by removing the
 * unnecessary stack traces coming from the testing framework itself as well
 * as possible repetition.
 *
 * @see    https://github.com/karma-runner/karma-jasmine/issues/60
 * @param  {Object} step Step object with stack and message properties.
 * @return {String}      Formatted step.
 */
function formatFailedStep (step) {
  var relevantMessage = []
  var relevantStack = []

  // Safari/Firefox seems to have no stack trace,
  // so we just return the error message and if available
  // construct a stacktrace out of filename and lineno:
  if (!step.stack) {
    if (step.filename) {
      var stackframe = step.filename
      if (step.lineno) {
        stackframe = stackframe + ':' + step.lineno
      }
      relevantStack.push(stackframe)
    }
    relevantMessage.push(step.message)
    return relevantMessage.concat(relevantStack).join('\n')
  }

  // Remove the message prior to processing the stack to prevent issues like
  // https://github.com/karma-runner/karma-jasmine/issues/79
  var stackframes = step.stack.split('\n')
  var messageOnStack = null
  if (stackframes[0].indexOf(step.message) !== -1) {
    // Remove the message if it is in the stack string (eg Chrome)
    messageOnStack = stackframes.shift()
  }
  // Filter frames
  var relevantStackFrames = getRelevantStackFrom(stackframes)
  if (messageOnStack) {
    // Put the message back if we removed it.
    relevantStackFrames.unshift(messageOnStack)
  } else {
    // The stack did not have the step.message so add it.
    relevantStackFrames.unshift(step.message)
  }

  return relevantStackFrames.join('\n')
}

function debugUrl (description) {
  // A link to re-run just one failed test case.
  return window.location.origin + '/debug.html?spec=' + encodeURIComponent(description)
}

function SuiteNode (name, parent) {
  this.name = name
  this.parent = parent
  this.children = []

  this.addChild = function (name) {
    var suite = new SuiteNode(name, this)
    this.children.push(suite)
    return suite
  }
}

function processSuite (suite, pointer) {
  var child
  var childPointer

  for (var i = 0; i < suite.children.length; i++) {
    child = suite.children[i]

    if (child.children) {
      childPointer = pointer[child.description] = { _: [] }
      processSuite(child, childPointer)
    } else {
      if (!pointer._) {
        pointer._ = []
      }
      pointer._.push(child.description)
    }
  }
}

function getAllSpecNames (topSuite) {
  var specNames = {}

  processSuite(topSuite, specNames)

  return specNames
}

/**
 * Very simple reporter for Jasmine.
 */
function KarmaReporter (tc, jasmineEnv) {
  var currentSuite = new SuiteNode()

  var startTimeCurrentSpec = new _Date().getTime()

  function handleGlobalErrors (result) {
    if (result.failedExpectations && result.failedExpectations.length) {
      var message = 'An error was thrown in afterAll'
      var steps = result.failedExpectations
      for (var i = 0, l = steps.length; i < l; i++) {
        message += '\n' + formatFailedStep(steps[i])
      }

      tc.error(message)
    }
  }

  /**
   * Jasmine 2.0 dispatches the following events:
   *
   *  - jasmineStarted
   *  - jasmineDone
   *  - suiteStarted
   *  - suiteDone
   *  - specStarted
   *  - specDone
   */

  this.jasmineStarted = function (data) {
    // TODO(vojta): Do not send spec names when polling.
    tc.info({
      total: data.totalSpecsDefined,
      specs: getAllSpecNames(jasmineEnv.topSuite())
    })
  }

  this.jasmineDone = function (result) {
    result = result || {}

    // Any errors in top-level afterAll blocks are given here.
    handleGlobalErrors(result)

    tc.complete({
      order: result.order,
      coverage: window.__coverage__
    })
  }

  this.suiteStarted = function (result) {
    currentSuite = currentSuite.addChild(result.description)
  }

  this.suiteDone = function (result) {
    // In the case of xdescribe, only "suiteDone" is fired.
    // We need to skip that.
    if (result.description !== currentSuite.name) {
      return
    }

    // Any errors in afterAll blocks are given here, except for top-level
    // afterAll blocks.
    handleGlobalErrors(result)

    currentSuite = currentSuite.parent
  }

  this.specStarted = function () {
    startTimeCurrentSpec = new _Date().getTime()
  }

  this.specDone = function (specResult) {
    var skipped = specResult.status === 'disabled' || specResult.status === 'pending' || specResult.status === 'excluded'
    var result = {
      fullName: specResult.fullName,
      description: specResult.description,
      id: specResult.id,
      log: [],
      skipped: skipped,
      disabled: specResult.status === 'disabled' || specResult.status === 'excluded',
      pending: specResult.status === 'pending',
      success: specResult.failedExpectations.length === 0,
      suite: [],
      time: skipped ? 0 : new _Date().getTime() - startTimeCurrentSpec,
      executedExpectationsCount: specResult.failedExpectations.length + specResult.passedExpectations.length
    }

    // generate ordered list of (nested) suite names
    var suitePointer = currentSuite
    while (suitePointer.parent) {
      result.suite.unshift(suitePointer.name)
      suitePointer = suitePointer.parent
    }

    if (!result.success) {
      var steps = specResult.failedExpectations
      for (var i = 0, l = steps.length; i < l; i++) {
        result.log.push(formatFailedStep(steps[i]))
      }

      if (typeof window !== 'undefined' && window.location && window.location.origin) {
        // Report the name of fhe failing spec so the reporter can emit a debug url.
        result.debug_url = debugUrl(specResult.fullName)
      }
    }

    // When failSpecWithNoExpectations is true, Jasmine will report specs without expectations as failed
    if (result.executedExpectationsCount === 0 && specResult.status === 'failed') {
      result.success = false
      result.log.push('Spec has no expectations')
    }

    tc.result(result)
    delete specResult.startTime
  }
}

/**
 * Extract grep option from karma config
 * @param {[Array|string]} clientArguments The karma client arguments
 * @return {string} The value of grep option by default empty string
 */
var getGrepOption = function (clientArguments) {
  var grepRegex = /^--grep=(.*)$/

  if (Object.prototype.toString.call(clientArguments) === '[object Array]') {
    var indexOfGrep = indexOf(clientArguments, '--grep')

    if (indexOfGrep !== -1) {
      return clientArguments[indexOfGrep + 1]
    }

    return map(filter(clientArguments, function (arg) {
      return grepRegex.test(arg)
    }), function (arg) {
      return arg.replace(grepRegex, '$1')
    })[0] || ''
  } else if (typeof clientArguments === 'string') {
    var match = /--grep=([^=]+)/.exec(clientArguments)

    return match ? match[1] : ''
  }
}

var createRegExp = function (filter) {
  filter = filter || ''
  if (filter === '') {
    return new RegExp() // to match all
  }

  var regExp = /^[/](.*)[/]([gmixXsuUAJD]*)$/ // pattern to check whether the string is RegExp pattern

  var parts = regExp.exec(filter)
  if (parts === null) {
    return new RegExp(filter.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')) // escape functional symbols
  }

  var patternExpression = parts[1]
  var patternSwitches = parts[2]
  return new RegExp(patternExpression, patternSwitches)
}

function getGrepSpecsToRun (clientConfig, specs) {
  var grepOption = getGrepOption(clientConfig.args)
  if (grepOption) {
    var regExp = createRegExp(grepOption)
    return filter(specs, function specFilter (spec) {
      return regExp.test(spec.getFullName())
    })
  }
}

function parseQueryParams (location) {
  var params = {}
  if (location && Object.prototype.hasOwnProperty.call(location, 'search')) {
    var pairs = location.search.substr(1).split('&')
    for (var i = 0; i < pairs.length; i++) {
      var keyValue = pairs[i].split('=')
      params[decodeURIComponent(keyValue[0])] =
          decodeURIComponent(keyValue[1])
    }
  }
  return params
}

function getId (s) {
  return s.id
}

function getSpecsByName (specs, name) {
  specs = specs.filter(function (s) {
    return s.name === name
  })
  if (specs.length === 0) {
    throw new Error('No spec found with name: "' + name + '"')
  }
  return specs
}

function getDebugSpecToRun (location, specs) {
  var queryParams = parseQueryParams(location)
  var spec = queryParams.spec
  if (spec) {
    // A single spec has been requested by name for debugging.
    return getSpecsByName(specs, spec)
  }
}

function getSpecsToRunForCurrentShard (specs, shardIndex, totalShards) {
  if (specs.length < totalShards) {
    throw new Error(
      'More shards (' + totalShards + ') than test specs (' + specs.length +
      ')')
  }

  // Just do a simple sharding strategy of dividing the number of specs
  // equally.
  var firstSpec = Math.floor(specs.length * shardIndex / totalShards)
  var lastSpec = Math.floor(specs.length * (shardIndex + 1) / totalShards)
  return specs.slice(firstSpec, lastSpec)
}

function getShardedSpecsToRun (specs, clientConfig) {
  var shardIndex = clientConfig.shardIndex
  var totalShards = clientConfig.totalShards
  if (shardIndex != null && totalShards != null) {
    // Sharded mode - Run only the subset of the specs corresponding to the
    // current shard.
    return getSpecsToRunForCurrentShard(
      specs, Number(shardIndex), Number(totalShards))
  }
}

/**
 * Create jasmine spec filter
 * @param {Object} clientConfig karma config
 * @param {!Object} jasmineEnv
 */
var KarmaSpecFilter = function (clientConfig, jasmineEnv) {
  /**
   * Walk the test suite tree depth first and collect all test specs
   * @param {!Object} jasmineEnv
   * @return {!Array<string>} All possible tests.
   */
  function getAllSpecs (jasmineEnv) {
    var specs = []
    var stack = [jasmineEnv.topSuite()]
    var currentNode
    while ((currentNode = stack.pop())) {
      if (currentNode.children) {
        // jasmine.Suite
        stack = stack.concat(currentNode.children)
      } else if (currentNode.id) {
        // jasmine.Spec
        specs.unshift(currentNode)
      }
    }

    return specs
  }

  /**
   * Filter the specs with URL search params and config.
   * @param {!Object} location property 'search' from URL.
   * @param {!Object} clientConfig karma client config
   * @param {!Object} jasmineEnv
   * @return {!Array<string>}
   */
  function getSpecsToRun (location, clientConfig, jasmineEnv) {
    var specs = getAllSpecs(jasmineEnv).map(function (spec) {
      spec.name = spec.getFullName()
      return spec
    })

    if (!specs || !specs.length) {
      return []
    }

    return getGrepSpecsToRun(clientConfig, specs) ||
          getDebugSpecToRun(location, specs) ||
          getShardedSpecsToRun(specs, clientConfig) ||
          specs
  }

  this.specIdsToRun =
    getSpecsToRun(window.location, clientConfig, jasmineEnv).map(getId)

  this.matches = function (spec) {
    return this.specIdsToRun.indexOf(spec.id) !== -1
  }
}

/**
 * Configure jasmine specFilter
 *
 * This function is invoked from the wrapper.
 * @see  adapter.wrapper
 *
 * @param {Object} config The karma config
 * @param {Object} jasmineEnv jasmine environment object
 */
var createSpecFilter = function (config, jasmineEnv) {
  var karmaSpecFilter = new KarmaSpecFilter(config, jasmineEnv)

  var specFilter = function (spec) {
    return karmaSpecFilter.matches(spec)
  }

  return specFilter
}

/**
 * Karma starter function factory.
 *
 * This function is invoked from the wrapper.
 * @see  adapter.wrapper
 *
 * @param  {Object}   karma        Karma runner instance.
 * @param  {Object}   [jasmineEnv] Optional Jasmine environment for testing.
 * @return {Function}              Karma starter function.
 */
function createStartFn (karma, jasmineEnv) {
  // This function will be assigned to `window.__karma__.start`:
  return function () {
    var clientConfig = karma.config || {}
    var jasmineConfig = clientConfig.jasmine || {}

    jasmineEnv = jasmineEnv || window.jasmine.getEnv()

    jasmineConfig.specFilter = createSpecFilter(clientConfig, jasmineEnv)

    jasmineEnv.configure(jasmineConfig)

    window.jasmine.DEFAULT_TIMEOUT_INTERVAL = jasmineConfig.timeoutInterval ||
      window.jasmine.DEFAULT_TIMEOUT_INTERVAL
    jasmineEnv.addReporter(new KarmaReporter(karma, jasmineEnv))
    jasmineEnv.execute()
  }
}

function indexOf (collection, find, i /* opt */) {
  if (collection.indexOf) {
    return collection.indexOf(find, i)
  }

  if (i === undefined) { i = 0 }
  if (i < 0) { i += collection.length }
  if (i < 0) { i = 0 }
  for (var n = collection.length; i < n; i++) {
    if (i in collection && collection[i] === find) {
      return i
    }
  }
  return -1
}

function filter (collection, filter, that /* opt */) {
  if (collection.filter) {
    return collection.filter(filter, that)
  }

  var other = []
  var v
  for (var i = 0, n = collection.length; i < n; i++) {
    if (i in collection && filter.call(that, v = collection[i], i, collection)) {
      other.push(v)
    }
  }
  return other
}

function map (collection, mapper, that /* opt */) {
  if (collection.map) {
    return collection.map(mapper, that)
  }

  var other = new Array(collection.length)
  for (var i = 0, n = collection.length; i < n; i++) {
    if (i in collection) {
      other[i] = mapper.call(that, collection[i], i, collection)
    }
  }
  return other
}
<|endoftext|>


<commit_before>/**
 * 分词器接口
 *
 * @author 老雷<leizongmin@gmail.com>
 * @version 0.1
 */

var fs = require('fs');
var path = require('path');
var POSTAG = require('./POSTAG');
var Tokenizer = require('./Tokenizer');
var Optimizer = require('./Optimizer');

var debug = console.log;

/**
 * 创建分词器接口
*/
var Segment = module.exports = function () {
	this.POSTAG = POSTAG;	// 词性
	this.DICT = {};			// 词典表
	this.modules = {
		tokenizer:	[],		// 分词模块
		optimizer:	[]		// 优化模块
	}
	this.tokenizer = new Tokenizer(this);
	this.optimizer = new Optimizer(this);
}

/**
 * 载入分词模块
 *
 * @param {string|array|object} module 模块名称(数组)或模块对象
 * @return {Segment}
 */
Segment.prototype.use = function (module) {
	if (module instanceof Array) {
		for (var i in module)
			this.use(module[i]);
	}
	else {
		if (typeof module == 'string') {
			var filename = path.resolve(__dirname, 'module', module + '.js');
			if (!path.existsSync(filename))
				throw Error('Cannot find module "' + module + '".');
			else
				module = require(filename);
		}
		// 初始化并注册模块
		module.init(this);
		this.modules[module.type].push(module);
	}

	return this;
}

/**
 * 载入字典文件
 *
 * @param {string} name 字典文件名
 * @param {string} type 类型
 * @param {bool} convert_to_lower 是否全部转换为小写
 * @return {Segment}
 */
Segment.prototype.loadDict = function (name, type, convert_to_lower) {
	var filename = path.resolve(__dirname, '../dicts', name);
	if (!type)	type = 'TABLE';			// 默认为TABLE
	if (!path.existsSync(filename))
		throw Error('Cannot find dict file "' + filename + '".');
	else {
		// 初始化词典
		if (!this.DICT[type])
			this.DICT[type] = {};
		if (!this.DICT[type + '2'])
			this.DICT[type + '2'] = {};
		var TABLE = this.DICT[type];			// 词典表  '词' => {属性}
		var TABLE2 = this.DICT[type + '2'];	// 词典表  '长度' => '词' => 属性
		// 导入数据
		var POSTAG = this.POSTAG;
		var data = fs.readFileSync(filename, 'utf8');
		if (convert_to_lower)
			data = data.toLowerCase();
		var lines = data.split(/\r?\n/);

		for (var i = 0, line; line = lines[i]; i++) {
			var blocks = line.split('|');
			if (blocks.length > 2) {
				var w = blocks[0].trim();
				var p = Number(blocks[1]);
				var f = Number(blocks[2]);

				TABLE[w] = {f: f, p: p};
				if (!TABLE2[w.length])
					TABLE2[w.length] = {};
				TABLE2[w.length][w] = {f: f, p: p};
			}
		}
		return this;
	}
}

/**
 * 取词典表
 *
 * @param {string} type 类型
 * @return {object}
 */
Segment.prototype.getDict = function (type) {
	return this.DICT[type];
}

/**
 * 使用默认的识别模块和字典文件
 *
 * @return {Segment}
 */
Segment.prototype.useDefault = function () {
	this
		// 识别模块
		// 强制分割类单词识别
		.use('URLTokenizer')			// URL识别
		.use('WildcardTokenizer')		// 通配符，必须在标点符号识别之前
		.use('PunctuationTokenizer')	// 标点符号识别
		.use('ForeignTokenizer')		// 外文字符、数字识别，必须在标点符号识别之后
		// 中文单词识别
		.use('DictTokenizer')			// 词典识别
		.use('ChsNameTokenizer')		// 人名识别，建议在词典识别之后

		// 优化模块
		.use('EmailOptimizer')			// 邮箱地址识别
		.use('ChsNameOptimizer')		// 人名识别优化
		.use('DictOptimizer')			// 词典识别优化
		.use('DatetimeOptimizer')		// 日期时间识别优化

		// 字典文件
		.loadDict('dict.txt')			// 盘古词典
		.loadDict('dict2.txt')			// 扩展词典（用于调整原盘古词典）
		.loadDict('names.txt')			// 常见名词、人名
		.loadDict('wildcard.txt', 'WILDCARD', true)		// 通配符
	;
	return this;
}

/**
 * 开始分词
 *
 * @param {string} text 文本
 * @return {array}
 */
Segment.prototype.doSegment = function (text) {
	// 将文本按照换行符分割成多段，并逐一分词
	var sections = text.replace(/\r?\n(\r?\n)*/, '\n').split(/\r?\n/);
	var ret = [];
	for (var i in sections) {
		var section = sections[i].trim();
		if (section.length < 1)
			continue;
		// ======================================
		// 分词
		var sret = this.tokenizer.split(section, this.modules.tokenizer);

		// 优化
		sret = this.optimizer.doOptimize(sret, this.modules.optimizer);

		// ======================================
		// 连接分词结果
		if (sret.length > 0)
			ret = ret.concat(sret);
	}
	return ret;
}

/**
 * 将单词数组连接成字符串
 *
 * @param {array} words 单词数组
 * @return {string}
 */
Segment.prototype.toString= function (words) {
	var ret = '';
	for (var i in words)
		ret += words[i].w;
	return ret;
}

/**
 * 根据某个单词或词性来分割单词数组
 *
 * @param {array} words 单词数组
 * @param {int|string} s 用于分割的单词或词性
 * @return {array}
 */
Segment.prototype.split = function (words, s) {
	var ret = [];
	var lasti = 0;
	var i = 0;
	if (typeof s === 'string') {
		while (i < words.length) {
			if (words[i].w == s) {
				if (lasti < i)
					ret.push(words.slice(lasti, i));
				ret.push(words.slice(i, i + 1));
				i++;
				lasti = i;
			}
			else
				i++;
		}
		if (lasti < words.length - 1)
			ret.push(words.slice(lasti, words.length));
	}
	else {
		while (i < words.length) {
			if ((words[i].p & s) > 0) {
				if (lasti < i)
					ret.push(words.slice(lasti, i));
				ret.push(words.slice(i, i + 1));
				i++;
				lasti = i;
			}
			else
				i++;
		}
		if (lasti < words.length - 1)
			ret.push(words.slice(lasti, words.length));
	}
	return ret;
}

/**
 * 在单词数组中查找某一个单词或词性所在的位置
 *
 * @param {array} words 单词数组
 * @param {int|string} s 要查找的单词或词性
 * @param {int} cur 开始位置
 * @return {int} 找不到，返回-1
 */
Segment.prototype.indexOf = function (words, s, cur) {
	if (isNaN(cur))
		cur = 0;
	if (typeof s === 'string') {
		while (cur < words.length) {
			if (words[cur].w == s)
				return cur;
			cur++;
		}
	}
	else {
		while (cur < words.length) {
			if ((words[cur].p & s) > 0)
				return cur;
			cur++;
		}
	}
	return -1;
}
<commit_msg>[fix] path.existsSync was moved to fs.existsSync<commit_after>/**
 * 分词器接口
 *
 * @author 老雷<leizongmin@gmail.com>
 * @version 0.1
 */

var fs = require('fs');
var path = require('path');
var POSTAG = require('./POSTAG');
var Tokenizer = require('./Tokenizer');
var Optimizer = require('./Optimizer');

var debug = console.log;

/**
 * 创建分词器接口
*/
var Segment = module.exports = function () {
	this.POSTAG = POSTAG;	// 词性
	this.DICT = {};			// 词典表
	this.modules = {
		tokenizer:	[],		// 分词模块
		optimizer:	[]		// 优化模块
	}
	this.tokenizer = new Tokenizer(this);
	this.optimizer = new Optimizer(this);
}

/**
 * 载入分词模块
 *
 * @param {string|array|object} module 模块名称(数组)或模块对象
 * @return {Segment}
 */
Segment.prototype.use = function (module) {
	if (module instanceof Array) {
		for (var i in module)
			this.use(module[i]);
	}
	else {
		if (typeof module == 'string') {
			var filename = path.resolve(__dirname, 'module', module + '.js');
			if (!fs.existsSync(filename))
				throw Error('Cannot find module "' + module + '".');
			else
				module = require(filename);
		}
		// 初始化并注册模块
		module.init(this);
		this.modules[module.type].push(module);
	}

	return this;
}

/**
 * 载入字典文件
 *
 * @param {string} name 字典文件名
 * @param {string} type 类型
 * @param {bool} convert_to_lower 是否全部转换为小写
 * @return {Segment}
 */
Segment.prototype.loadDict = function (name, type, convert_to_lower) {
	var filename = path.resolve(__dirname, '../dicts', name);
	if (!type)	type = 'TABLE';			// 默认为TABLE
	if (!fs.existsSync(filename))
		throw Error('Cannot find dict file "' + filename + '".');
	else {
		// 初始化词典
		if (!this.DICT[type])
			this.DICT[type] = {};
		if (!this.DICT[type + '2'])
			this.DICT[type + '2'] = {};
		var TABLE = this.DICT[type];			// 词典表  '词' => {属性}
		var TABLE2 = this.DICT[type + '2'];	// 词典表  '长度' => '词' => 属性
		// 导入数据
		var POSTAG = this.POSTAG;
		var data = fs.readFileSync(filename, 'utf8');
		if (convert_to_lower)
			data = data.toLowerCase();
		var lines = data.split(/\r?\n/);

		for (var i = 0, line; line = lines[i]; i++) {
			var blocks = line.split('|');
			if (blocks.length > 2) {
				var w = blocks[0].trim();
				var p = Number(blocks[1]);
				var f = Number(blocks[2]);

				TABLE[w] = {f: f, p: p};
				if (!TABLE2[w.length])
					TABLE2[w.length] = {};
				TABLE2[w.length][w] = {f: f, p: p};
			}
		}
		return this;
	}
}

/**
 * 取词典表
 *
 * @param {string} type 类型
 * @return {object}
 */
Segment.prototype.getDict = function (type) {
	return this.DICT[type];
}

/**
 * 使用默认的识别模块和字典文件
 *
 * @return {Segment}
 */
Segment.prototype.useDefault = function () {
	this
		// 识别模块
		// 强制分割类单词识别
		.use('URLTokenizer')			// URL识别
		.use('WildcardTokenizer')		// 通配符，必须在标点符号识别之前
		.use('PunctuationTokenizer')	// 标点符号识别
		.use('ForeignTokenizer')		// 外文字符、数字识别，必须在标点符号识别之后
		// 中文单词识别
		.use('DictTokenizer')			// 词典识别
		.use('ChsNameTokenizer')		// 人名识别，建议在词典识别之后

		// 优化模块
		.use('EmailOptimizer')			// 邮箱地址识别
		.use('ChsNameOptimizer')		// 人名识别优化
		.use('DictOptimizer')			// 词典识别优化
		.use('DatetimeOptimizer')		// 日期时间识别优化

		// 字典文件
		.loadDict('dict.txt')			// 盘古词典
		.loadDict('dict2.txt')			// 扩展词典（用于调整原盘古词典）
		.loadDict('names.txt')			// 常见名词、人名
		.loadDict('wildcard.txt', 'WILDCARD', true)		// 通配符
	;
	return this;
}

/**
 * 开始分词
 *
 * @param {string} text 文本
 * @return {array}
 */
Segment.prototype.doSegment = function (text) {
	// 将文本按照换行符分割成多段，并逐一分词
	var sections = text.replace(/\r?\n(\r?\n)*/, '\n').split(/\r?\n/);
	var ret = [];
	for (var i in sections) {
		var section = sections[i].trim();
		if (section.length < 1)
			continue;
		// ======================================
		// 分词
		var sret = this.tokenizer.split(section, this.modules.tokenizer);

		// 优化
		sret = this.optimizer.doOptimize(sret, this.modules.optimizer);

		// ======================================
		// 连接分词结果
		if (sret.length > 0)
			ret = ret.concat(sret);
	}
	return ret;
}

/**
 * 将单词数组连接成字符串
 *
 * @param {array} words 单词数组
 * @return {string}
 */
Segment.prototype.toString= function (words) {
	var ret = '';
	for (var i in words)
		ret += words[i].w;
	return ret;
}

/**
 * 根据某个单词或词性来分割单词数组
 *
 * @param {array} words 单词数组
 * @param {int|string} s 用于分割的单词或词性
 * @return {array}
 */
Segment.prototype.split = function (words, s) {
	var ret = [];
	var lasti = 0;
	var i = 0;
	if (typeof s === 'string') {
		while (i < words.length) {
			if (words[i].w == s) {
				if (lasti < i)
					ret.push(words.slice(lasti, i));
				ret.push(words.slice(i, i + 1));
				i++;
				lasti = i;
			}
			else
				i++;
		}
		if (lasti < words.length - 1)
			ret.push(words.slice(lasti, words.length));
	}
	else {
		while (i < words.length) {
			if ((words[i].p & s) > 0) {
				if (lasti < i)
					ret.push(words.slice(lasti, i));
				ret.push(words.slice(i, i + 1));
				i++;
				lasti = i;
			}
			else
				i++;
		}
		if (lasti < words.length - 1)
			ret.push(words.slice(lasti, words.length));
	}
	return ret;
}

/**
 * 在单词数组中查找某一个单词或词性所在的位置
 *
 * @param {array} words 单词数组
 * @param {int|string} s 要查找的单词或词性
 * @param {int} cur 开始位置
 * @return {int} 找不到，返回-1
 */
Segment.prototype.indexOf = function (words, s, cur) {
	if (isNaN(cur))
		cur = 0;
	if (typeof s === 'string') {
		while (cur < words.length) {
			if (words[cur].w == s)
				return cur;
			cur++;
		}
	}
	else {
		while (cur < words.length) {
			if ((words[cur].p & s) > 0)
				return cur;
			cur++;
		}
	}
	return -1;
}
<|endoftext|>


<commit_before>  var fetchTextFromURL;
  if (typeof XMLHttpRequest != 'undefined') {
    fetchTextFromURL = function(url, fulfill, reject) {
      // percent encode just '#' in urls
      // according to https://github.com/jorendorff/js-loaders/blob/master/browser-loader.js#L238
      // we should encode everything, but it breaks for servers that don't expect it
      // like in (https://github.com/systemjs/systemjs/issues/168)
      if (isBrowser)
        url = url.replace(/#/g, '%23');

      var xhr = new XMLHttpRequest();
      var sameDomain = true;
      var doTimeout = false;
      if (!('withCredentials' in xhr)) {
        // check if same domain
        var domainCheck = /^(\w+:)?\/\/([^\/]+)/.exec(url);
        if (domainCheck) {
          sameDomain = domainCheck[2] === window.location.host;
          if (domainCheck[1])
            sameDomain &= domainCheck[1] === window.location.protocol;
        }
      }
      if (!sameDomain && typeof XDomainRequest != 'undefined') {
        xhr = new XDomainRequest();
        xhr.onload = load;
        xhr.onerror = error;
        xhr.ontimeout = error;
        xhr.onprogress = function() {};
        xhr.timeout = 0;
        doTimeout = true;
      }
      function load() {
        fulfill(xhr.responseText);
      }
      function error() {
        reject(xhr.statusText + ': ' + url || 'XHR error');
      }

      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200 || (xhr.status == 0 && xhr.responseText)) {
            load();
          } else {
            error();
          }
        }
      };
      xhr.open("GET", url, true);

      if (doTimeout)
        setTimeout(function() {
          xhr.send();
        }, 0);

      xhr.send(null);
    };
  }
  else if (typeof require != 'undefined') {
    var fs;
    fetchTextFromURL = function(url, fulfill, reject) {
      if (url.substr(0, 8) != 'file:///')
        throw 'Only file URLs of the form file:/// allowed running in Node.';
      fs = fs || require('fs');
      if (isWindows)
        url = url.replace(/\//g, '\\').substr(8);
      else
        url = url.substr(7);
      return fs.readFile(url, function(err, data) {
        if (err)
          return reject(err);
        else {
          // Strip Byte Order Mark out if it's the leading char
          var dataString = data + '';
          if (dataString[0] === '\ufeff') {
            dataString = dataString.length === 1 ? '' : dataString.substr(1);
          }

          fulfill(dataString);
        }
      });
    };
  }
  else {
    throw new TypeError('No environment fetch API available.');
  }

  SystemLoader.prototype.fetch = function(load) {
    return new Promise(function(resolve, reject) {
      fetchTextFromURL(load.address, resolve, reject);
    });
  };<commit_msg>substr is flexible<commit_after>  var fetchTextFromURL;
  if (typeof XMLHttpRequest != 'undefined') {
    fetchTextFromURL = function(url, fulfill, reject) {
      // percent encode just '#' in urls
      // according to https://github.com/jorendorff/js-loaders/blob/master/browser-loader.js#L238
      // we should encode everything, but it breaks for servers that don't expect it
      // like in (https://github.com/systemjs/systemjs/issues/168)
      if (isBrowser)
        url = url.replace(/#/g, '%23');

      var xhr = new XMLHttpRequest();
      var sameDomain = true;
      var doTimeout = false;
      if (!('withCredentials' in xhr)) {
        // check if same domain
        var domainCheck = /^(\w+:)?\/\/([^\/]+)/.exec(url);
        if (domainCheck) {
          sameDomain = domainCheck[2] === window.location.host;
          if (domainCheck[1])
            sameDomain &= domainCheck[1] === window.location.protocol;
        }
      }
      if (!sameDomain && typeof XDomainRequest != 'undefined') {
        xhr = new XDomainRequest();
        xhr.onload = load;
        xhr.onerror = error;
        xhr.ontimeout = error;
        xhr.onprogress = function() {};
        xhr.timeout = 0;
        doTimeout = true;
      }
      function load() {
        fulfill(xhr.responseText);
      }
      function error() {
        reject(xhr.statusText + ': ' + url || 'XHR error');
      }

      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status === 200 || (xhr.status == 0 && xhr.responseText)) {
            load();
          } else {
            error();
          }
        }
      };
      xhr.open("GET", url, true);

      if (doTimeout)
        setTimeout(function() {
          xhr.send();
        }, 0);

      xhr.send(null);
    };
  }
  else if (typeof require != 'undefined') {
    var fs;
    fetchTextFromURL = function(url, fulfill, reject) {
      if (url.substr(0, 8) != 'file:///')
        throw 'Only file URLs of the form file:/// allowed running in Node.';
      fs = fs || require('fs');
      if (isWindows)
        url = url.replace(/\//g, '\\').substr(8);
      else
        url = url.substr(7);
      return fs.readFile(url, function(err, data) {
        if (err)
          return reject(err);
        else {
          // Strip Byte Order Mark out if it's the leading char
          var dataString = data + '';
          if (dataString[0] === '\ufeff')
            dataString = dataString.substr(1);

          fulfill(dataString);
        }
      });
    };
  }
  else {
    throw new TypeError('No environment fetch API available.');
  }

  SystemLoader.prototype.fetch = function(load) {
    return new Promise(function(resolve, reject) {
      fetchTextFromURL(load.address, resolve, reject);
    });
  };<|endoftext|>


<commit_before>'use strict';

const test = require('ava');
const Span = require('./');

const dates = [
	new Date(2010, 0, 1, 8, 0, 0, 0),
	new Date(2010, 0, 2, 0, 0, 0, 0),
	new Date(2010, 0, 3, 0, 0, 0, 0),
	new Date(2010, 0, 4, 0, 0, 0, 0),
	new Date(2010, 0, 4, 8, 0, 0, 0)
];

test('constructor', t => {
	const span = new Span(dates[0], dates[1]);

	t.truthy(span);
	t.is(span.from, dates[0]);
	t.is(span.to, dates[1]);
});

test('contains(date)', t => {
	const span = new Span(dates[1], dates[3]);

	t.is(span.contains(dates[0]), false);
	t.is(span.contains(dates[1]), true);
	t.is(span.contains(dates[2]), true);
	t.is(span.contains(dates[3]), true);
	t.is(span.contains(dates[4]), false);
});

test('inside(date)', t => {
	const span = new Span(dates[1], dates[3]);

	t.is(span.inside(dates[0]), false);
	t.is(span.inside(dates[1]), false);
	t.is(span.inside(dates[2]), true);
	t.is(span.inside(dates[3]), false);
	t.is(span.inside(dates[4]), false);
});

test('overlaps(span)', t => {
	const span = new Span(dates[1], dates[3]);

	t.is(span.overlaps(new Span(dates[0], dates[1])), false);
	t.is(span.overlaps(new Span(dates[0], dates[2])), true);
	t.is(span.overlaps(new Span(dates[0], dates[3])), true);
	t.is(span.overlaps(new Span(dates[0], dates[4])), true);

	t.is(span.overlaps(new Span(dates[1], dates[2])), true);
	t.is(span.overlaps(new Span(dates[1], dates[3])), true);
	t.is(span.overlaps(new Span(dates[1], dates[4])), true);

	t.is(span.overlaps(new Span(dates[2], dates[3])), true);
	t.is(span.overlaps(new Span(dates[2], dates[4])), true);

	t.is(span.overlaps(new Span(dates[3], dates[4])), false);
});

test('intersection(span)', t => {
	const span = new Span(dates[1], dates[3]);

	let result = span.intersection(new Span(dates[0], dates[2]));
	t.is(Number(result.from), Number(dates[0]));
	t.is(Number(result.to), Number(dates[3]));
});
<commit_msg>test(): steal twix tests<commit_after>'use strict';

const test = require('ava');
const Span = require('./');

const dates = [
	new Date(2010, 0, 1, 8, 0, 0, 0),
	new Date(2010, 0, 2, 0, 0, 0, 0),
	new Date(2010, 0, 3, 0, 0, 0, 0),
	new Date(2010, 0, 4, 0, 0, 0, 0),
	new Date(2010, 0, 4, 8, 0, 0, 0)
];

function thatDay(start, end) {
	return new Span(new Date("1982-05-25T" + start), new Date("1982-05-25T" + end));
}

function assertOverlap(first, second) {
	assertOverlapness(true).bind(this)(first, second);
}

function assertNoOverlap(first, second) {
	assertOverlapness(false).bind(this)(first, second);
}

function assertOverlapness(shouldOverlap) {
	return function (first, second) {
		this.is(shouldOverlap, first.overlaps(second));
		this.is(shouldOverlap, second.overlaps(first));
	};
}

test('constructor', t => {
	const span = new Span(dates[0], dates[1]);

	t.truthy(span);
	t.is(span.from, Number(dates[0]));
	t.is(span.to, Number(dates[1]));
});

test('contains(date)', t => {
	const span = new Span(dates[1], dates[3]);

	t.is(span.contains(dates[0]), false);
	t.is(span.contains(dates[1]), true);
	t.is(span.contains(dates[2]), true);
	t.is(span.contains(dates[3]), true);
	t.is(span.contains(dates[4]), false);
});

test('inside(date)', t => {
	const span = new Span(dates[1], dates[3]);

	t.is(span.inside(dates[0]), false);
	t.is(span.inside(dates[1]), false);
	t.is(span.inside(dates[2]), true);
	t.is(span.inside(dates[3]), false);
	t.is(span.inside(dates[4]), false);
});

test('returns false for a later range', t => {
	const someTime = thatDay('05:30', '08:30');
	assertNoOverlap.bind(t)(someTime, thatDay('09:30', '11:30'));
});

test('returns false for an earlier range', t => {
	const someTime = thatDay('05:30', '08:30');
	assertNoOverlap.bind(t)(someTime, thatDay('03:30', '04:30'));
});

test('returns true for a partially later range', t => {
	const someTime = thatDay('05:30', '08:30');
	assertOverlap.bind(t)(someTime, thatDay('08:00', '11:30'));
});

test('returns true for a partially earlier range', t => {
	const someTime = thatDay('05:30', '08:30');
	assertOverlap.bind(t)(someTime, thatDay('04:30', '06:30'));
});

test('returns true for an engulfed range', t => {
	const someTime = thatDay('05:30', '08:30');
	assertOverlap.bind(t)(someTime, thatDay('06:30', '07:30'));
});

test('returns true for an engulfing range', t => {
	const someTime = thatDay('05:30', '08:30');
	assertOverlap.bind(t)(someTime, thatDay('04:30', '09:30'));
});

test('returns false for a range that starts immediately afterwards', t => {
	const someTime = thatDay('05:30', '08:30');
	assertNoOverlap.bind(t)(someTime, thatDay('08:30', '09:30'));
});

test('returns false for a range that ends immediately before', t => {
	const someTime = thatDay('05:30', '08:30');
	assertNoOverlap.bind(t)(someTime, thatDay('04:30', '05:30'));
});

test('intersection(span) - not with later', t => {
	const someTime = thatDay('05:30', '08:30');
	const intersection = someTime.intersection(thatDay('09:30', '11:30'));
	t.deepEqual(intersection, thatDay('09:30', '08:30'));
	// t.is(intersection.isValid(), false);
});

test('intersection(span) - not with earlier', t => {
	const someTime = thatDay('05:30', '08:30');
	const intersection = someTime.intersection(thatDay('03:30', '04:30'));
	t.deepEqual(thatDay('05:30', '04:30'), intersection);
	// t.is(false, intersection.isValid());
});

test('intersection(span) - with partially later', t => {
	const someTime = thatDay('05:30', '08:30');
	t.deepEqual(thatDay('08:00', '08:30'), someTime.intersection(thatDay('08:00', '11:30')));
});

test('intersection(span) - with partially earlier', t => {
	const someTime = thatDay('05:30', '08:30');
	t.deepEqual(thatDay('05:30', '06:30'), someTime.intersection(thatDay('04:30', '06:30')));
});

test('intersection(span) - with engulfed', t => {
	const someTime = thatDay('05:30', '08:30');
	t.deepEqual(thatDay('06:30', '07:30'), someTime.intersection(thatDay('06:30', '07:30')));
});

test('intersection(span) - with engulfing', t => {
	const someTime = thatDay('05:30', '08:30');
	t.deepEqual(thatDay('05:30', '08:30'), someTime.intersection(thatDay('04:30', '09:30')));
});

test('intersection(span) - not with adjacent later', t => {
	const someTime = thatDay('05:30', '08:30');
	t.is(0, someTime.intersection(thatDay('08:30', '09:30')).length());
});

test('intersection(span) - not with adjacent earlier', t => {
	const someTime = thatDay('05:30', '08:30');
	t.is(0, someTime.intersection(thatDay('04:30', '05:30')).length());
});

test('intersection(span) - self for identical', t => {
	const someTime = thatDay('05:30', '08:30');
	t.deepEqual(someTime, someTime.intersection(someTime));
});

test('intersection(span) - self for time that starts same but ends later', t => {
	const someTime = thatDay('05:30', '08:30');
	t.deepEqual(someTime, someTime.intersection(thatDay('05:30', '09:30')));
});

test('xor - not overlapping', t => {
	let someTime = thatDay('05:30', '08:30');
	let later = thatDay('09:30', '11:30');
	let orred;

	orred = someTime.xor(later);
	t.is(orred.length, 2);
	t.deepEqual(someTime, orred[0]);
	t.deepEqual(later, orred[1]);

	orred = later.xor(someTime);
	t.is(orred.length, 2);
	t.deepEqual(someTime, orred[0]);
	t.deepEqual(later, orred[1]);
});

test('xor - overlapping', t => {
	let someTime = thatDay('05:30', '08:30');
	let orred;

	orred = someTime.xor(thatDay('08:00', '11:30'));
	t.is(2, orred.length);
	t.deepEqual(thatDay('05:30', '08:00'), orred[0]);
	t.deepEqual(thatDay('08:30', '11:30'), orred[1]);

	orred = thatDay('08:00', '11:30').xor(someTime);
	t.is(2, orred.length);
	t.deepEqual(thatDay('05:30', '08:00'), orred[0]);
	t.deepEqual(thatDay('08:30', '11:30'), orred[1]);
});

test('xor - engulfed', t => {
	let someTime = thatDay('05:30', '08:30');
	let orred;

	orred = someTime.xor(thatDay('06:30', '07:30'));
	t.is(2, orred.length);
	t.deepEqual(thatDay('05:30', '06:30'), orred[0]);
	t.deepEqual(thatDay('07:30', '08:30'), orred[1]);

	orred = thatDay('06:30', '07:30').xor(someTime);
	t.is(2, orred.length);
	t.deepEqual(thatDay('05:30', '06:30'), orred[0]);
	t.deepEqual(thatDay('07:30', '08:30'), orred[1]);
});

test('getMinutes', t => {
	let span = thatDay('06:30', '08:30');
	t.is(span.getMinutes(), 120);
});
<|endoftext|>


<commit_before>;var main = (function(){
  'use strict';
  var ref = new Firebase("https://tictactoe-alex.firebaseio.com");
  var playerTurn;
  var who;
  var board;
  var game = ref.child('game');
  var myGame;
  var myPlayer;
  var gameOver;
  var $begin = $('.begin');
  var $infoBox = $('.info_box p');
  var _turn;
  var _count;
    function _boardChange(){
      board.on('child_added', function(childSnapshot, prevChildName) {
        var td = '.' + childSnapshot.key();
        $(td).addClass(childSnapshot.val());
      })
    };

    clearBoard : function(){
      myGame.remove();
      $('td').removeClass('x');
      $('td').removeClass('o');
    },

    function _playerChange(){
      playerTurn.on("value", function(snapshot) {
         who = snapshot.val();
         if(myPlayer === who){
           $infoBox.text('It is your turn!');
         } else {
           $infoBox.text('Waiting for opponent...')
         }
         tac.checkGameOver();
      });
    };

    function _setCount(){
      _turn.on('value', function(data){
        _count = data.val();
      })
    };

    function _setVariables(){
      board = myGame.child('board');
      myGame.onDisconnect().remove();
      playerTurn = myGame.child('player');
      _boardChange();
      _playerChange();
      _turn = myGame.child('turn');
      _setCount();
    };

    $begin.click(function(){
      if(board){
        tac.clearBoard();
        gameOver = false;
      }
      game.once('value', function(dataSnapshot){
        var found = false
        dataSnapshot.forEach(function(childSnapshot){
          if(childSnapshot.val() === false){
            myGame = childSnapshot.ref();
            myGame.set(true);
            found = true;
            myPlayer = 'x';
            myGame.child('player').set('o');
            _setVariables();
            _turn.set(0);
          }
        })
        if(!found){
          myGame = game.push();
          myGame.set(false);
          myPlayer = 'o';
          $infoBox.text('waiting for another player');
          _setVariables();
        }
      })
      $(this).hide();
    })

    $('td').click(function(){
      if(who != myPlayer || !who || $(this).hasClass('x') || $(this).hasClass('o') || gameOver){
        return
      }
      var spot = $(this).data('cell')
      board.child(spot).set(who);
      var it = who === 'x' ? 'o' : 'x' ;
      _count ++;
      _turn.set(_count);
      playerTurn.set(it);
    })

    function _endGame(lett){
      $infoBox.text(lett.toUpperCase() + "\'s Win! \n New Game?");
      gameOver = true;
      $begin.show();
    }
    return{
      count: _count,
      endGame: _endGame
    }
})();
<commit_msg>Update main.js<commit_after>;var main = (function(){
  'use strict';
  var ref = new Firebase("https://tictactoe-alex.firebaseio.com");
  var playerTurn;
  var who;
  var board;
  var game = ref.child('game');
  var myGame;
  var myPlayer;
  var gameOver;
  var $begin = $('.begin');
  var $infoBox = $('.info_box p');
  var _turn;
  var _count;
    function _boardChange(){
      board.on('child_added', function(childSnapshot, prevChildName) {
        var td = '.' + childSnapshot.key();
        $(td).addClass(childSnapshot.val());
      })
    };

    clearBoard : function(){
      myGame.remove();
      $('td').removeClass('x');
      $('td').removeClass('o');
    }

    function _playerChange(){
      playerTurn.on("value", function(snapshot) {
         who = snapshot.val();
         if(myPlayer === who){
           $infoBox.text('It is your turn!');
         } else {
           $infoBox.text('Waiting for opponent...')
         }
         tac.checkGameOver();
      });
    };

    function _setCount(){
      _turn.on('value', function(data){
        _count = data.val();
      })
    };

    function _setVariables(){
      board = myGame.child('board');
      myGame.onDisconnect().remove();
      playerTurn = myGame.child('player');
      _boardChange();
      _playerChange();
      _turn = myGame.child('turn');
      _setCount();
    };

    $begin.click(function(){
      if(board){
        tac.clearBoard();
        gameOver = false;
      }
      game.once('value', function(dataSnapshot){
        var found = false
        dataSnapshot.forEach(function(childSnapshot){
          if(childSnapshot.val() === false){
            myGame = childSnapshot.ref();
            myGame.set(true);
            found = true;
            myPlayer = 'x';
            myGame.child('player').set('o');
            _setVariables();
            _turn.set(0);
          }
        })
        if(!found){
          myGame = game.push();
          myGame.set(false);
          myPlayer = 'o';
          $infoBox.text('waiting for another player');
          _setVariables();
        }
      })
      $(this).hide();
    })

    $('td').click(function(){
      if(who != myPlayer || !who || $(this).hasClass('x') || $(this).hasClass('o') || gameOver){
        return
      }
      var spot = $(this).data('cell')
      board.child(spot).set(who);
      var it = who === 'x' ? 'o' : 'x' ;
      _count ++;
      _turn.set(_count);
      playerTurn.set(it);
    })

    function _endGame(lett){
      $infoBox.text(lett.toUpperCase() + "\'s Win! \n New Game?");
      gameOver = true;
      $begin.show();
    }
    return{
      count: _count,
      endGame: _endGame
    }
})();
<|endoftext|>


<commit_before>"use strict";

var _ = require("underscore");
var pubit = require("pubit-as-promised");
var publishLogger = require("./utils").publishLogger;
var whatsDifferent = require("./utils").whatsDifferent;
var isObject = require("./utils").isObject;
var cloneObject = require("./utils").cloneObject;
var isEqual = require("./utils").isEqual;
var Action = require("./Action");
var EventName = require("./EventName");
var StoreitError = require("./StoreitError");

var NS_SEPARATOR_CHAR = "#";

var events = [EventName.added, EventName.modified, EventName.removed, EventName.cleared];

function throwUninitializedError() {
    throw new StoreitError(StoreitError.loadUninitialized);
}

function throwIfUndefined(value) {
    if (value === undefined) {
        throw new StoreitError(StoreitError.undefinedValue);
    }
}

function Storeit(namespace, storageProvider, logger) {
    var that = this;
    var cache = {};

    if (namespace.indexOf(NS_SEPARATOR_CHAR) !== -1) {
        throw new StoreitError(StoreitError.invalidNamespace);
    }

    var options = { // Defaut options.
        publish: true,
        publishRemoveOnClear: false
    };

    var publish = pubit.makeEmitter(that, events); // Mixin `on`, `off`, `once`.
    publish = publishLogger(publish, logger || _.noop, namespace); // Make the logger function optional.
    var originalPublish = publish;

    var throwIfUninitialized = throwUninitializedError; // Default until `load` is called.

    function typeCheckKey(key) {
        throwIfUninitialized();
        if (typeof key !== "string") {
            throw new StoreitError(StoreitError.invalidKey);
        }
    }

    function nskey(key, modifier) {
        return namespace + (modifier ? NS_SEPARATOR_CHAR + modifier : "") + ":" + key;
    }

    function ikey(key) {
        return nskey(key, "index"); // ex: mynamespace#index:primary
    }

    function mkey(key) {
        return nskey(key, "metadata"); // ex: mynamespace#metadata:123
    }

    function has(key) {
        typeCheckKey(key);
        return key in cache;
    }

    function getValue(key) {
        return cache[key].value;
    }

    function deleteKey(key) {
        var value = getValue(key);
        delete cache[key];
        storageProvider.removeItem(nskey(key));
        storageProvider.removeItem(mkey(key));
        publish(EventName.removed, value, key);
        return value;
    }

    function setMetadata(key, value) {
        cache[key].metadata = value;
        storageProvider.setItem(mkey(key), value);
    }

    function set(key, value) {
        var results = {};
        if (has(key)) {
            var partial = value;
            var currentValue = getValue(key);
            if (isObject(value) && isObject(currentValue)) {
                value = _.extend(cloneObject(currentValue), value); // Allow "patching" with partial value.
                partial = whatsDifferent(currentValue, value);
            }
            if (isEqual(currentValue, value)) {
                results.action = Action.none;
            } else {
                cache[key].value = value;
                publish(EventName.modified, partial, key);
                results.action = Action.modified;
            }
        } else {
            cache[key] = {
                value: value,
                metadata: null
            };
            publish(EventName.added, cloneObject(value), key);
            results.action = Action.added;
        }
        results.key = key;
        results.value = value;
        return results;
    }

    function getIndex() {
        return storageProvider.getMetadata(ikey("primary")) || [];
    }

    function setIndex() {
        var keys = Object.keys(cache);
        storageProvider.setMetadata(ikey("primary"), keys);
    }

    function getAllFilteredByProperty(propName, propValue) {
        throwIfUninitialized();
        var results = [];
        Object.keys(cache).forEach(function (key) {
            var value = getValue(key);
            if (!propName || value[propName] === propValue) {
                results.push(value);
            }
        });
        return results;
    }

    // Read in the base namespace key
    function initializeItemSerializer(hasItems) {
        // Is there is a itemSerializer specified, we MUST use it.
        // If existing data and no itemSerializer specified, this is an old JSON database,
        // so "fake" the compatible JSON serializer
        var providerInfo = storageProvider.getMetadata(namespace); // TODO I hate this!
        var itemSerializerName = providerInfo ? providerInfo.itemSerializer :
            hasItems ? "JSONSerializer" : null;
        storageProvider.itemSerializer = itemSerializerName;
    }

    function setSerializerName() {
        var data = {
            itemSerializer: storageProvider.itemSerializer
        };
        storageProvider.setMetadata(namespace, data);
    }

    that.has = has;

    that.get = function (key, defaultValue) {
        return has(key) ? getValue(key) : defaultValue;
    };

    that.getAll = function () {
        return getAllFilteredByProperty();
    };

    that.getAllFilteredByProperty = getAllFilteredByProperty;

    that.metadata = function (key) {
        if (has(key)) {
            return {
                set: function (value) {
                    throwIfUndefined(value);
                    setMetadata(key, value);
                },
                get: function () {
                    return cache[key].metadata;
                }
            };
        } else {
            throw new StoreitError(StoreitError.nonexistentKey);
        }
    };

    that.set = function (key, value, metadata) {
        throwIfUndefined(value);
        var results = set(key, value, metadata);
        if (results.action === Action.added) {
            setSerializerName();
            setIndex();
        }
        if (results.action !== Action.none) {
            storageProvider.setItem(nskey(key), results.value);
        }
        if (metadata !== undefined) {
            setMetadata(key, metadata);
            results.metadata = metadata;
        }
        return results;
    };

    function remove(key) {
        if (has(key)) {
            var deletedValue = deleteKey(key);
            setIndex();
            return {
                action: Action.removed,
                key: key,
                value: deletedValue
            };
        } else {
            throw new StoreitError(StoreitError.nonexistentKey);
        }
    }

    that.remove = remove;
    that["delete"] = remove;

    that.forEach = function (fun) {
        throwIfUninitialized();
        Object.keys(cache).forEach(function (key) {
            fun(key, getValue(key));
        });
    };

    that.clear = function () {
        if (options.publishRemoveOnClear) { // Publish removed events only for things that the domain knows about.
            Object.keys(cache).reverse().forEach(deleteKey);
        }
        getIndex().forEach(function (key) { // Remove everything from provider, loaded or not.
            storageProvider.removeItem(nskey(key));
            storageProvider.removeItem(mkey(key));
        });
        cache = {};
        storageProvider.removeItem(ikey("primary"));
        storageProvider.removeItem(namespace); // Remove the storageMetadata for the namespace.
        publish(EventName.cleared);
    };

    Object.defineProperty(that, "options", {
        get: function () {
            return options;
        },
        set: function (value) {
            _.extend(options, value);
            publish = options.publish ? originalPublish : _.noop;
        },
        enumerable: true
    });

    Object.defineProperty(that, "keys", {
        get: function () {
            throwIfUninitialized();
            return Object.keys(cache);
        },
        enumerable: true
    });

    Object.defineProperty(that, "namespace", {
        value: namespace,
        enumerable: true
    });

    that.load = function () {
        throwIfUninitialized = _.noop; // Allow other methods to work without throwing.
        var index = getIndex();
        initializeItemSerializer(!!index.length);
        index.forEach(function (key) { // For each key in "namespace#index:primary"...
            var value = storageProvider.getItem(nskey(key));
            set(key, value); // Build cache and publish an "added" events.
        });
    };

}

Storeit.Action = Action;
Storeit.EventName = EventName;
Storeit.StoreitError = StoreitError;

module.exports = Storeit;
<commit_msg>Allow `clear` to act as "initialized" (like `load`).<commit_after>"use strict";

var _ = require("underscore");
var pubit = require("pubit-as-promised");
var publishLogger = require("./utils").publishLogger;
var whatsDifferent = require("./utils").whatsDifferent;
var isObject = require("./utils").isObject;
var cloneObject = require("./utils").cloneObject;
var isEqual = require("./utils").isEqual;
var Action = require("./Action");
var EventName = require("./EventName");
var StoreitError = require("./StoreitError");

var NS_SEPARATOR_CHAR = "#";

var events = [EventName.added, EventName.modified, EventName.removed, EventName.cleared];

function throwUninitializedError() {
    throw new StoreitError(StoreitError.loadUninitialized);
}

function throwIfUndefined(value) {
    if (value === undefined) {
        throw new StoreitError(StoreitError.undefinedValue);
    }
}

function Storeit(namespace, storageProvider, logger) {
    var that = this;
    var cache = {};

    if (namespace.indexOf(NS_SEPARATOR_CHAR) !== -1) {
        throw new StoreitError(StoreitError.invalidNamespace);
    }

    var options = { // Defaut options.
        publish: true,
        publishRemoveOnClear: false
    };

    var publish = pubit.makeEmitter(that, events); // Mixin `on`, `off`, `once`.
    publish = publishLogger(publish, logger || _.noop, namespace); // Make the logger function optional.
    var originalPublish = publish;

    var throwIfUninitialized = throwUninitializedError; // Default until `load` is called.

    function typeCheckKey(key) {
        throwIfUninitialized();
        if (typeof key !== "string") {
            throw new StoreitError(StoreitError.invalidKey);
        }
    }

    function nskey(key, modifier) {
        return namespace + (modifier ? NS_SEPARATOR_CHAR + modifier : "") + ":" + key;
    }

    function ikey(key) {
        return nskey(key, "index"); // ex: mynamespace#index:primary
    }

    function mkey(key) {
        return nskey(key, "metadata"); // ex: mynamespace#metadata:123
    }

    function has(key) {
        typeCheckKey(key);
        return key in cache;
    }

    function getValue(key) {
        return cache[key].value;
    }

    function deleteKey(key) {
        var value = getValue(key);
        delete cache[key];
        storageProvider.removeItem(nskey(key));
        storageProvider.removeItem(mkey(key));
        publish(EventName.removed, value, key);
        return value;
    }

    function setMetadata(key, value) {
        cache[key].metadata = value;
        storageProvider.setItem(mkey(key), value);
    }

    function set(key, value) {
        var results = {};
        if (has(key)) {
            var partial = value;
            var currentValue = getValue(key);
            if (isObject(value) && isObject(currentValue)) {
                value = _.extend(cloneObject(currentValue), value); // Allow "patching" with partial value.
                partial = whatsDifferent(currentValue, value);
            }
            if (isEqual(currentValue, value)) {
                results.action = Action.none;
            } else {
                cache[key].value = value;
                publish(EventName.modified, partial, key);
                results.action = Action.modified;
            }
        } else {
            cache[key] = {
                value: value,
                metadata: null
            };
            publish(EventName.added, cloneObject(value), key);
            results.action = Action.added;
        }
        results.key = key;
        results.value = value;
        return results;
    }

    function getIndex() {
        return storageProvider.getMetadata(ikey("primary")) || [];
    }

    function setIndex() {
        var keys = Object.keys(cache);
        storageProvider.setMetadata(ikey("primary"), keys);
    }

    function getAllFilteredByProperty(propName, propValue) {
        throwIfUninitialized();
        var results = [];
        Object.keys(cache).forEach(function (key) {
            var value = getValue(key);
            if (!propName || value[propName] === propValue) {
                results.push(value);
            }
        });
        return results;
    }

    // Read in the base namespace key
    function initializeItemSerializer(hasItems) {
        // Is there is a itemSerializer specified, we MUST use it.
        // If existing data and no itemSerializer specified, this is an old JSON database,
        // so "fake" the compatible JSON serializer
        var providerInfo = storageProvider.getMetadata(namespace); // TODO I hate this!
        var itemSerializerName = providerInfo ? providerInfo.itemSerializer :
            hasItems ? "JSONSerializer" : null;
        storageProvider.itemSerializer = itemSerializerName;
    }

    function setSerializerName() {
        var data = {
            itemSerializer: storageProvider.itemSerializer
        };
        storageProvider.setMetadata(namespace, data);
    }

    that.has = has;

    that.get = function (key, defaultValue) {
        return has(key) ? getValue(key) : defaultValue;
    };

    that.getAll = function () {
        return getAllFilteredByProperty();
    };

    that.getAllFilteredByProperty = getAllFilteredByProperty;

    that.metadata = function (key) {
        if (has(key)) {
            return {
                set: function (value) {
                    throwIfUndefined(value);
                    setMetadata(key, value);
                },
                get: function () {
                    return cache[key].metadata;
                }
            };
        } else {
            throw new StoreitError(StoreitError.nonexistentKey);
        }
    };

    that.set = function (key, value, metadata) {
        throwIfUndefined(value);
        var results = set(key, value, metadata);
        if (results.action === Action.added) {
            setSerializerName();
            setIndex();
        }
        if (results.action !== Action.none) {
            storageProvider.setItem(nskey(key), results.value);
        }
        if (metadata !== undefined) {
            setMetadata(key, metadata);
            results.metadata = metadata;
        }
        return results;
    };

    function remove(key) {
        if (has(key)) {
            var deletedValue = deleteKey(key);
            setIndex();
            return {
                action: Action.removed,
                key: key,
                value: deletedValue
            };
        } else {
            throw new StoreitError(StoreitError.nonexistentKey);
        }
    }

    that.remove = remove;
    that["delete"] = remove;

    that.forEach = function (fun) {
        throwIfUninitialized();
        Object.keys(cache).forEach(function (key) {
            fun(key, getValue(key));
        });
    };

    that.clear = function () {
        throwIfUninitialized = _.noop; // Allow other methods to work without throwing.
        if (options.publishRemoveOnClear) { // Publish removed events only for things that the domain knows about.
            Object.keys(cache).reverse().forEach(deleteKey);
        }
        getIndex().forEach(function (key) { // Remove everything from provider, loaded or not.
            storageProvider.removeItem(nskey(key));
            storageProvider.removeItem(mkey(key));
        });
        cache = {};
        storageProvider.removeItem(ikey("primary"));
        storageProvider.removeItem(namespace); // Remove the storageMetadata for the namespace.
        publish(EventName.cleared);
    };

    Object.defineProperty(that, "options", {
        get: function () {
            return options;
        },
        set: function (value) {
            _.extend(options, value);
            publish = options.publish ? originalPublish : _.noop;
        },
        enumerable: true
    });

    Object.defineProperty(that, "keys", {
        get: function () {
            throwIfUninitialized();
            return Object.keys(cache);
        },
        enumerable: true
    });

    Object.defineProperty(that, "namespace", {
        value: namespace,
        enumerable: true
    });

    that.load = function () {
        throwIfUninitialized = _.noop; // Allow other methods to work without throwing.
        var index = getIndex();
        initializeItemSerializer(!!index.length);
        index.forEach(function (key) { // For each key in "namespace#index:primary"...
            var value = storageProvider.getItem(nskey(key));
            set(key, value); // Build cache and publish an "added" events.
        });
    };

}

Storeit.Action = Action;
Storeit.EventName = EventName;
Storeit.StoreitError = StoreitError;

module.exports = Storeit;
<|endoftext|>