<commit_before>import {computeAccessibleName} from 'dom-accessibility-api'
import {roles as allRoles, roleElements} from 'aria-query'
import {
  computeAriaSelected,
  computeAriaChecked,
  computeAriaPressed,
  computeAriaCurrent,
  computeAriaExpanded,
  computeHeadingLevel,
  getImplicitAriaRoles,
  prettyRoles,
  isInaccessible,
  isSubtreeInaccessible,
} from '../role-helpers'
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {
  buildQueries,
  fuzzyMatches,
  getConfig,
  makeNormalizer,
  matches,
} from './all-utils'

function queryAllByRole(
  container,
  role,
  {
    exact = true,
    collapseWhitespace,
    hidden = getConfig().defaultHidden,
    name,
    trim,
    normalizer,
    queryFallbacks = false,
    selected,
    checked,
    pressed,
    current,
    level,
    expanded,
  } = {},
) {
  checkContainerType(container)
  const matcher = exact ? matches : fuzzyMatches
  const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})

  if (selected !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-selected'] === undefined) {
      throw new Error(`"aria-selected" is not supported on role "${role}".`)
    }
  }

  if (checked !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-checked'] === undefined) {
      throw new Error(`"aria-checked" is not supported on role "${role}".`)
    }
  }

  if (pressed !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-pressed'] === undefined) {
      throw new Error(`"aria-pressed" is not supported on role "${role}".`)
    }
  }

  if (current !== undefined) {
    /* istanbul ignore next */
    // guard against unknown roles
    // All currently released ARIA versions support `aria-current` on all roles.
    // Leaving this for symetry and forward compatibility
    if (allRoles.get(role)?.props['aria-current'] === undefined) {
      throw new Error(`"aria-current" is not supported on role "${role}".`)
    }
  }

  if (level !== undefined) {
    // guard against using `level` option with any role other than `heading`
    if (role !== 'heading') {
      throw new Error(`Role "${role}" cannot have "level" property.`)
    }
  }

  if (expanded !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-expanded'] === undefined) {
      throw new Error(`"aria-expanded" is not supported on role "${role}".`)
    }
  }

  const subtreeIsInaccessibleCache = new WeakMap()
  function cachedIsSubtreeInaccessible(element) {
    if (!subtreeIsInaccessibleCache.has(element)) {
      subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element))
    }

    return subtreeIsInaccessibleCache.get(element)
  }

  return Array.from(
    container.querySelectorAll(
      // Only query elements that can be matched by the following filters
      makeRoleSelector(role, exact, normalizer ? matchNormalizer : undefined),
    ),
  )
    .filter(node => {
      const isRoleSpecifiedExplicitly = node.hasAttribute('role')

      if (isRoleSpecifiedExplicitly) {
        const roleValue = node.getAttribute('role')
        if (queryFallbacks) {
          return roleValue
            .split(' ')
            .filter(Boolean)
            .some(text => matcher(text, node, role, matchNormalizer))
        }
        // if a custom normalizer is passed then let normalizer handle the role value
        if (normalizer) {
          return matcher(roleValue, node, role, matchNormalizer)
        }
        // other wise only send the first word to match
        const [firstWord] = roleValue.split(' ')
        return matcher(firstWord, node, role, matchNormalizer)
      }

      const implicitRoles = getImplicitAriaRoles(node)

      return implicitRoles.some(implicitRole =>
        matcher(implicitRole, node, role, matchNormalizer),
      )
    })
    .filter(element => {
      if (selected !== undefined) {
        return selected === computeAriaSelected(element)
      }
      if (checked !== undefined) {
        return checked === computeAriaChecked(element)
      }
      if (pressed !== undefined) {
        return pressed === computeAriaPressed(element)
      }
      if (current !== undefined) {
        return current === computeAriaCurrent(element)
      }
      if (expanded !== undefined) {
        return expanded === computeAriaExpanded(element)
      }
      if (level !== undefined) {
        return level === computeHeadingLevel(element)
      }
      // don't care if aria attributes are unspecified
      return true
    })
    .filter(element => {
      return hidden === false
        ? isInaccessible(element, {
            isSubtreeInaccessible: cachedIsSubtreeInaccessible,
          }) === false
        : true
    })
    .filter(element => {
      if (name === undefined) {
        // Don't care
        return true
      }

      return matches(
        computeAccessibleName(element, {
          computedStyleSupportsPseudoElements:
            getConfig().computedStyleSupportsPseudoElements,
        }),
        element,
        name,
        text => text,
      )
    })
}

function makeRoleSelector(role, exact, customNormalizer) {
  if (typeof role !== 'string') {
    // For non-string role parameters we can not determine the implicitRoleSelectors.
    return '*'
  }

  const explicitRoleSelector =
    exact && !customNormalizer ? `*[role~="${role}"]` : '*[role]'

  const roleRelations = roleElements.get(role) ?? new Set()
  const implicitRoleSelectors = new Set(
    Array.from(roleRelations).map(({name}) => name),
  )

  // Current transpilation config sometimes assumes `...` is always applied to arrays.
  // `...` is equivalent to `Array.prototype.concat` for arrays.
  // If you replace this code with `[explicitRoleSelector, ...implicitRoleSelectors]`, make sure every transpilation target retains the `...` in favor of `Array.prototype.concat`.
  return [explicitRoleSelector]
    .concat(Array.from(implicitRoleSelectors))
    .join(',')
}

const getMultipleError = (c, role, {name} = {}) => {
  let nameHint = ''
  if (name === undefined) {
    nameHint = ''
  } else if (typeof name === 'string') {
    nameHint = ` and name "${name}"`
  } else {
    nameHint = ` and name \`${name}\``
  }

  return `Found multiple elements with the role "${role}"${nameHint}`
}

const getMissingError = (
  container,
  role,
  {hidden = getConfig().defaultHidden, name} = {},
) => {
  if (getConfig()._disableExpensiveErrorDiagnostics) {
    return `Unable to find role="${role}"`
  }

  let roles = ''
  Array.from(container.children).forEach(childElement => {
    roles += prettyRoles(childElement, {
      hidden,
      includeName: name !== undefined,
    })
  })
  let roleMessage

  if (roles.length === 0) {
    if (hidden === false) {
      roleMessage =
        'There are no accessible roles. But there might be some inaccessible roles. ' +
        'If you wish to access them, then set the `hidden` option to `true`. ' +
        'Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole'
    } else {
      roleMessage = 'There are no available roles.'
    }
  } else {
    roleMessage = `
Here are the ${hidden === false ? 'accessible' : 'available'} roles:

  ${roles.replace(/\n/g, '\n  ').replace(/\n\s\s\n/g, '\n\n')}
`.trim()
  }

  let nameHint = ''
  if (name === undefined) {
    nameHint = ''
  } else if (typeof name === 'string') {
    nameHint = ` and name "${name}"`
  } else {
    nameHint = ` and name \`${name}\``
  }

  return `
Unable to find an ${
    hidden === false ? 'accessible ' : ''
  }element with the role "${role}"${nameHint}

${roleMessage}`.trim()
}
const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion(
  queryAllByRole,
  queryAllByRole.name,
  'queryAll',
)
const [queryByRole, getAllByRole, getByRole, findAllByRole, findByRole] =
  buildQueries(queryAllByRole, getMultipleError, getMissingError)

export {
  queryByRole,
  queryAllByRoleWithSuggestions as queryAllByRole,
  getAllByRole,
  getByRole,
  findAllByRole,
  findByRole,
}
<commit_msg>feat: Prioritize accessible names check higher than inaccessibility check in `byRole` queries (#1068)<commit_after>import {computeAccessibleName} from 'dom-accessibility-api'
import {roles as allRoles, roleElements} from 'aria-query'
import {
  computeAriaSelected,
  computeAriaChecked,
  computeAriaPressed,
  computeAriaCurrent,
  computeAriaExpanded,
  computeHeadingLevel,
  getImplicitAriaRoles,
  prettyRoles,
  isInaccessible,
  isSubtreeInaccessible,
} from '../role-helpers'
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
import {checkContainerType} from '../helpers'
import {
  buildQueries,
  fuzzyMatches,
  getConfig,
  makeNormalizer,
  matches,
} from './all-utils'

function queryAllByRole(
  container,
  role,
  {
    exact = true,
    collapseWhitespace,
    hidden = getConfig().defaultHidden,
    name,
    trim,
    normalizer,
    queryFallbacks = false,
    selected,
    checked,
    pressed,
    current,
    level,
    expanded,
  } = {},
) {
  checkContainerType(container)
  const matcher = exact ? matches : fuzzyMatches
  const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})

  if (selected !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-selected'] === undefined) {
      throw new Error(`"aria-selected" is not supported on role "${role}".`)
    }
  }

  if (checked !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-checked'] === undefined) {
      throw new Error(`"aria-checked" is not supported on role "${role}".`)
    }
  }

  if (pressed !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-pressed'] === undefined) {
      throw new Error(`"aria-pressed" is not supported on role "${role}".`)
    }
  }

  if (current !== undefined) {
    /* istanbul ignore next */
    // guard against unknown roles
    // All currently released ARIA versions support `aria-current` on all roles.
    // Leaving this for symetry and forward compatibility
    if (allRoles.get(role)?.props['aria-current'] === undefined) {
      throw new Error(`"aria-current" is not supported on role "${role}".`)
    }
  }

  if (level !== undefined) {
    // guard against using `level` option with any role other than `heading`
    if (role !== 'heading') {
      throw new Error(`Role "${role}" cannot have "level" property.`)
    }
  }

  if (expanded !== undefined) {
    // guard against unknown roles
    if (allRoles.get(role)?.props['aria-expanded'] === undefined) {
      throw new Error(`"aria-expanded" is not supported on role "${role}".`)
    }
  }

  const subtreeIsInaccessibleCache = new WeakMap()
  function cachedIsSubtreeInaccessible(element) {
    if (!subtreeIsInaccessibleCache.has(element)) {
      subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element))
    }

    return subtreeIsInaccessibleCache.get(element)
  }

  return Array.from(
    container.querySelectorAll(
      // Only query elements that can be matched by the following filters
      makeRoleSelector(role, exact, normalizer ? matchNormalizer : undefined),
    ),
  )
    .filter(node => {
      const isRoleSpecifiedExplicitly = node.hasAttribute('role')

      if (isRoleSpecifiedExplicitly) {
        const roleValue = node.getAttribute('role')
        if (queryFallbacks) {
          return roleValue
            .split(' ')
            .filter(Boolean)
            .some(text => matcher(text, node, role, matchNormalizer))
        }
        // if a custom normalizer is passed then let normalizer handle the role value
        if (normalizer) {
          return matcher(roleValue, node, role, matchNormalizer)
        }
        // other wise only send the first word to match
        const [firstWord] = roleValue.split(' ')
        return matcher(firstWord, node, role, matchNormalizer)
      }

      const implicitRoles = getImplicitAriaRoles(node)

      return implicitRoles.some(implicitRole =>
        matcher(implicitRole, node, role, matchNormalizer),
      )
    })
    .filter(element => {
      if (selected !== undefined) {
        return selected === computeAriaSelected(element)
      }
      if (checked !== undefined) {
        return checked === computeAriaChecked(element)
      }
      if (pressed !== undefined) {
        return pressed === computeAriaPressed(element)
      }
      if (current !== undefined) {
        return current === computeAriaCurrent(element)
      }
      if (expanded !== undefined) {
        return expanded === computeAriaExpanded(element)
      }
      if (level !== undefined) {
        return level === computeHeadingLevel(element)
      }
      // don't care if aria attributes are unspecified
      return true
    })
    .filter(element => {
      if (name === undefined) {
        // Don't care
        return true
      }

      return matches(
        computeAccessibleName(element, {
          computedStyleSupportsPseudoElements:
            getConfig().computedStyleSupportsPseudoElements,
        }),
        element,
        name,
        text => text,
      )
    })
    .filter(element => {
      return hidden === false
        ? isInaccessible(element, {
            isSubtreeInaccessible: cachedIsSubtreeInaccessible,
          }) === false
        : true
    })
}

function makeRoleSelector(role, exact, customNormalizer) {
  if (typeof role !== 'string') {
    // For non-string role parameters we can not determine the implicitRoleSelectors.
    return '*'
  }

  const explicitRoleSelector =
    exact && !customNormalizer ? `*[role~="${role}"]` : '*[role]'

  const roleRelations = roleElements.get(role) ?? new Set()
  const implicitRoleSelectors = new Set(
    Array.from(roleRelations).map(({name}) => name),
  )

  // Current transpilation config sometimes assumes `...` is always applied to arrays.
  // `...` is equivalent to `Array.prototype.concat` for arrays.
  // If you replace this code with `[explicitRoleSelector, ...implicitRoleSelectors]`, make sure every transpilation target retains the `...` in favor of `Array.prototype.concat`.
  return [explicitRoleSelector]
    .concat(Array.from(implicitRoleSelectors))
    .join(',')
}

const getMultipleError = (c, role, {name} = {}) => {
  let nameHint = ''
  if (name === undefined) {
    nameHint = ''
  } else if (typeof name === 'string') {
    nameHint = ` and name "${name}"`
  } else {
    nameHint = ` and name \`${name}\``
  }

  return `Found multiple elements with the role "${role}"${nameHint}`
}

const getMissingError = (
  container,
  role,
  {hidden = getConfig().defaultHidden, name} = {},
) => {
  if (getConfig()._disableExpensiveErrorDiagnostics) {
    return `Unable to find role="${role}"`
  }

  let roles = ''
  Array.from(container.children).forEach(childElement => {
    roles += prettyRoles(childElement, {
      hidden,
      includeName: name !== undefined,
    })
  })
  let roleMessage

  if (roles.length === 0) {
    if (hidden === false) {
      roleMessage =
        'There are no accessible roles. But there might be some inaccessible roles. ' +
        'If you wish to access them, then set the `hidden` option to `true`. ' +
        'Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole'
    } else {
      roleMessage = 'There are no available roles.'
    }
  } else {
    roleMessage = `
Here are the ${hidden === false ? 'accessible' : 'available'} roles:

  ${roles.replace(/\n/g, '\n  ').replace(/\n\s\s\n/g, '\n\n')}
`.trim()
  }

  let nameHint = ''
  if (name === undefined) {
    nameHint = ''
  } else if (typeof name === 'string') {
    nameHint = ` and name "${name}"`
  } else {
    nameHint = ` and name \`${name}\``
  }

  return `
Unable to find an ${
    hidden === false ? 'accessible ' : ''
  }element with the role "${role}"${nameHint}

${roleMessage}`.trim()
}
const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion(
  queryAllByRole,
  queryAllByRole.name,
  'queryAll',
)
const [queryByRole, getAllByRole, getByRole, findAllByRole, findByRole] =
  buildQueries(queryAllByRole, getMultipleError, getMissingError)

export {
  queryByRole,
  queryAllByRoleWithSuggestions as queryAllByRole,
  getAllByRole,
  getByRole,
  findAllByRole,
  findByRole,
}
<|endoftext|>


<commit_before>/* eslint-disable no-unused-expressions */
/* eslint-disable camelcase */
require('./gently-preamble')
const chai = require('chai')
var chaiAsPromised = require('chai-as-promised')
const TransloaditClient = require('../src/TransloaditClient')
const localtunnel = require('localtunnel')
const http = require('http')
const querystring = require('querystring')
const temp = require('temp')
const fs = require('fs')
const _ = require('lodash')
const { join } = require('path')
const { promisify } = require('util')
const { pipeline: streamPipeline } = require('stream')
const got = require('got')
const pipeline = promisify(streamPipeline)

const { expect } = chai
chai.use(chaiAsPromised)

async function downloadTmpFile (url) {
  const { path } = await temp.open('transloadit')
  await pipeline(got.stream(url), fs.createWriteStream(path))
  return path
}

const authKey = process.env.TRANSLOADIT_KEY
const authSecret = process.env.TRANSLOADIT_SECRET
if (authKey == null || authSecret == null) {
  let msg = 'specify environment variables TRANSLOADIT_KEY and TRANSLOADIT_SECRET'
  msg += ' to enable integration tests.'
  console.warn(msg)
} else {
  const startServerAsync = async (handler) => new Promise((resolve, reject) => {
    const server = http.createServer(handler)

    // Find a port to use
    let port = 8000
    server.on('error', err => {
      if (err.code === 'EADDRINUSE') {
        if (++port >= 65535) {
          server.close()
          reject(new Error('Failed to bind to port'))
        }
        return server.listen(port, '127.0.0.1')
      } else {
        return reject(err)
      }
    })

    server.listen(port, '127.0.0.1')

    // Once a port has been found and the server is ready, setup the
    // localtunnel
    return server.on('listening', async () => {
      try {
        const tunnel = await localtunnel(port)
        // console.log('localtunnel', tunnel.url)

        tunnel.on('error', console.error)
        tunnel.on('close', () => {
          // console.log('tunnel closed')
          server.close()
        })

        return resolve({
          url: tunnel.url,
          close () {
            tunnel.close()
          },
        })
      } catch (err) {
        if (err != null) {
          server.close()
          return reject(err)
        }
      }
    })
  })

  const startServer = (handler, cb) => startServerAsync(handler).then((server) => cb(null, server)).catch(cb)

  // https://transloadit.com/demos/importing-files/import-a-file-over-http
  const genericImg = 'https://demos.transloadit.com/66/01604e7d0248109df8c7cc0f8daef8/snowflake.jpg'
  const resizeOriginalStep = {
    robot : '/image/resize',
    use   : ':original',
    result: true,
    width : 130,
    height: 130,
  }
  const genericParams = {
    params: {
      steps: {
        import: {
          robot: '/http/import',
          url  : genericImg,
        },
        resize: {
          robot : '/image/resize',
          use   : 'import',
          result: true,
          width : 130,
          height: 130,
        },
      },
    },
    waitForCompletion: true,
  }

  describe('API integration', function () {
    this.timeout(100000)
    describe('assembly creation', () => {
      it('should create a retrievable assembly on the server', done => {
        const client = new TransloaditClient({ authKey, authSecret })

        return client.createAssembly(genericParams, (err, result) => {
          expect(err).to.not.exist
          expect(result).to.not.have.property('error')
          expect(result).to.have.property('ok')
          expect(result).to.have.property('assembly_id') // Since we're using it

          const id = result.assembly_id

          return client.getAssembly(id, (err, result) => {
            expect(err).to.not.exist
            expect(result).to.not.have.property('error')
            expect(result).to.have.property('ok')
            expect(result.assembly_id).to.equal(id)
            return done()
          })
        })
      })

      it('should get a full assembly status reliably', function (done) {
        this.timeout(0)

        const client = new TransloaditClient({ authKey, authSecret })

        const nbranches = 5
        let ndone = 0
        const branchDone = () => {
          if (++ndone === nbranches) {
            return done()
          }
        }

        const reproduce = nattempts => {
          if (nattempts === 0) {
            return branchDone()
          }

          client.createAssembly(genericParams, (err, { error, assembly_id } = {}) => {
            if (err != null || error != null) {
              return reproduce(nattempts - 1)
            }

            client.getAssembly(assembly_id, (err, result) => {
              if (err != null || result.error != null) {
                reproduce(nattempts - 1)
              }

              expect(result).to.have.property('assembly_url').that.exist
              reproduce(nattempts - 1)
            })
          })
        }

        // attempt to reproduce the incomplete status response 100 times
        for (let i = 0; i < nbranches; i++) reproduce(100 / nbranches)
      })

      it("should signal an error if a file selected for upload doesn't exist", async () => {
        const client = new TransloaditClient({ authKey, authSecret })

        const params = {
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
        }

        client.addFile('original', temp.path({ suffix: '.transloadit.jpg' })) // Non-existing path

        const promise = client.createAssemblyAsync(params)
        await expect(promise).to.eventually.be.rejectedWith(Error).and.have.property('code').that.equals('ENOENT')
      })

      it('should allow uploading files that do exist', async () => {
        const client = new TransloaditClient({ authKey, authSecret })

        const params = {
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
        }

        const path = await downloadTmpFile(genericImg)
            client.addFile('original', path)

        await client.createAssemblyAsync(params)
      })

      async function testUploadProgress (isResumable) {
        const client = new TransloaditClient({ authKey, authSecret })

        const params = {
          isResumable,
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
        }

        const path = await downloadTmpFile(genericImg)
        client.addFile('original', path)

        let progressCalled = false
        function onProgress (progress) {
          // console.log(progress)
          expect(progress.uploadProgress.uploadedBytes).to.exist
          progressCalled = true
        }
        await client.createAssemblyAsync(params, onProgress)
        expect(progressCalled).to.be.true
      }

      it('should trigger progress callbacks when uploading files, resumable', async () => {
        await testUploadProgress(true)
      })

      it('should trigger progress callbacks when uploading files, nonresumable', async () => {
        await testUploadProgress(false)
      })

      it('should trigger the callback when waitForComplete is false', done => {
        const client = new TransloaditClient({ authKey, authSecret })
        const params = Object.assign({}, genericParams, { waitForCompletion: false })

        return client.createAssembly(params, (err, result) => {
          expect(err).to.not.exist
          expect(result).to.not.have.property('error')
          expect(result).to.have.property('ok')
          return done()
        })
      })

      it('should exit fast when assembly has failed', async () => {
        // An old bug caused it to continuously retry until timeout when errors such as INVALID_FILE_META_DATA
        const client = new TransloaditClient({ authKey, authSecret })
        const opts = {
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
          waitForCompletion: true,
        }
        client.addFile('file', join(__dirname, './fixtures/zerobytes.jpg'))

        const promise = client.createAssemblyAsync(opts)
        await promise.catch((err) => {
          expect(err.error).to.eq('INVALID_FILE_META_DATA')
          expect(err.assembly_id).to.exist
        })
        await expect(promise).to.eventually.be.rejectedWith(Error)
      }).timeout(7000)
    })

    describe('assembly cancelation', () => {
      it('should stop the assembly from reaching completion', async () => {
        const client = new TransloaditClient({ authKey, authSecret })

        // We need to ensure that the assembly doesn't complete before it can be
        // canceled, so we start an http server for the assembly to import from,
        // and delay transmission of data until we've already sent the cancel
        // request

        // Async book-keeping for delaying the response
        let sendServerResponse

        const promise = new Promise((resolve) => {
          sendServerResponse = resolve
        })

        const handler = async (req, res) => {
          // console.log('handler', req.url)

          expect(req.url).to.equal('/')

          await promise

          res.setHeader('Content-type', 'image/jpeg')
          res.writeHead(200)
          got.stream(genericImg).pipe(res)
        }

        const server = await startServerAsync(handler)

        try {
          const params = {
            params: {
              steps: {
                import: {
                  robot: '/http/import',
                  url  : server.url,
                },
                resize: {
                  robot : '/image/resize',
                  use   : 'import',
                  result: true,
                  width : 130,
                  height: 130,
                },
              },
            },
          }

          // Finally send the createAssembly request
          const { assembly_id: id } = await client.createAssemblyAsync(params)

          // Now delete it
          const resp = await client.deleteAssemblyAsync(id)

          // Allow the upload to finish
          sendServerResponse()

          expect(resp.ok).to.equal('ASSEMBLY_CANCELED')

          // Successful cancel requests get ASSEMBLY_CANCELED even when it
          // completed, so we now request the assembly status to check the
          // *actual* status.
          const resp2 = await client.getAssemblyAsync(id)
          expect(resp2.ok).to.equal('ASSEMBLY_CANCELED')
        } finally {
          server.close()
        }
      })
    })

    describe('replaying assemblies', () => {
      it('should replay an assembly after it has completed', done => {
        const client = new TransloaditClient({ authKey, authSecret })

        client.createAssembly(genericParams, (err, { assembly_id } = {}) => {
          expect(err).to.not.exist

          const originalId = assembly_id // eslint-disable-line camelcase

          // ensure that the assembly has completed
          const ensureCompletion = cb =>
            client.getAssembly(originalId, (err, result) => {
              expect(err).to.not.exist
              const ok = result.ok

              if (ok === 'ASSEMBLY_UPLOADING' || ok === 'ASSEMBLY_EXECUTING') {
                setTimeout(() => ensureCompletion(cb), 1000)
              } else {
                cb()
              }
            })

          // Start an asynchonous loop
          ensureCompletion(() =>
            client.replayAssembly({ assembly_id: originalId }, (err, { ok } = {}) => {
              expect(err).to.not.exist
              expect(ok).to.equal('ASSEMBLY_REPLAYING')
              done()
            }),
          )
        })
      })
    })

    describe('assembly list retrieval', () => {
      it('should retrieve a list of assemblies', done => {
        const client = new TransloaditClient({ authKey, authSecret })

        client.listAssemblies({}, (err, result) => {
          expect(err).to.not.exist
          expect(result).to.have.property('count')
          expect(result)
            .to.have.property('items')
            .that.is.instanceof(Array)
          done()
        })
      })

      it('should be able to handle pagination with a stream', done => {
        const client = new TransloaditClient({ authKey, authSecret })
        const assemblies = client.streamAssemblies({ pagesize: 2 })
        let n = 0
        let isDone = false

        assemblies.on('readable', () => {
          const assembly = assemblies.read()

          if (isDone) return

          if (assembly == null) {
            return done()
          }

          if (n === 5) {
            isDone = true
            return done()
          }

          expect(assembly).to.have.property('id')
          n++
        })
      })
    })

    describe('assembly notification', () => {
      // helper function
      const streamToString = (stream, cb) => {
        const chunks = []
        stream.on('data', chunk => chunks.push(chunk))
        stream.on('error', err => cb(err))
        stream.on('end', () => cb(null, chunks.join('')))
      }

      const testCase = (desc, endBehavior) =>
        it(desc, done => {
          const client = new TransloaditClient({ authKey, authSecret })

          // listens for notifications
          const handler = (req, res) => {
            expect(req.url).to.equal('/')

            expect(req.method).to.equal('POST')
            streamToString(req, (err, body) => {
              if (err) {
                console.error(err)
              }
              const result = JSON.parse(querystring.parse(body).transloadit)
              expect(result).to.have.property('ok')
              res.writeHead(200)
              res.end()
              if (result.ok !== 'ASSEMBLY_COMPLETED') return
              endBehavior(client, result.assembly_id, done)
            })
          }

          startServer(handler, (err, server) => {
            expect(err).to.not.exist

            const params = { params: _.extend({}, genericParams.params, { notify_url: server.url }) }

            client.createAssembly(params, (err, result) => expect(err).to.not.exist)
          })
        })

      testCase('should send a notification upon assembly completion', (client, id, done) => done())

      let notificationsRecvd = 0
      testCase('should replay the notification when requested', (client, id, done) => {
        if (notificationsRecvd++ === 0) {
          setTimeout(() => {
            client.replayAssemblyNotification({ assembly_id: id }, err => expect(err).to.not.exist)
          }, 2000)
        } else {
          done()
        }
      })
    })

    describe('template methods', () => {
      // can contain only lowercase latin letters, numbers, and dashes.
      const templName = `node-sdk-test-${new Date().toISOString().toLocaleLowerCase('en-US').replace(/[^0-9a-z-]/g, '-')}`
      let templId = null
      const client = new TransloaditClient({ authKey, authSecret })

      it('should allow creating a template', async () => {
        const { id } = await client.createTemplateAsync({ name: templName, template: genericParams.params })
        templId = id
      })

      it("should be able to fetch a template's definition", async () => {
        expect(templId).to.exist

        const { name, content } = await client.getTemplateAsync(templId)
        expect(name).to.equal(templName)
        expect(content).to.deep.equal(genericParams.params)
      })

      it('should delete the template successfully', done => {
        expect(templId).to.exist

        client.deleteTemplate(templId, (err, { ok } = {}) => {
          expect(err).to.not.exist
          expect(ok).to.equal('TEMPLATE_DELETED')
          client.getTemplate(templId, (err, result) => {
            expect(result).to.not.exist
            expect(err).to.exist
            expect(err.error).to.equal('TEMPLATE_NOT_FOUND')
            done()
          })
        })
      })
    })
  })
}
<commit_msg>fix typo<commit_after>/* eslint-disable no-unused-expressions */
/* eslint-disable camelcase */
require('./gently-preamble')
const chai = require('chai')
var chaiAsPromised = require('chai-as-promised')
const TransloaditClient = require('../src/TransloaditClient')
const localtunnel = require('localtunnel')
const http = require('http')
const querystring = require('querystring')
const temp = require('temp')
const fs = require('fs')
const _ = require('lodash')
const { join } = require('path')
const { promisify } = require('util')
const { pipeline: streamPipeline } = require('stream')
const got = require('got')
const pipeline = promisify(streamPipeline)

const { expect } = chai
chai.use(chaiAsPromised)

async function downloadTmpFile (url) {
  const { path } = await temp.open('transloadit')
  await pipeline(got.stream(url), fs.createWriteStream(path))
  return path
}

const authKey = process.env.TRANSLOADIT_KEY
const authSecret = process.env.TRANSLOADIT_SECRET
if (authKey == null || authSecret == null) {
  let msg = 'specify environment variables TRANSLOADIT_KEY and TRANSLOADIT_SECRET'
  msg += ' to enable integration tests.'
  console.warn(msg)
} else {
  const startServerAsync = async (handler) => new Promise((resolve, reject) => {
    const server = http.createServer(handler)

    // Find a port to use
    let port = 8000
    server.on('error', err => {
      if (err.code === 'EADDRINUSE') {
        if (++port >= 65535) {
          server.close()
          reject(new Error('Failed to bind to port'))
        }
        return server.listen(port, '127.0.0.1')
      } else {
        return reject(err)
      }
    })

    server.listen(port, '127.0.0.1')

    // Once a port has been found and the server is ready, setup the
    // localtunnel
    return server.on('listening', async () => {
      try {
        const tunnel = await localtunnel(port)
        // console.log('localtunnel', tunnel.url)

        tunnel.on('error', console.error)
        tunnel.on('close', () => {
          // console.log('tunnel closed')
          server.close()
        })

        return resolve({
          url: tunnel.url,
          close () {
            tunnel.close()
          },
        })
      } catch (err) {
        if (err != null) {
          server.close()
          return reject(err)
        }
      }
    })
  })

  const startServer = (handler, cb) => startServerAsync(handler).then((server) => cb(null, server)).catch(cb)

  // https://transloadit.com/demos/importing-files/import-a-file-over-http
  const genericImg = 'https://demos.transloadit.com/66/01604e7d0248109df8c7cc0f8daef8/snowflake.jpg'
  const resizeOriginalStep = {
    robot : '/image/resize',
    use   : ':original',
    result: true,
    width : 130,
    height: 130,
  }
  const genericParams = {
    params: {
      steps: {
        import: {
          robot: '/http/import',
          url  : genericImg,
        },
        resize: {
          robot : '/image/resize',
          use   : 'import',
          result: true,
          width : 130,
          height: 130,
        },
      },
    },
    waitForCompletion: true,
  }

  describe('API integration', function () {
    this.timeout(100000)
    describe('assembly creation', () => {
      it('should create a retrievable assembly on the server', done => {
        const client = new TransloaditClient({ authKey, authSecret })

        return client.createAssembly(genericParams, (err, result) => {
          expect(err).to.not.exist
          expect(result).to.not.have.property('error')
          expect(result).to.have.property('ok')
          expect(result).to.have.property('assembly_id') // Since we're using it

          const id = result.assembly_id

          return client.getAssembly(id, (err, result) => {
            expect(err).to.not.exist
            expect(result).to.not.have.property('error')
            expect(result).to.have.property('ok')
            expect(result.assembly_id).to.equal(id)
            return done()
          })
        })
      })

      it('should get a full assembly status reliably', function (done) {
        this.timeout(0)

        const client = new TransloaditClient({ authKey, authSecret })

        const nbranches = 5
        let ndone = 0
        const branchDone = () => {
          if (++ndone === nbranches) {
            return done()
          }
        }

        const reproduce = nattempts => {
          if (nattempts === 0) {
            return branchDone()
          }

          client.createAssembly(genericParams, (err, { error, assembly_id } = {}) => {
            if (err != null || error != null) {
              return reproduce(nattempts - 1)
            }

            client.getAssembly(assembly_id, (err, result) => {
              if (err != null || result.error != null) {
                reproduce(nattempts - 1)
              }

              expect(result).to.have.property('assembly_url').that.exist
              reproduce(nattempts - 1)
            })
          })
        }

        // attempt to reproduce the incomplete status response 100 times
        for (let i = 0; i < nbranches; i++) reproduce(100 / nbranches)
      })

      it("should signal an error if a file selected for upload doesn't exist", async () => {
        const client = new TransloaditClient({ authKey, authSecret })

        const params = {
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
        }

        client.addFile('original', temp.path({ suffix: '.transloadit.jpg' })) // Non-existing path

        const promise = client.createAssemblyAsync(params)
        await expect(promise).to.eventually.be.rejectedWith(Error).and.have.property('code').that.equals('ENOENT')
      })

      it('should allow uploading files that do exist', async () => {
        const client = new TransloaditClient({ authKey, authSecret })

        const params = {
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
        }

        const path = await downloadTmpFile(genericImg)
            client.addFile('original', path)

        await client.createAssemblyAsync(params)
      })

      async function testUploadProgress (isResumable) {
        const client = new TransloaditClient({ authKey, authSecret })

        const params = {
          isResumable,
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
        }

        const path = await downloadTmpFile(genericImg)
        client.addFile('original', path)

        let progressCalled = false
        function onProgress (progress) {
          // console.log(progress)
          expect(progress.uploadProgress.uploadedBytes).to.exist
          progressCalled = true
        }
        await client.createAssemblyAsync(params, onProgress)
        expect(progressCalled).to.be.true
      }

      it('should trigger progress callbacks when uploading files, resumable', async () => {
        await testUploadProgress(true)
      })

      it('should trigger progress callbacks when uploading files, nonresumable', async () => {
        await testUploadProgress(false)
      })

      it('should trigger the callback when waitForCompletion is false', done => {
        const client = new TransloaditClient({ authKey, authSecret })
        const params = Object.assign({}, genericParams, { waitForCompletion: false })

        return client.createAssembly(params, (err, result) => {
          expect(err).to.not.exist
          expect(result).to.not.have.property('error')
          expect(result).to.have.property('ok')
          return done()
        })
      })

      it('should exit fast when assembly has failed', async () => {
        // An old bug caused it to continuously retry until timeout when errors such as INVALID_FILE_META_DATA
        const client = new TransloaditClient({ authKey, authSecret })
        const opts = {
          params: {
            steps: {
              resize: resizeOriginalStep,
            },
          },
          waitForCompletion: true,
        }
        client.addFile('file', join(__dirname, './fixtures/zerobytes.jpg'))

        const promise = client.createAssemblyAsync(opts)
        await promise.catch((err) => {
          expect(err.error).to.eq('INVALID_FILE_META_DATA')
          expect(err.assembly_id).to.exist
        })
        await expect(promise).to.eventually.be.rejectedWith(Error)
      }).timeout(7000)
    })

    describe('assembly cancelation', () => {
      it('should stop the assembly from reaching completion', async () => {
        const client = new TransloaditClient({ authKey, authSecret })

        // We need to ensure that the assembly doesn't complete before it can be
        // canceled, so we start an http server for the assembly to import from,
        // and delay transmission of data until we've already sent the cancel
        // request

        // Async book-keeping for delaying the response
        let sendServerResponse

        const promise = new Promise((resolve) => {
          sendServerResponse = resolve
        })

        const handler = async (req, res) => {
          // console.log('handler', req.url)

          expect(req.url).to.equal('/')

          await promise

          res.setHeader('Content-type', 'image/jpeg')
          res.writeHead(200)
          got.stream(genericImg).pipe(res)
        }

        const server = await startServerAsync(handler)

        try {
          const params = {
            params: {
              steps: {
                import: {
                  robot: '/http/import',
                  url  : server.url,
                },
                resize: {
                  robot : '/image/resize',
                  use   : 'import',
                  result: true,
                  width : 130,
                  height: 130,
                },
              },
            },
          }

          // Finally send the createAssembly request
          const { assembly_id: id } = await client.createAssemblyAsync(params)

          // Now delete it
          const resp = await client.deleteAssemblyAsync(id)

          // Allow the upload to finish
          sendServerResponse()

          expect(resp.ok).to.equal('ASSEMBLY_CANCELED')

          // Successful cancel requests get ASSEMBLY_CANCELED even when it
          // completed, so we now request the assembly status to check the
          // *actual* status.
          const resp2 = await client.getAssemblyAsync(id)
          expect(resp2.ok).to.equal('ASSEMBLY_CANCELED')
        } finally {
          server.close()
        }
      })
    })

    describe('replaying assemblies', () => {
      it('should replay an assembly after it has completed', done => {
        const client = new TransloaditClient({ authKey, authSecret })

        client.createAssembly(genericParams, (err, { assembly_id } = {}) => {
          expect(err).to.not.exist

          const originalId = assembly_id // eslint-disable-line camelcase

          // ensure that the assembly has completed
          const ensureCompletion = cb =>
            client.getAssembly(originalId, (err, result) => {
              expect(err).to.not.exist
              const ok = result.ok

              if (ok === 'ASSEMBLY_UPLOADING' || ok === 'ASSEMBLY_EXECUTING') {
                setTimeout(() => ensureCompletion(cb), 1000)
              } else {
                cb()
              }
            })

          // Start an asynchonous loop
          ensureCompletion(() =>
            client.replayAssembly({ assembly_id: originalId }, (err, { ok } = {}) => {
              expect(err).to.not.exist
              expect(ok).to.equal('ASSEMBLY_REPLAYING')
              done()
            }),
          )
        })
      })
    })

    describe('assembly list retrieval', () => {
      it('should retrieve a list of assemblies', done => {
        const client = new TransloaditClient({ authKey, authSecret })

        client.listAssemblies({}, (err, result) => {
          expect(err).to.not.exist
          expect(result).to.have.property('count')
          expect(result)
            .to.have.property('items')
            .that.is.instanceof(Array)
          done()
        })
      })

      it('should be able to handle pagination with a stream', done => {
        const client = new TransloaditClient({ authKey, authSecret })
        const assemblies = client.streamAssemblies({ pagesize: 2 })
        let n = 0
        let isDone = false

        assemblies.on('readable', () => {
          const assembly = assemblies.read()

          if (isDone) return

          if (assembly == null) {
            return done()
          }

          if (n === 5) {
            isDone = true
            return done()
          }

          expect(assembly).to.have.property('id')
          n++
        })
      })
    })

    describe('assembly notification', () => {
      // helper function
      const streamToString = (stream, cb) => {
        const chunks = []
        stream.on('data', chunk => chunks.push(chunk))
        stream.on('error', err => cb(err))
        stream.on('end', () => cb(null, chunks.join('')))
      }

      const testCase = (desc, endBehavior) =>
        it(desc, done => {
          const client = new TransloaditClient({ authKey, authSecret })

          // listens for notifications
          const handler = (req, res) => {
            expect(req.url).to.equal('/')

            expect(req.method).to.equal('POST')
            streamToString(req, (err, body) => {
              if (err) {
                console.error(err)
              }
              const result = JSON.parse(querystring.parse(body).transloadit)
              expect(result).to.have.property('ok')
              res.writeHead(200)
              res.end()
              if (result.ok !== 'ASSEMBLY_COMPLETED') return
              endBehavior(client, result.assembly_id, done)
            })
          }

          startServer(handler, (err, server) => {
            expect(err).to.not.exist

            const params = { params: _.extend({}, genericParams.params, { notify_url: server.url }) }

            client.createAssembly(params, (err, result) => expect(err).to.not.exist)
          })
        })

      testCase('should send a notification upon assembly completion', (client, id, done) => done())

      let notificationsRecvd = 0
      testCase('should replay the notification when requested', (client, id, done) => {
        if (notificationsRecvd++ === 0) {
          setTimeout(() => {
            client.replayAssemblyNotification({ assembly_id: id }, err => expect(err).to.not.exist)
          }, 2000)
        } else {
          done()
        }
      })
    })

    describe('template methods', () => {
      // can contain only lowercase latin letters, numbers, and dashes.
      const templName = `node-sdk-test-${new Date().toISOString().toLocaleLowerCase('en-US').replace(/[^0-9a-z-]/g, '-')}`
      let templId = null
      const client = new TransloaditClient({ authKey, authSecret })

      it('should allow creating a template', async () => {
        const { id } = await client.createTemplateAsync({ name: templName, template: genericParams.params })
        templId = id
      })

      it("should be able to fetch a template's definition", async () => {
        expect(templId).to.exist

        const { name, content } = await client.getTemplateAsync(templId)
        expect(name).to.equal(templName)
        expect(content).to.deep.equal(genericParams.params)
      })

      it('should delete the template successfully', done => {
        expect(templId).to.exist

        client.deleteTemplate(templId, (err, { ok } = {}) => {
          expect(err).to.not.exist
          expect(ok).to.equal('TEMPLATE_DELETED')
          client.getTemplate(templId, (err, result) => {
            expect(result).to.not.exist
            expect(err).to.exist
            expect(err.error).to.equal('TEMPLATE_NOT_FOUND')
            done()
          })
        })
      })
    })
  })
}
<|endoftext|>


<commit_before>var _ = require('underscore');
var callerId = require('caller-id');
var cloudinary = require('cloudinary');
var debug = require('debug')('keystone:core:options');
var mandrillapi = require('mandrill-api');
var path = require('path');
var url = require('url');
var utils = require('keystone-utils');

function options() {

	var exports = {};

	/**
	 * This file contains methods specific to dealing with Keystone's options.
	 * All exports are added to the Keystone.prototype
	 */

	// Deprecated options that have been mapped to new keys
	var remappedOptions = {
		'signin success': 'signin redirect',
		'signout': 'signout url'
	};

	// Determines if path is absolute or relative
	function isAbsolutePath(value) {
		return path.resolve(value) === path.normalize(value).replace(new RegExp(path.sep + '$'), '');
	}

	/**
	 * Sets keystone options
	 *
	 * ####Example:
	 *
	 *     keystone.set('user model', 'User') // sets the 'user model' option to `User`
	 *
	 * @param {String} key
	 * @param {String} value
	 * @api public
	 */
	exports.set = function(key, value) {

		if (arguments.length === 1) {
			return this._options[key];
		}

		if (remappedOptions[key]) {
			if (this.get('logger')) {
				console.log('\nWarning: the `' + key + '` option has been deprecated. Please use `' + remappedOptions[key] + '` instead.\n\n' +
					'Support for `' + key + '` will be removed in a future version.');
			}
			key = remappedOptions[key];
		}

		switch (key) {
			// warn on deprecated/old/invalid settings
			case 'less middleware options':
			case 'less parser options':
			case 'less compiler options':
				if (this.get('logger')) {
					console.log('\nWarning: less-middleware has changed the way it handles options, and ' +
						'\n`' + key + '` is no longer supported. You should simply use `less options` now;' +
						'\nsee https://github.com/emberfeather/less.js-middleware for details.');
				}
			break;

			// handle special settings
			case 'cloudinary config':
				if (_.isString(value)) {
					var parts = url.parse(value, true);
					var auth = parts.auth ? parts.auth.split(':') : [];
					value = {
						cloud_name: parts.host,
						api_key: auth[0],
						api_secret: auth[1],
						private_cdn: parts.pathname != null,
						secure_distribution: parts.pathname && parts.pathname.substring(1)
					};
				}
				if (_.isObject(value)) {
					cloudinary.config(value);
				}
				value = cloudinary.config();
			break;
			case 'mandrill api key':
				if (value) {
					debug('found mandril key');
					this.mandrillAPI = new mandrillapi.Mandrill(value);
				}
			break;
			case 'auth':
				if (value === true && !this.get('session')) {
					debug('setting session for auth');
					this.set('session', true);
				}
			break;
			case 'nav':
				debug('setting nav');
				this.nav = this.initNav(value);
			break;
			case 'mongo':
				if ('string' !== typeof value) {
					if (Array.isArray(value) && (value.length === 2 || value.length === 3)) {
						console.log('\nWarning: using an array for the `mongo` option has been deprecated.\nPlease use a mongodb connection string, e.g. mongodb://localhost/db_name instead.\n\n' +
							'Support for arrays as the `mongo` setting will be removed in a future version.');
						value = (value.length === 2) ? 'mongodb://' + value[0] + '/' + value[1] : 'mongodb://' + value[0] + ':' + value[2] + '/' + value[1];
					} else {
						console.error('\nInvalid Configuration:\nThe `mongo` option must be a mongodb connection string, e.g. mongodb://localhost/db_name\n');
						process.exit(1);
					}
				}
			break;
			case 'module root':
				// if relative path is used, resolve it based on the caller's path
				if (!isAbsolutePath(value)) {
					var caller = callerId.getData();
					value = path.resolve(path.dirname(caller.filePath), value);
				}
			break;
			case 'app':
				this.app = value;
			break;
			case 'mongoose':
				this.mongoose = value;
			break;
			case 'frame guard':
				var validFrameGuardOptions = ['deny', 'sameorigin'];

				if (value === true) {
					value = 'deny';
				}
				if (utils.isString(value)) {
					value = value.toLowerCase();
					if (validFrameGuardOptions.indexOf(value) < 0) {
						value = false;
					}
				} else if ('boolean' !== typeof value) {
					value = false;
				}
			break;
		}

		this._options[key] = value;
		return this;
	};

	/**
	 * Sets multiple keystone options.
	 *
	 * ####Example:
	 *
	 *     keystone.options({test: value}) // sets the 'test' option to `value`
	 *
	 * @param {Object} options
	 * @api public
	 */

	exports.options = function(options) {
		if (!arguments.length) {
			return this._options;
		}
		if (utils.isObject(options)) {
			debug('settings options');
			var keys = Object.keys(options),
				i = keys.length,
				k;
			while (i--) {
				k = keys[i];
				this.set(k, options[k]);
			}
		}
		return this._options;
	};

	/**
	 * Gets keystone options
	 *
	 * ####Example:
	 *
	 *     keystone.get('test') // returns the 'test' value
	 *
	 * @param {String} key
	 * @api public
	 */

	exports.get = exports.set;

	/**
	 * Gets an expanded path option, expanded to include moduleRoot if it is relative
	 *
	 * ####Example:
	 *
	 *     keystone.get('pathOption', 'defaultValue')
	 *
	 * @param {String} key
	 * @param {String} defaultValue
	 * @api public
	 */

	exports.getPath = function(key, defaultValue) {
		return this.expandPath(this.get(key) || defaultValue);
	};

	/**
	 * Expands a path to include moduleRoot if it is relative
	 *
	 * @param {String} pathValue
	 * @api public
	 */

	exports.expandPath = function(pathValue) {
		pathValue = ('string' === typeof pathValue && pathValue.substr(0, 1) !== path.sep && pathValue.substr(1, 2) !== ':\\')
			? path.join(this.get('module root'), pathValue)
			: pathValue;
		return pathValue;
	};

	return exports;

}

module.exports = options;
<commit_msg>Removing deprecated options<commit_after>var _ = require('underscore');
var callerId = require('caller-id');
var cloudinary = require('cloudinary');
var debug = require('debug')('keystone:core:options');
var mandrillapi = require('mandrill-api');
var path = require('path');
var url = require('url');
var utils = require('keystone-utils');

function options() {

	var exports = {};

	/**
	 * This file contains methods specific to dealing with Keystone's options.
	 * All exports are added to the Keystone.prototype
	 */

	// Deprecated options that have been mapped to new keys
	var remappedOptions = {
		'signin success': 'signin redirect',
		'signout': 'signout url'
	};

	// Determines if path is absolute or relative
	function isAbsolutePath(value) {
		return path.resolve(value) === path.normalize(value).replace(new RegExp(path.sep + '$'), '');
	}

	/**
	 * Sets keystone options
	 *
	 * ####Example:
	 *
	 *     keystone.set('user model', 'User') // sets the 'user model' option to `User`
	 *
	 * @param {String} key
	 * @param {String} value
	 * @api public
	 */
	exports.set = function(key, value) {

		if (arguments.length === 1) {
			return this._options[key];
		}

		if (remappedOptions[key]) {
			if (this.get('logger')) {
				console.log('\nWarning: the `' + key + '` option has been deprecated. Please use `' + remappedOptions[key] + '` instead.\n\n' +
					'Support for `' + key + '` will be removed in a future version.');
			}
			key = remappedOptions[key];
		}

		switch (key) {
			// handle special settings
			case 'cloudinary config':
				if (_.isString(value)) {
					var parts = url.parse(value, true);
					var auth = parts.auth ? parts.auth.split(':') : [];
					value = {
						cloud_name: parts.host,
						api_key: auth[0],
						api_secret: auth[1],
						private_cdn: parts.pathname != null,
						secure_distribution: parts.pathname && parts.pathname.substring(1)
					};
				}
				if (_.isObject(value)) {
					cloudinary.config(value);
				}
				value = cloudinary.config();
			break;
			case 'mandrill api key':
				if (value) {
					debug('found mandril key');
					this.mandrillAPI = new mandrillapi.Mandrill(value);
				}
			break;
			case 'auth':
				if (value === true && !this.get('session')) {
					debug('setting session for auth');
					this.set('session', true);
				}
			break;
			case 'nav':
				debug('setting nav');
				this.nav = this.initNav(value);
			break;
			case 'mongo':
				if ('string' !== typeof value) {
					if (Array.isArray(value) && (value.length === 2 || value.length === 3)) {
						console.log('\nWarning: using an array for the `mongo` option has been deprecated.\nPlease use a mongodb connection string, e.g. mongodb://localhost/db_name instead.\n\n' +
							'Support for arrays as the `mongo` setting will be removed in a future version.');
						value = (value.length === 2) ? 'mongodb://' + value[0] + '/' + value[1] : 'mongodb://' + value[0] + ':' + value[2] + '/' + value[1];
					} else {
						console.error('\nInvalid Configuration:\nThe `mongo` option must be a mongodb connection string, e.g. mongodb://localhost/db_name\n');
						process.exit(1);
					}
				}
			break;
			case 'module root':
				// if relative path is used, resolve it based on the caller's path
				if (!isAbsolutePath(value)) {
					var caller = callerId.getData();
					value = path.resolve(path.dirname(caller.filePath), value);
				}
			break;
			case 'app':
				this.app = value;
			break;
			case 'mongoose':
				this.mongoose = value;
			break;
			case 'frame guard':
				var validFrameGuardOptions = ['deny', 'sameorigin'];

				if (value === true) {
					value = 'deny';
				}
				if (utils.isString(value)) {
					value = value.toLowerCase();
					if (validFrameGuardOptions.indexOf(value) < 0) {
						value = false;
					}
				} else if ('boolean' !== typeof value) {
					value = false;
				}
			break;
		}

		this._options[key] = value;
		return this;
	};

	/**
	 * Sets multiple keystone options.
	 *
	 * ####Example:
	 *
	 *     keystone.options({test: value}) // sets the 'test' option to `value`
	 *
	 * @param {Object} options
	 * @api public
	 */

	exports.options = function(options) {
		if (!arguments.length) {
			return this._options;
		}
		if (utils.isObject(options)) {
			debug('settings options');
			var keys = Object.keys(options),
				i = keys.length,
				k;
			while (i--) {
				k = keys[i];
				this.set(k, options[k]);
			}
		}
		return this._options;
	};

	/**
	 * Gets keystone options
	 *
	 * ####Example:
	 *
	 *     keystone.get('test') // returns the 'test' value
	 *
	 * @param {String} key
	 * @api public
	 */

	exports.get = exports.set;

	/**
	 * Gets an expanded path option, expanded to include moduleRoot if it is relative
	 *
	 * ####Example:
	 *
	 *     keystone.get('pathOption', 'defaultValue')
	 *
	 * @param {String} key
	 * @param {String} defaultValue
	 * @api public
	 */

	exports.getPath = function(key, defaultValue) {
		return this.expandPath(this.get(key) || defaultValue);
	};

	/**
	 * Expands a path to include moduleRoot if it is relative
	 *
	 * @param {String} pathValue
	 * @api public
	 */

	exports.expandPath = function(pathValue) {
		pathValue = ('string' === typeof pathValue && pathValue.substr(0, 1) !== path.sep && pathValue.substr(1, 2) !== ':\\')
			? path.join(this.get('module root'), pathValue)
			: pathValue;
		return pathValue;
	};

	return exports;

}

module.exports = options;
<|endoftext|>


<commit_before>'use strict';

const test = require('tape');
const expect = require('chai').expect;
const tracker = require('../lib/index');
const intercom = require('../trackers/intercom');
const ga = require('../trackers/ga');
const gtm = require('../trackers/gtm');

const intercomServer = require('../trackers/server/intercom');
const config = require('./helpers/config');
const { intercomCreatedAt } = require('../lib/utils');

test('Test tracker method', (assert) => {
	expect(tracker).to.be.a('function');
	expect(intercom).to.be.a('function');
	expect(ga).to.be.a('function');
	expect(intercomServer).to.be.a('function');
	assert.end();
});

test('Throw error if call createEvent and dont create an user before', (assert) => {
	const intercomTrackerServer = intercomServer({
		appId: config.appId,
		apiKey: config.apiKey
	});

	intercomTrackerServer.createEvent('rejectedEvent').catch((errorResult) => {
		expect(errorResult).to.be.equal('You just need to pass a valid email or userId');
		assert.end();
	});
});

test('Create user and send event', (assert) => {
	const intercomTrackerServer = intercomServer({
		appId: config.appId,
		apiKey: config.apiKey
	});

	intercomTrackerServer.createUser({
		userId: '393039',
		email: 'john@doe.com',
		name: 'John Doe'
	});

	intercomTrackerServer.createEvent(`testEvent - ${intercomCreatedAt()}`, {
		email: 'john@doe.com',
		metadata: {
			name: 'just john doe'
		}
	});

	intercomTrackerServer.createUser({
		userId: '291029',
		email: 'senado@lheira.com',
		name: 'Senado Federal'
	});

	intercomTrackerServer.createEvent(`testEvent - ${intercomCreatedAt()}`, {
		email: 'john@doe.com',
		metadata: {
			name: 'just john doe'
		}
	});

	intercomTrackerServer.createEvent(`test - ${intercomCreatedAt()}`, {
		metadata: {
			eventFor: 'last created user'
		}
	});

	expect(intercomTrackerServer).to.have.any.keys('createEvent', 'createUser');
	assert.end();
});

test('Intercom tracker (client side) test', (assert) => {
	const intercomTracker = intercom({
		appId: config.appId
	});

	// logged-out user
	intercomTracker.update();

	// logged-in user
	intercomTracker.update({
		name: 'John Doe',
		email: 'john@doe.com',
		userId: 'anonymous'
	});

	intercomTracker.shutdown();

	expect(intercomTracker).to.have.all.keys('createEvent', 'update', 'shutdown');
	assert.end();
});

test('GA Tracker (client side) test', (assert) => {
	const gaTracker = ga();

	gaTracker.createEvent('eventError');

	gaTracker.createEvent('eventError', {
		action: 'click'
	});

	gaTracker.createEvent('eventError', {
		action: 'click',
		metadata: {
			label: 'Any label'
		}
	});

	gaTracker.createEvent('eventError', {
		action: 'click',
		metadata: {
			value: 'Any value'
		}
	});

	gaTracker.createEvent('eventError', {
		action: 'click',
		metadata: {
			url: 'http://sales.com'
		}
	});

	expect(gaTracker).to.have.property('createEvent');
	assert.end();
});

test('GTM Tracker (client side)', (assert) => {
	const gtmTracker = gtm();

	gtmTracker
		.createEvent('event-test')
		.createEvent('another-event', {
			method: 'facebook',
			'validation-error': 'Not authorized'
		});

	expect(gtmTracker).to.have.property('createEvent');
	assert.end();
});

test('All trackers (client side)', (assert) => {
	const intercomTracker = intercom({
		appId: config.appId
	});
	const gaTracker = ga();
	const gtmTracker = gtm();
	const eventTracker = tracker(intercomTracker, gaTracker, gtmTracker);

	eventTracker.createEvent('store_purchase', {
		action: 'click',
		metadata: {
			value: 10,
			label: 'Product Name',
			url: 'http://www.url.com'
		},
		method: 'facebook',
		'validation-error': 'Not authorized'
	});

	expect(eventTracker).to.have.property('createEvent');
	assert.end();
});
<commit_msg>Update tests<commit_after>'use strict';

const test = require('tape');
const expect = require('chai').expect;
const tracker = require('../lib/index');
const intercom = require('../trackers/intercom/client');
const ga = require('../trackers/ga');
const gtm = require('../trackers/gtm');

const intercomServer = require('../trackers/intercom/server');
const config = require('./helpers/config');
const { intercomCreatedAt } = require('../lib/utils');

test('Test tracker method', (assert) => {
	expect(tracker).to.be.a('function');
	expect(intercom).to.be.a('function');
	expect(ga).to.be.a('function');
	expect(intercomServer).to.be.a('function');
	assert.end();
});

test('Throw error if call createEvent and dont create an user before', (assert) => {
	const intercomTrackerServer = intercomServer({
		appId: config.appId,
		apiKey: config.apiKey
	});

	intercomTrackerServer.createEvent('rejectedEvent').catch((errorResult) => {
		expect(errorResult).to.be.equal('You just need to pass a valid email or userId');
		assert.end();
	});
});

test('Create user and send event', (assert) => {
	const intercomTrackerServer = intercomServer({
		appId: config.appId,
		apiKey: config.apiKey
	});

	intercomTrackerServer.createUser({
		userId: '393039',
		email: 'john@doe.com',
		name: 'John Doe'
	});

	intercomTrackerServer.createEvent(`testEvent - ${intercomCreatedAt()}`, {
		email: 'john@doe.com',
		metadata: {
			name: 'just john doe'
		}
	});

	intercomTrackerServer.createUser({
		userId: '291029',
		email: 'senado@lheira.com',
		name: 'Senado Federal'
	});

	intercomTrackerServer.createEvent(`testEvent - ${intercomCreatedAt()}`, {
		email: 'john@doe.com',
		metadata: {
			name: 'just john doe'
		}
	});

	intercomTrackerServer.createEvent(`test - ${intercomCreatedAt()}`, {
		metadata: {
			eventFor: 'last created user'
		}
	});

	expect(intercomTrackerServer).to.have.any.keys('createEvent', 'createUser');
	assert.end();
});

test('Intercom tracker (client side) test', (assert) => {
	const intercomTracker = intercom({
		appId: config.appId
	});

	// logged-out user
	intercomTracker.update();

	// logged-in user
	intercomTracker.update({
		name: 'John Doe',
		email: 'john@doe.com',
		userId: 'anonymous'
	});

	intercomTracker.shutdown();

	expect(intercomTracker).to.have.all.keys('createEvent', 'update', 'shutdown');
	assert.end();
});

test('GA Tracker (client side) test', (assert) => {
	const gaTracker = ga();

	gaTracker.createEvent('eventError');

	gaTracker.createEvent('eventError', {
		action: 'click'
	});

	gaTracker.createEvent('eventError', {
		action: 'click',
		metadata: {
			label: 'Any label'
		}
	});

	gaTracker.createEvent('eventError', {
		action: 'click',
		metadata: {
			value: 'Any value'
		}
	});

	gaTracker.createEvent('eventError', {
		action: 'click',
		metadata: {
			url: 'http://sales.com'
		}
	});

	expect(gaTracker).to.have.property('createEvent');
	assert.end();
});

test('GTM Tracker (client side)', (assert) => {
	const gtmTracker = gtm();

	gtmTracker
		.createEvent('event-test')
		.createEvent('another-event', {
			method: 'facebook',
			'validation-error': 'Not authorized'
		});

	expect(gtmTracker).to.have.property('createEvent');
	assert.end();
});

test('All trackers (client side)', (assert) => {
	const intercomTracker = intercom({
		appId: config.appId
	});
	const gaTracker = ga();
	const gtmTracker = gtm();
	const eventTracker = tracker(intercomTracker, gaTracker, gtmTracker);

	eventTracker.createEvent('store_purchase', {
		action: 'click',
		metadata: {
			value: 10,
			label: 'Product Name',
			url: 'http://www.url.com'
		},
		method: 'facebook',
		'validation-error': 'Not authorized'
	});

	expect(eventTracker).to.have.property('createEvent');
	assert.end();
});
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import Relay from 'react-relay';
import GCForm from './forms/GCForm';
import Form from 'react-formal';
import yup from 'yup';
import moment from 'moment';
import ReactQuill from 'react-quill';
import {Card, CardActions, CardExpandable, CardTitle, CardHeader, CardText, FlatButton, TextField, DropDownMenu, SelectField, DatePicker, TimePicker, Checkbox} from 'material-ui';
import InfoHeader from './InfoHeader'
import {USTimeZones} from './data/USTimeZones';

require('quill/dist/quill.base.css');
require('quill/dist/quill.snow.css');

class EventEdit extends React.Component {

  componentDidMount() {
    if (this.props.event.host){
      let {email} = this.props.event.host
      this.props.relay.setVariables({personFilters: {email}})
    }
  }

  eventTypes() {
    let allTypes = {}
    this.props.listContainer.eventTypes.forEach((eventType) => {
      allTypes[eventType.id] = eventType.name
    })
    return allTypes;
  }

  timezones() {
    let allZones = {}
    USTimeZones.forEach((zone) => {
      allZones[zone.value] = zone.name
    })
    return allZones;
  }

  volunteerShowOptions() {
    return {
      '1': 'Yes',
      '0': 'No',
      '-1': 'Use Event Type Default'
    }
  }

  eventSearchableOptions() {
    // actually get valid options for event type later
    let eventSearchableOptions = {
      '1': 'Make Public',
      '0': 'Make Private',
      '-2': 'Use Event Type Default'
    };
    return eventSearchableOptions;
  }

  renderForm() {
    let event = this.props.event
    const people = this.props.listContainer.people.edges
    const host = (people.length === 0) ? null : people[0].node

    const eventSchema = yup.object({
      name: yup
        .string()
        .default(event.name)
        .required(),

      eventTypeId: yup
        .string()
        .default(event.eventType.id)
        .required(),

      description: yup.string().default(event.description)
        .required(),

      rsvpEmailReminderHours: yup.number()
        .default(event.rsvpEmailReminderHours)
        .min(0)
        .nullable(),

      startDate: yup.date()
        .default(event.startDate)
        .required(),

      localTimezone: yup.string()
        .default(event.localTimezone)
        .required(),

      duration: yup.object({
        h: yup.number()
          .default(Math.floor(event.duration / 60))
          .min(-1)
          .nullable()
          .required(),

        m: yup.number()
          .default(event.duration % 60)
          .min(-1).max(59)
          .nullable()
          .required(),
      }),

      venueName: yup.string().default(event.venueName)
        .required(),

      venueAddr1: yup.string().default(event.venueAddr1)
        .required(),

      venueAddr2: yup.string().default(event.venueAddr2)
        .nullable(),

      venueCity: yup.string().default(event.venueCity)
        .required(),

      venueState: yup.string().default(event.venueState)
        .required(),

      venueZip: yup.string().default(event.venueZip)
        .required(),

      venueCountry: yup.string().default(event.venueCountry)
        .required(),

      venueDirections: yup.string().default(event.venueDirections)
        .nullable(),
      capacity: yup.number()
        .default(event.capacity)
        .min(0)
        .required(),

      hostEmail: yup.string().email()
        .default((event.host) ? event.host.email : null),

      contactPhone: yup.string()
        .default(event.contactPhone),

      publicPhone: yup.boolean()
        .default(event.publicPhone),

      hostReceiveRsvpEmails: yup.boolean()
        .default(event.hostReceiveRsvpEmails),

      rsvpUseReminderEmail: yup.boolean()
        .default(event.rsvpUseReminderEmail),

      attendeeVolunteerShow: yup.string()
        .default(String(event.attendeeVolunteerShow)),

      attendeeVolunteerMessage: yup.string()
        .default(event.attendeeVolunteerMessage),

      isSearchable: yup.string()
        .default(String(event.isSearchable)),

      isOfficial: yup.boolean()
        .default(event.isOfficial)

    });

    const form = (
      <GCForm
        ref="form"
        schema={eventSchema}
        defaultValue={eventSchema.default()}
        onSubmit={ (data) => {
          data.description = this.refs.quill.getEditorContents();
          data.duration = data.duration.h * 60 + data.duration.m;
          data.isSearchable = Number(data.isSearchable);
          if (host)
            data.hostId = host.id
          delete data.hostEmail

          this.props.onSubmit(data)
        }}
      >
        <InfoHeader content='Event Information' />
        <Form.Field
          name='eventTypeId'
          type='select'
          label='Event Type'
          fullWidth={true}
          choices={this.eventTypes()}
        />

        <Form.Field
          name='name'
          label='Event Name'
          fullWidth={true}
        />

        Event Description
        <ReactQuill defaultValue={event.description} theme="snow" ref="quill" />

        <InfoHeader content='Event Date & Time' />

        <Form.Field
          name='startDate'
          label='Start Date'
          type='date'
          utcOffset={event.localUTCOffset}
        />

        <Form.Field
          name='startDate'
          label='Start Time'
          type='time'
          utcOffset={event.localUTCOffset}
        />

        <Form.Field
          name='localTimezone'
          type='select'
          label='Time Zone'
          choices={this.timezones()}
          style={{
            marginTop: 5
          }}
        />

        <Form.Field
          name='duration.h'
          label="Duration (Hours)"
          type='number'
          min="0"
        />

        <Form.Field
          name='duration.m'
          label="Duration (Minutes)"
          type='number'
          min="0"
          max="59"
        />

        <InfoHeader content='Event Location' />

        <Form.Field
          name='venueName'
          label='Venue Name'
        />

        <Form.Field
          name='capacity'
          label="Venue Capacity (enter 0 for unlimited)"
          type='number'
          min="0"
        />

        <Form.Field
          name='venueAddr1'
          label='Address Line 1'
          fullWidth={true}
        />

        <Form.Field
          name='venueAddr2'
          label='Address Line 2'
          fullWidth={true}
        />

        <Form.Field
          name='venueCity'
          label='City'
        />

        <Form.Field
          name='venueState'
          label='State'
        />

        <Form.Field
          name='venueZip'
          label='Zip Code'
        />

        <Form.Field
          name='venueCountry'
          label='Country'
        />

        <Form.Field
          name='venueDirections'
          label='Directions to Venue'
          multiLine={true}
          fullWidth={true}
        />

        <InfoHeader content='Event Host' />
        {(host) ? `${host.firstName} ${host.lastName}` : 'No host name available'}

        <Form.Field
          name="hostEmail"
          type="email"
          label="Host Email"
          errorText={(host) ? null : 'No account found'}
          onChange={(value) => {
            this.props.relay.setVariables({personFilters: {email: value}})
          }}
        />

        <Form.Field
          name="contactPhone"
          type="phone"
          label="Contact Phone"
        />

        <Form.Field
          name="publicPhone"
          label="Make Contact Number Public"
        />

        <Form.Field
          name="hostReceiveRsvpEmails"
          label="Send Host RSVPs via Email"
        />

        <InfoHeader content='Event Attendees' />

        <Form.Field
          name="rsvpUseReminderEmail"
          label="Send Guests RSVP Email Reminder"
        />

        <Form.Field
          name='rsvpEmailReminderHours'
          label="RSVP Reminder Hours"
          type='number'
          min="0"
        />

        <Form.Field
          name="attendeeVolunteerShow"
          type='select'
          label="Ask Attendees to Volunteer"
          fullWidth={true}
          choices={this.volunteerShowOptions()}
        />

        <Form.Field
          name='attendeeVolunteerMessage'
          label="Message for Event Volunteers"
          multiLine={true}
          fullWidth={true}
        />

        <InfoHeader content='Event Settings' />

        <Form.Field
          name='isSearchable'
          type='select'
          label='Make Event Public?'
          fullWidth={true}
          choices={this.eventSearchableOptions()}
        />

        <Form.Field
          name="isOfficial"
          label="Mark as an official campaign event"
        />

      <Form.Button  style={ { display: "none" } } ref="submit" type='submit' label='Submit Changes' fullWidth={true} />

    </GCForm>)

    return form
  }

  render() {
    return (

      <CardText>
        {this.renderForm()}
      </CardText>

    )
  }

  submit() {
    jQuery("button", ReactDOM.findDOMNode(this.refs.submit)).click()
  }
}

export default Relay.createContainer(EventEdit, {
  initialVariables: {
    personFilters: {},
  },
  fragments: {
    listContainer: () => Relay.QL`
      fragment on ListContainer {
        eventTypes {
          id
          name
        }
        people(
          first: 1
          personFilters: $personFilters
        ) {
          edges {
            cursor
            node {
              id
              firstName
              lastName
              email
            }
          }
        }
      }
    `
  }
})
<commit_msg>Let selects auto-size rather than force full width<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import Relay from 'react-relay';
import GCForm from './forms/GCForm';
import Form from 'react-formal';
import yup from 'yup';
import moment from 'moment';
import ReactQuill from 'react-quill';
import {Card, CardActions, CardExpandable, CardTitle, CardHeader, CardText, FlatButton, TextField, DropDownMenu, SelectField, DatePicker, TimePicker, Checkbox} from 'material-ui';
import InfoHeader from './InfoHeader'
import {USTimeZones} from './data/USTimeZones';

require('quill/dist/quill.base.css');
require('quill/dist/quill.snow.css');

class EventEdit extends React.Component {

  componentDidMount() {
    if (this.props.event.host){
      let {email} = this.props.event.host
      this.props.relay.setVariables({personFilters: {email}})
    }
  }

  eventTypes() {
    let allTypes = {}
    this.props.listContainer.eventTypes.forEach((eventType) => {
      allTypes[eventType.id] = eventType.name
    })
    return allTypes;
  }

  timezones() {
    let allZones = {}
    USTimeZones.forEach((zone) => {
      allZones[zone.value] = zone.name
    })
    return allZones;
  }

  volunteerShowOptions() {
    return {
      '1': 'Yes',
      '0': 'No',
      '-1': 'Use Event Type Default'
    }
  }

  eventSearchableOptions() {
    // actually get valid options for event type later
    let eventSearchableOptions = {
      '1': 'Make Public',
      '0': 'Make Private',
      '-2': 'Use Event Type Default'
    };
    return eventSearchableOptions;
  }

  renderForm() {
    let event = this.props.event
    const people = this.props.listContainer.people.edges
    const host = (people.length === 0) ? null : people[0].node

    const eventSchema = yup.object({
      name: yup
        .string()
        .default(event.name)
        .required(),

      eventTypeId: yup
        .string()
        .default(event.eventType.id)
        .required(),

      description: yup.string().default(event.description)
        .required(),

      rsvpEmailReminderHours: yup.number()
        .default(event.rsvpEmailReminderHours)
        .min(0)
        .nullable(),

      startDate: yup.date()
        .default(event.startDate)
        .required(),

      localTimezone: yup.string()
        .default(event.localTimezone)
        .required(),

      duration: yup.object({
        h: yup.number()
          .default(Math.floor(event.duration / 60))
          .min(-1)
          .nullable()
          .required(),

        m: yup.number()
          .default(event.duration % 60)
          .min(-1).max(59)
          .nullable()
          .required(),
      }),

      venueName: yup.string().default(event.venueName)
        .required(),

      venueAddr1: yup.string().default(event.venueAddr1)
        .required(),

      venueAddr2: yup.string().default(event.venueAddr2)
        .nullable(),

      venueCity: yup.string().default(event.venueCity)
        .required(),

      venueState: yup.string().default(event.venueState)
        .required(),

      venueZip: yup.string().default(event.venueZip)
        .required(),

      venueCountry: yup.string().default(event.venueCountry)
        .required(),

      venueDirections: yup.string().default(event.venueDirections)
        .nullable(),
      capacity: yup.number()
        .default(event.capacity)
        .min(0)
        .required(),

      hostEmail: yup.string().email()
        .default((event.host) ? event.host.email : null),

      contactPhone: yup.string()
        .default(event.contactPhone),

      publicPhone: yup.boolean()
        .default(event.publicPhone),

      hostReceiveRsvpEmails: yup.boolean()
        .default(event.hostReceiveRsvpEmails),

      rsvpUseReminderEmail: yup.boolean()
        .default(event.rsvpUseReminderEmail),

      attendeeVolunteerShow: yup.string()
        .default(String(event.attendeeVolunteerShow)),

      attendeeVolunteerMessage: yup.string()
        .default(event.attendeeVolunteerMessage),

      isSearchable: yup.string()
        .default(String(event.isSearchable)),

      isOfficial: yup.boolean()
        .default(event.isOfficial)

    });

    const form = (
      <GCForm
        ref="form"
        schema={eventSchema}
        defaultValue={eventSchema.default()}
        onSubmit={ (data) => {
          data.description = this.refs.quill.getEditorContents();
          data.duration = data.duration.h * 60 + data.duration.m;
          data.isSearchable = Number(data.isSearchable);
          if (host)
            data.hostId = host.id
          delete data.hostEmail

          this.props.onSubmit(data)
        }}
      >
        <InfoHeader content='Event Information' />
        <Form.Field
          name='eventTypeId'
          type='select'
          label='Event Type'
          choices={this.eventTypes()}
        />

        <Form.Field
          name='name'
          label='Event Name'
          fullWidth={true}
        />

        Event Description
        <ReactQuill defaultValue={event.description} theme="snow" ref="quill" />

        <InfoHeader content='Event Date & Time' />

        <Form.Field
          name='startDate'
          label='Start Date'
          type='date'
          utcOffset={event.localUTCOffset}
        />

        <Form.Field
          name='startDate'
          label='Start Time'
          type='time'
          utcOffset={event.localUTCOffset}
        />

        <Form.Field
          name='localTimezone'
          type='select'
          label='Time Zone'
          choices={this.timezones()}
          style={{
            marginTop: 5
          }}
        />

        <Form.Field
          name='duration.h'
          label="Duration (Hours)"
          type='number'
          min="0"
        />

        <Form.Field
          name='duration.m'
          label="Duration (Minutes)"
          type='number'
          min="0"
          max="59"
        />

        <InfoHeader content='Event Location' />

        <Form.Field
          name='venueName'
          label='Venue Name'
        />

        <Form.Field
          name='capacity'
          label="Venue Capacity (enter 0 for unlimited)"
          type='number'
          min="0"
        />

        <Form.Field
          name='venueAddr1'
          label='Address Line 1'
          fullWidth={true}
        />

        <Form.Field
          name='venueAddr2'
          label='Address Line 2'
          fullWidth={true}
        />

        <Form.Field
          name='venueCity'
          label='City'
        />

        <Form.Field
          name='venueState'
          label='State'
        />

        <Form.Field
          name='venueZip'
          label='Zip Code'
        />

        <Form.Field
          name='venueCountry'
          label='Country'
        />

        <Form.Field
          name='venueDirections'
          label='Directions to Venue'
          multiLine={true}
          fullWidth={true}
        />

        <InfoHeader content='Event Host' />
        {(host) ? `${host.firstName} ${host.lastName}` : 'No host name available'}

        <Form.Field
          name="hostEmail"
          type="email"
          label="Host Email"
          errorText={(host) ? null : 'No account found'}
          onChange={(value) => {
            this.props.relay.setVariables({personFilters: {email: value}})
          }}
        />

        <Form.Field
          name="contactPhone"
          type="phone"
          label="Contact Phone"
        />

        <Form.Field
          name="publicPhone"
          label="Make Contact Number Public"
        />

        <Form.Field
          name="hostReceiveRsvpEmails"
          label="Send Host RSVPs via Email"
        />

        <InfoHeader content='Event Attendees' />

        <Form.Field
          name="rsvpUseReminderEmail"
          label="Send Guests RSVP Email Reminder"
        />

        <Form.Field
          name='rsvpEmailReminderHours'
          label="RSVP Reminder Hours"
          type='number'
          min="0"
        />

        <Form.Field
          name="attendeeVolunteerShow"
          type='select'
          label="Ask Attendees to Volunteer"
          choices={this.volunteerShowOptions()}
        />

        <Form.Field
          name='attendeeVolunteerMessage'
          label="Message for Event Volunteers"
          multiLine={true}
          fullWidth={true}
        />

        <InfoHeader content='Event Settings' />

        <Form.Field
          name='isSearchable'
          type='select'
          label='Make Event Public?'
          choices={this.eventSearchableOptions()}
        />

        <Form.Field
          name="isOfficial"
          label="Mark as an official campaign event"
        />

      <Form.Button  style={ { display: "none" } } ref="submit" type='submit' label='Submit Changes' fullWidth={true} />

    </GCForm>)

    return form
  }

  render() {
    return (

      <CardText>
        {this.renderForm()}
      </CardText>

    )
  }

  submit() {
    jQuery("button", ReactDOM.findDOMNode(this.refs.submit)).click()
  }
}

export default Relay.createContainer(EventEdit, {
  initialVariables: {
    personFilters: {},
  },
  fragments: {
    listContainer: () => Relay.QL`
      fragment on ListContainer {
        eventTypes {
          id
          name
        }
        people(
          first: 1
          personFilters: $personFilters
        ) {
          edges {
            cursor
            node {
              id
              firstName
              lastName
              email
            }
          }
        }
      }
    `
  }
})
<|endoftext|>


<commit_before>var _ = require('../index.js'),
    test = require('tape');

test('uuid: should validate a uuid', function (t) {
    t.plan(1);

    t.equal(_.isUuid(_.uuid()), true);
});

test('uuid: should reject an invalid uuid', function (t) {
    t.plan(3);

    t.equal(_.isUuid('12345678-1234-1234-1234-123412341234'), false);
    t.equal(_.isUuid('12345678-1234-4234-1234-123412341234'), false);
    t.equal(_.isUuid('12345678-1234-1234-9234-123412341234'), false);
});
<commit_msg>Correct test name<commit_after>var _ = require('../index.js'),
    test = require('tape');

test('isUuid: should validate a uuid', function (t) {
    t.plan(1);

    t.equal(_.isUuid(_.uuid()), true);
});

test('isUuid: should reject an invalid uuid', function (t) {
    t.plan(3);

    t.equal(_.isUuid('12345678-1234-1234-1234-123412341234'), false);
    t.equal(_.isUuid('12345678-1234-4234-1234-123412341234'), false);
    t.equal(_.isUuid('12345678-1234-1234-9234-123412341234'), false);
});
<|endoftext|>


<commit_before>/**
 * Created by alykoshin on 30.03.16.
 */

'use strict';

//var assert = require('assert');
var _ = require('lodash');
var util = require('util');
var deepAssign = require('mini-deep-assign');

var debug = function(/*arguments*/) {
  //console.log.apply(this, arguments);
};

/**
 * Determine projection mode basing on values in projection
 *
 * _id has special handling in mongodb projections
 * we have no _id, so we'll ignore settings for _id field name
 *
 * @param {Object} projection - object with set of field names with values:
 *                              0||false - to exclude the field specified (exclusion mode)
 *                              1||true  - to include the field specified (inclusion mode)
 * @returns {'exclusion'|'inclusion'}  - returns exclusion mode (return all the fields except listed) ,
 *                                     - or inclusive (return only listed fields)
 */
function projectionMode(projection) {
  for (var path in projection) { if (projection.hasOwnProperty(path)) {
    //
    // Handle _id similar to Mongo
    //
    if (path === '_id') { continue; } // ignore value _id when determining projectionMode
    //
    if (projection[path] === 1 || projection[path] === true) {
      return 'inclusion';
    } else if (projection[path] === 0 || projection[path] === false) {
      return 'exclusion';
    } else {
      throw new Error('values for the projection may be only: true, false, 1, 0');
    }
  }}
  return 'exclusion'; // if projection is empty, set mode to 'exclusion', i.e. include all doc fields
}

var project = function(doc, projection) {

  var validateProjectionValue = function(value) {
    if (allowedProjectionValues.indexOf(value) < 0) {
      throw new Error('Values for projection musty be in ' + JSON.stringify(allowedProjectionValues) + ', found: '+util.inspect(value));
    }
  };

  var res, path;
  var allowedProjectionValues = [ false, true, 0, 1 ];

  if (_.isEmpty(projection)) {
    res = deepAssign({}, doc); // return full deep copy
    return res;
  }

  var mode = projectionMode(projection);

  if (mode === 'exclusion') { // exclusion mode
    // make full deep copy
    res = deepAssign({}, doc);
  } else {
    res = {};
    //
    // Handle _id similar to Mongo
    //
    // include _id by default
    if (typeof doc._id !== 'undefined') {
      res._id = deepAssign({}, doc._id);
    }
    //
  }

  // iterate through projection properties
  for (path in projection) { if (projection.hasOwnProperty(path)) {

    validateProjectionValue(projection[ path ]);
    //
    // Handle _id similar to Mongo
    // before anything else
    //
    if (path === '_id') {
      if (!projection[ path ]) { // if
        _.unset(res, path);
      }
      continue;
    }

    // check projection mode
    if ( (mode === 'exclusion' && projection[path]) || (mode !== 'exclusion' &&  !projection[path]) ) {
      throw new Error('Unable to mix inclusion and exclusion modes');
    }

    // project property
    if (mode === 'exclusion') {      // remove property in exclusion mode
      _.unset(res, path);

    } else {                         // copy property in exclusion mode
      var value = _.get(doc, path);  // get value at path
      value = deepAssign({}, value); // make full copy
      _.set(res, path, value);
    }

  }}

  debug('project(): doc: '+JSON.stringify(doc)+', res: '+JSON.stringify(res));
  return res;
};

module.exports = project
module.exports.projectionMode = projectionMode;
<commit_msg>refactored crud project<commit_after>/**
 * Created by alykoshin on 30.03.16.
 */

'use strict';

//var assert = require('assert');
var _ = require('lodash');
var util = require('util');
var deepAssign = require('mini-deep-assign');

var debug = function(/*arguments*/) {
  //console.log.apply(this, arguments);
};

/**
 * Determine projection mode basing on values in projection
 *
 * _id has special handling in mongodb projections
 * we have no _id, so we'll ignore settings for _id field name
 *
 * @param {Object} projection - object with set of field names with values:
 *                              0||false - to exclude the field specified (exclusion mode)
 *                              1||true  - to include the field specified (inclusion mode)
 * @returns {'exclusion'|'inclusion'}  - returns exclusion mode (return all the fields except listed) ,
 *                                     - or inclusive (return only listed fields)
 */
var projectionMode = function(projection) {
  for (var path in projection) { if (projection.hasOwnProperty(path)) {
    //
    // Handle _id similar to Mongo
    //
    if (path === '_id') { continue; } // ignore value _id when determining projectionMode
    //
    if (projection[path] === 1 || projection[path] === true) {
      return 'inclusion';
    } else if (projection[path] === 0 || projection[path] === false) {
      return 'exclusion';
    } else {
      throw new Error('values for the projection may be only: true, false, 1, 0');
    }
  }}
  return 'exclusion'; // if projection is empty, set mode to 'exclusion', i.e. include all doc fields
};

var initDoc = function(doc, mode) {
  var res;
  if (mode === 'exclusion') { // exclusion mode
                              // make full deep copy
    res = deepAssign({}, doc);
  } else {
    res = {};
    //
    // Handle _id similar to Mongo
    // include _id by default
    //
    if (typeof doc._id !== 'undefined') {
      includeField(doc, res, '_id');
    }
    //
  }
  return res;
};

var validateProjection = function(mode, path, projectionValue) {
  var allowedProjectionValues = [ false, true, 0, 1 ];
  if (allowedProjectionValues.indexOf(projectionValue) < 0) {
    throw new Error('Values for projection musty be in ' + JSON.stringify(allowedProjectionValues) + ', found: '+util.inspect(projectionValue));
  }
  // check projection mode
  if ( path !== '_id' && ((mode === 'exclusion' && projectionValue) || (mode !== 'exclusion' &&  !projectionValue)) ) {
    throw new Error('Unable to mix inclusion and exclusion modes');
  }

};

var includeField = function(sourceDoc, targetDoc, path) {
  var value = _.get(sourceDoc, path);  // get value at path
  value = deepAssign({}, value);       // make full copy
  _.set(targetDoc, path, value);
};

var excludeField = function(sourceDoc, targetDoc, path) {
  _.unset(targetDoc, path);
};

var projectId = function(sourceDoc, targetDoc, path, projection) {
  //
  // Handle _id similar to Mongo
  // _id by default is always included
  //
  if (!projection) {
    excludeField(sourceDoc, targetDoc, path);
  }
};

/**
 * Inclusion or suppression of the field
 * Prerequisites:
 * - in exclusion mode targetDoc must be deep copy of sourceDoc
 * - in inclusion mode targetDoc must initially be empty
 *
 * @param {Object} sourceDoc
 * @param {Object} targetDoc
 * @param {string} path
 * @param {boolean||number} projection
 */
var projectBasic = function(sourceDoc, targetDoc, path, projection) {
  if (!projection) {                     // remove property
    excludeField(sourceDoc, targetDoc, path);
  } else {                               // copy property
    includeField(sourceDoc, targetDoc, path);
  }
};

var project = function(doc, projection) {

  var mode = projectionMode(projection);
  var res = initDoc(doc, mode);

  // iterate through projection properties
  for (var path in projection) { if (projection.hasOwnProperty(path)) {

    validateProjection(mode, path, projection[ path ]);

    if (path === '_id') { // _id has priority over other properties
      projectId(doc, res, path, projection[ path ]);
    } else {
      projectBasic(doc, res, path, projection[ path ]);
    }

  }}

  debug('project(): doc: '+JSON.stringify(doc)+', res: '+JSON.stringify(res));
  return res;
};

module.exports = project;

module.exports.projectionMode = projectionMode;
module.exports.initDoc = initDoc;

module.exports.validateProjection = validateProjection;

module.exports.includeField = includeField;
module.exports.excludeField = excludeField;

module.exports.projectId = projectId;
module.exports.projectBasic = projectBasic;
<|endoftext|>


<commit_before>'use strict';

import pkg from '../package.json';
import { expect } from 'chai';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
chai.should();

import Ava from '../src';
import { any, weather } from '../src/intents'
import actionMock from './actions/action.mock'

import colors from 'colors';

describe('.listen()', () => {

  let ava;
  beforeEach( () => ava = new Ava() );

  it('Up and running', () => {
    expect(ava.listen).to.be.ok;
  });

  it('Returns a Promise (then/catch)', () => {
    const listen = ava.listen();

    expect(listen instanceof Promise).to.equal(true);
    expect(typeof(listen.then)).to.equal('function');
    expect(typeof(listen.catch)).to.equal('function');
  });

  it('Is successful', () => {
    ava.intent(any, actionMock);
    const state = await ava.listen('Hello world!');

    expect(state.version).to.equal(pkg.version);
    expect(state.intents.length).to.equal(1);
    expect(state.action).to.exist;
  });

  it('Listen is unsuccesful because no intent is matched', () => {
    ava.intent(weather, actionMock);

    expect( ava.listen('Hello world!') ).to.be.rejected;
  });

  it('Listen is unsuccesful because timeout', () => {
    ava.intent(any, []);
    const timeout = 300;

    expect( ava.listen('Hello world!', timeout) ).to.be.rejected;
  });

});
<commit_msg>Fixed typo<commit_after>'use strict';

import pkg from '../package.json';
import { expect } from 'chai';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
chai.use(chaiAsPromised);
chai.should();

import Ava from '../src';
import { any, weather } from '../src/intents'
import actionMock from './actions/action.mock'

import colors from 'colors';

describe('.listen()', () => {

  let ava;
  beforeEach( () => ava = new Ava() );

  it('Up and running', () => {
    expect(ava.listen).to.be.ok;
  });

  it('Returns a Promise (then/catch)', () => {
    const listen = ava.listen();

    expect(listen instanceof Promise).to.equal(true);
    expect(typeof(listen.then)).to.equal('function');
    expect(typeof(listen.catch)).to.equal('function');
  });

  it('Is successful', async () => {
    ava.intent(any, actionMock);
    const state = await ava.listen('Hello world!');

    expect(state.version).to.equal(pkg.version);
    expect(state.intents.length).to.equal(1);
    expect(state.action).to.exist;
  });

  it('Listen is unsuccesful because no intent is matched', () => {
    ava.intent(weather, actionMock);

    expect( ava.listen('Hello world!') ).to.be.rejected;
  });

  it('Listen is unsuccesful because timeout', () => {
    ava.intent(any, []);
    const timeout = 300;

    expect( ava.listen('Hello world!', timeout) ).to.be.rejected;
  });

});
<|endoftext|>