<commit_before>/* eslint-env mocha */
/* globals $ */
/*
 To see how Nightmare does its server stuff see:

 - https://github.com/segmentio/nightmare/blob/2453f7f/test/server.js#L86
 - https://github.com/segmentio/nightmare/blob/2771166/test/index.js#L43-L46
 */

const should = require('should')

const Nightmare = require('nightmare')
const url = require('url')
const exec = require('child_process').execFileSync
const fs = require('fs')

describe('Frontend', function () {
  const n = Nightmare({
    show: !!process.env.SHOW_BROWSER,
    height: 900
  })

  after(() => { n.end() })

  it('Should start the backend server if necessary', function () {
    return require('../backend/index.js')
  })
  let randInt = (min, max) => Math.floor(Math.random() * (max - min)) + min
  let username = `testuser${randInt(0, 10000000)}${randInt(10000000, 20000000)}`

  describe.skip('New user page', function () {
    it('Should create user George', function () {
      this.timeout(5000)
      return n.goto(page('signup'))
        .should.finally.containEql({ code: 200, url: page('signup') })
        .then(() => {
          return n.wait('.signup')
            .insert('input[name="name"]', 'George')
            .insert('input[name="email"]', 'george@lasvegas.com')
            .insert('input[name="password"]', '$$111$$')
            .click('.signup button.submit')
            .wait(() => document.getElementById('serverMsg').innerText !== '')
            .evaluate(() => document.getElementById('serverMsg').className)
            .should.finally.containEql('success')
        })
    })

    it('Should fail to create George again', function () {
      return n.click('.signup button.submit')
        .wait(() => document.getElementById('serverMsg').className.indexOf('danger') !== -1)
    })
  })

  describe('Event Log Test', function () {
    it('Should Append to the log', async function () {
      this.timeout(5000) // this one takes a while for some reason
      await n.goto(page('event-log'))
        .should.finally.containEql({ code: 200, url: page('event-log') })
      const result = await n.wait('#random').evaluate(() => ({
        current: document.getElementById('LogPosition').innerText,
        eventCount: document.querySelectorAll('.event').length
      }))
      await n.click('#random')
        .wait(() => +document.getElementById('count').innerText > 0)
      const obj = await n.insert('textarea[name="payload"]', 'This is a test payment event')
        .select('select[name="type"]', 'Payment')
        .click('#submit')
        .wait(() => +document.getElementById('count').innerText > 1)
        .evaluate(() => ({
          current: document.getElementById('LogPosition').innerText,
          eventCount: document.querySelectorAll('.event').length
        }))
      should(obj.eventCount).equal(result.eventCount + 2)
      should(obj.current !== result.current).equal(true)
    })

    // TODO: restore this based on the new TimeTravel
    it.skip('Should Traverse Log', async function () {
      this.timeout(4000)
      await n.goto(page('event-log'))
      const prior = await n.evaluate(() => document.getElementById('LogPosition').innerText)
      const initial = await n.wait('textarea[name="payload"]')
        .insert('textarea[name="payload"]', 'This is a test Group Payment Event')
        .select('select[name="type"]', 'Payment')
        .click('#submit')
        .wait(prior => document.getElementById('LogPosition').innerText !== prior, prior)
        .evaluate(() => document.getElementById('LogPosition').innerText)
      const secondary = await n.click('a.backward')
        .wait(initial => document.getElementById('LogPosition').innerText !== initial, initial)
        .evaluate(() => document.getElementById('LogPosition').innerText)
      should(initial !== secondary).equal(true)
      const tertiary = await n.click('a.forward')
        .wait(secondary => document.getElementById('LogPosition').innerText !== secondary, secondary)
        .evaluate(() => document.getElementById('LogPosition').innerText)
      should(initial).equal(tertiary)
    })
  })

  describe('Sign up Test', function () {
    it('Should register User', async function () {
      this.timeout(4000)
      await n.goto(page('signup'))
        .wait('#name')
      const signedup = await n.insert('#name', username)
        .insert('#email', `test@testgroupincome.com`)
        .insert('#password', 'testtest')
        .click('button[type="submit"]')
        .wait('#HomeLogo')
        .evaluate(() => !!document.getElementById('HomeLogo'))
      should(signedup).equal(true)
      /*
      await n.insert('#name')
        .insert('#email')
        .insert('#password')
        .wait(() => !document.getElementById('name').innerText &&
        !document.getElementById('email').innerText &&
        !document.getElementById('password').innerText)
        */
    })

    it('Test Logout and Login', async function () {
      this.timeout(6000)
      const response = await n
        // Logout
        .click('#LogoutBtn')
        .wait(1000)

        // Open login modal
        .wait(() => document.querySelector('#LoginBtn'))
        .wait(1000)
        .click('#LoginBtn')

        // Login
        .wait(() => document.querySelector('#LoginModal.is-active'))
        .insert('#LoginName', username)
        .insert('#LoginPassword', 'testtest')
        .wait(1000)
        .click('#LoginButton')
        .wait(() => Boolean(document.querySelector('#LoginResponse')))
        .wait(1000)
        .evaluate(() => document.querySelector('#LoginResponse').innerText)
      should(response).not.equal('Invalid username or password')
    })

    /* There appears to be a bug in nightmare that causes the insert and type commands to enter old data into the field if the
    insert or type commands or used more than once on the same field. This concatenates the old values to the new.
     This occurs regardless of whether you clear the field or not. Until its fixed skip this validation test
     */
    it.skip('Test Validation', async function () {
      this.timeout(4000)
      await n.goto(page('signup'))
      const badUsername = 't e s t'
      const badEmail = `@fail`
      const badPassword = `789`// six is so afraid
      const denied = await n.insert('#name', badUsername)
        .insert('#email', badEmail)
        .insert('#password', badPassword)
        .evaluate(() => document.querySelector('button[type="submit"]').disabled)
      should(denied).equal(true)
      const usernameMsg = await n.evaluate(() => !!document.getElementById('badUsername'))
      should(usernameMsg).equal(true)
      const emailMsg = await n.evaluate(() => !!document.getElementById('badEmail'))
      should(emailMsg).equal(true)
      const passwordMsg = await n.evaluate(() => !!document.getElementById('badPassword'))
      should(passwordMsg).equal(true)
    })
  })

  describe('Group Creation Test', function () {
    it('Create Additional User', async function () {
      this.timeout(4000)
      await n.click('#LoginBtn')
        .wait(250)
      await n.click('#SignupBtn')
        .wait('#name')
      let signedup = await n.insert('#name', username + '2')
      .insert('#email', `test2@testgroupincome.com`)
      .insert('#password', 'testtest')
      .click('button[type="submit"]')
      .wait('#HomeLogo')
      .evaluate(() => !!document.getElementById('HomeLogo'))
      should(signedup).equal(true)
    })
    it('Create Additional User 2', async function () {
      this.timeout(4000)
      await n.click('#LoginBtn')
        .wait(250)
      await n.click('#SignupBtn')
        .wait('#name')
      let signedup = await n.insert('#name', username + '3')
        .insert('#email', `test2@testgroupincome.com`)
        .insert('#password', 'testtest')
        .click('button[type="submit"]')
        .wait('#HomeLogo')
        .evaluate(() => !!document.getElementById('HomeLogo'))
      should(signedup).equal(true)
    })

    it('Should create a group', async function () {
      this.timeout(10000)
      await n.click('#CreateGroup')
      const created = await n
        .insert('input[name="groupName"]', 'Test Group')
        .insert('textarea[name="sharedValues"]', 'Testing this software')
        .insert('input[name="groupName"]', 'Test Group')
        .insert('input[name="incomeProvided"]', 200)
        // .insert('input[name="proxyChangePercentage"]', 75)
        // .insert('input[name="proxyMemberApprovalPercentage"]', 75)
        // .insert('input[name="proxyMemberRemovalPercentage"]', 75)
        .select('select[name="contributionPrivacy"]', 'Very Private')
        .click('button[type="submit"]')
        // Should get to invite page:
        .wait(500)
        .wait(() => !!document.getElementById('addButton'))
        .evaluate(() => !!document.getElementById('addButton'))
      should(created).equal(true)
    })

    it('Should invite members to group', async function () {
      this.timeout(4000)

      const count = await n
        .wait(() => Boolean(document.querySelector('#addButton')))
        .insert('#searchUser', username)
        .click('#addButton')
        .wait(() => document.querySelectorAll('.member').length > 0)
        .click('.delete')
        .wait(() => document.querySelectorAll('.member').length < 1)
        .evaluate(() => +document.querySelectorAll('.member').length)
      should(count).equal(0)

      const created = await n
        .insert('#searchUser', username)
        .click('#addButton')
        .wait(() => document.querySelectorAll('.member').length > 0)
        .insert('#searchUser', username + '2')
        .click('#addButton')
        .wait(() => document.querySelectorAll('.member').length > 1)
        .click('button[type="submit"]')
        .wait(() => !!document.querySelector('.notification.is-success'))
        .evaluate(() => !!document.querySelector('.notification.is-success'))
      should(created).equal(true)
    })

    it('Should Receive Message and Invite', async function () {
      this.timeout(80000)
      await n.goto(page('mailbox'))
        .wait('#Inbox')
        .click('#ComposeLink')
        .wait('#AddRecipient')
        .insert('#AddRecipient', username)
        .insert('#ComposedMessage', 'Best test ever!!')
        .click('#SendButton')
        .wait('#Inbox')
        .click('#LoginBtn')
        .wait(500)
        .click('#LoginBtn')
        .wait(() => document.getElementById('LoginModal').classList.contains('is-active'))
        .insert('#LoginName', username)
        .insert('#LoginPassword', 'testtest')
        .click('#LoginButton')
        .wait(() => !!document.getElementById('LoginResponse'))
        .wait('#MailboxLink')
        .click('#MailboxLink')
        .wait(300)
      const alert = await n.evaluate(() => !!document.getElementById('AlertNotification'))
      should(alert).equal(true)
      const unread = await n.evaluate(() => +document.querySelector('.unread').innerText)
      should(unread).equal(2)
      const hasInvite = await n.evaluate(() => !!document.getElementsByClassName('invite-message'))
      should(hasInvite).equal(true)
      const hasMessage = await n.evaluate(() => !!document.getElementsByClassName('inbox-message'))
      should(hasMessage).equal(true)
      const newUnread = await n.click('.invite-message')
        .wait(300)
        .evaluate(() => +document.querySelector('.unread').innerText)
      should(newUnread).equal(1)
    })
  })

  describe('Test Localization Gathering Function', function () {
    it('Verify output of transform functions', function () {
      const script = `
        <template>
            <i18n comment = "Amazing Test">A test of sorts</i18n>
            <i18n comment="Amazing Test2">A test of wit</i18n>
            <i18n>A test of strength</i18n>
        </template>
        <script>
            L('this is some translatable Text','this is relevant commentary')
            L('this text lacks a comment')
        </script>

         `
      const path = 'script.vue'
      fs.writeFileSync(path, script)
      const output = 'translation.json'
      const args = ['scripts/i18n.js', path, output]
      exec('node', args)
      const json = fs.readFileSync(output, 'utf8')
      const localeObject = JSON.parse(json)
      should(localeObject).have.property('A test of sorts')
      should(localeObject).have.property('A test of wit')
      should(localeObject).have.property('A test of strength')
      should(localeObject).have.property('this is some translatable Text')
      should(localeObject).have.property('this text lacks a comment')
      should(localeObject['A test of sorts']).have.property('comment', 'Amazing Test')
      should(localeObject['A test of wit']).have.property('comment', 'Amazing Test2')
      should(localeObject['this is some translatable Text']).have.property('comment', 'this is relevant commentary')
      should(localeObject['this is some translatable Text']).have.property('text', 'this is some translatable Text')
      should(localeObject['this text lacks a comment']).have.property('text', 'this text lacks a comment')
      should(localeObject['A test of sorts']).have.property('text', 'A test of sorts')
      should(localeObject['A test of wit']).have.property('text', 'A test of wit')
      should(localeObject['A test of strength']).have.property('text', 'A test of strength')
      fs.unlinkSync(path)
      fs.unlinkSync(output)
    })
  })

  describe('EJS test page', function () {
    it('List should have at least two items', function () {
      return n.goto(page('ejs-page'))
        .wait(() => typeof $ === 'function' && !!$().prevUntil)
        .evaluate(() => $('#todo').children().length)
        .should.finally.greaterThan(1)
    })
  })
})

function page (page) {
  return url.resolve(process.env.FRONTEND_URL, `simple/${page}`)
}

// returns the size of the document
Nightmare.action('size', function (done) {
  this.evaluate_now(() => {
    const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
    const height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
    return { height, width }
  }, done)
})
<commit_msg>Fix damn tests.<commit_after>/* eslint-env mocha */
/* globals $ */
/*
 To see how Nightmare does its server stuff see:

 - https://github.com/segmentio/nightmare/blob/2453f7f/test/server.js#L86
 - https://github.com/segmentio/nightmare/blob/2771166/test/index.js#L43-L46
 */

const should = require('should')

const Nightmare = require('nightmare')
const url = require('url')
const exec = require('child_process').execFileSync
const fs = require('fs')

describe('Frontend', function () {
  const n = Nightmare({
    show: !!process.env.SHOW_BROWSER,
    height: 900
  })

  after(() => { n.end() })

  it('Should start the backend server if necessary', function () {
    return require('../backend/index.js')
  })
  let randInt = (min, max) => Math.floor(Math.random() * (max - min)) + min
  let username = `testuser${randInt(0, 10000000)}${randInt(10000000, 20000000)}`

  describe.skip('New user page', function () {
    it('Should create user George', function () {
      this.timeout(5000)
      return n.goto(page('signup'))
        .should.finally.containEql({ code: 200, url: page('signup') })
        .then(() => {
          return n.wait('.signup')
            .insert('input[name="name"]', 'George')
            .insert('input[name="email"]', 'george@lasvegas.com')
            .insert('input[name="password"]', '$$111$$')
            .click('.signup button.submit')
            .wait(() => document.getElementById('serverMsg').innerText !== '')
            .evaluate(() => document.getElementById('serverMsg').className)
            .should.finally.containEql('success')
        })
    })

    it('Should fail to create George again', function () {
      return n.click('.signup button.submit')
        .wait(() => document.getElementById('serverMsg').className.indexOf('danger') !== -1)
    })
  })

  describe('Event Log Test', function () {
    it('Should Append to the log', async function () {
      this.timeout(5000) // this one takes a while for some reason
      await n.goto(page('event-log'))
        .should.finally.containEql({ code: 200, url: page('event-log') })
      const result = await n.wait('#random').evaluate(() => ({
        current: document.getElementById('LogPosition').innerText,
        eventCount: document.querySelectorAll('.event').length
      }))
      await n.click('#random')
        .wait(() => +document.getElementById('count').innerText > 0)
      const obj = await n.insert('textarea[name="payload"]', 'This is a test payment event')
        .select('select[name="type"]', 'Payment')
        .click('#submit')
        .wait(() => +document.getElementById('count').innerText > 1)
        .evaluate(() => ({
          current: document.getElementById('LogPosition').innerText,
          eventCount: document.querySelectorAll('.event').length
        }))
      should(obj.eventCount).equal(result.eventCount + 2)
      should(obj.current !== result.current).equal(true)
    })

    // TODO: restore this based on the new TimeTravel
    it.skip('Should Traverse Log', async function () {
      this.timeout(4000)
      await n.goto(page('event-log'))
      const prior = await n.evaluate(() => document.getElementById('LogPosition').innerText)
      const initial = await n.wait('textarea[name="payload"]')
        .insert('textarea[name="payload"]', 'This is a test Group Payment Event')
        .select('select[name="type"]', 'Payment')
        .click('#submit')
        .wait(prior => document.getElementById('LogPosition').innerText !== prior, prior)
        .evaluate(() => document.getElementById('LogPosition').innerText)
      const secondary = await n.click('a.backward')
        .wait(initial => document.getElementById('LogPosition').innerText !== initial, initial)
        .evaluate(() => document.getElementById('LogPosition').innerText)
      should(initial !== secondary).equal(true)
      const tertiary = await n.click('a.forward')
        .wait(secondary => document.getElementById('LogPosition').innerText !== secondary, secondary)
        .evaluate(() => document.getElementById('LogPosition').innerText)
      should(initial).equal(tertiary)
    })
  })

  describe('Sign up Test', function () {
    it('Should register User', async function () {
      this.timeout(4000)
      await n.goto(page('signup'))
        .wait('#name')
      const signedup = await n.insert('#name', username)
        .insert('#email', `test@testgroupincome.com`)
        .insert('#password', 'testtest')
        .click('button[type="submit"]')
        .wait('#HomeLogo')
        .evaluate(() => !!document.getElementById('HomeLogo'))
      should(signedup).equal(true)
      /*
      await n.insert('#name')
        .insert('#email')
        .insert('#password')
        .wait(() => !document.getElementById('name').innerText &&
        !document.getElementById('email').innerText &&
        !document.getElementById('password').innerText)
        */
    })

    it('Test Logout and Login', async function () {
      this.timeout(6000)
      const loggedin = await n
        // Logout
        .click('#LogoutBtn')

        // Open login modal
        .wait(() => Boolean(document.querySelector('#LoginBtn')))
        .click('#LoginBtn')

        // Login
        .wait(() => Boolean(document.querySelector('#LoginModal.is-active')))
        .wait(1000)
        .insert('#LoginName', username)
        .insert('#LoginPassword', 'testtest')
        .wait(1000)
        .click('#LoginButton')
        .wait(() => Boolean(document.querySelector('#LoginResponse')))
        .wait(1000)
        .evaluate(() => Boolean(document.querySelector('#LogoutBtn')))
      should(loggedin).equal(true)
    })

    /* There appears to be a bug in nightmare that causes the insert and type commands to enter old data into the field if the
    insert or type commands or used more than once on the same field. This concatenates the old values to the new.
     This occurs regardless of whether you clear the field or not. Until its fixed skip this validation test
     */
    it.skip('Test Validation', async function () {
      this.timeout(4000)
      await n.goto(page('signup'))
      const badUsername = 't e s t'
      const badEmail = `@fail`
      const badPassword = `789`// six is so afraid
      const denied = await n.insert('#name', badUsername)
        .insert('#email', badEmail)
        .insert('#password', badPassword)
        .evaluate(() => document.querySelector('button[type="submit"]').disabled)
      should(denied).equal(true)
      const usernameMsg = await n.evaluate(() => !!document.getElementById('badUsername'))
      should(usernameMsg).equal(true)
      const emailMsg = await n.evaluate(() => !!document.getElementById('badEmail'))
      should(emailMsg).equal(true)
      const passwordMsg = await n.evaluate(() => !!document.getElementById('badPassword'))
      should(passwordMsg).equal(true)
    })
  })

  describe('Group Creation Test', function () {
    it('Create Additional User', async function () {
      this.timeout(4000)
      const signedup = await n
        .click('#LogoutBtn')
        .wait('#SignupBtn')
        .click('#SignupBtn')
        .wait('#name')
        .insert('#name', username + '2')
        .insert('#email', 'test2@testgroupincome.com')
        .insert('#password', 'testtest')
        .click('button[type="submit"]')
        .wait('#HomeLogo')
        .evaluate(() => Boolean(document.querySelector('#HomeLogo')))
      should(signedup).equal(true)
    })

    it('Create Additional User 2', async function () {
      this.timeout(4000)
      const signedup = await n
        .click('#LogoutBtn')
        .wait('#SignupBtn')
        .click('#SignupBtn')
        .wait('#name')
        .insert('#name', username + '3')
        .insert('#email', `test2@testgroupincome.com`)
        .insert('#password', 'testtest')
        .click('button[type="submit"]')
        .wait('#HomeLogo')
        .evaluate(() => Boolean(document.querySelector('#HomeLogo')))
      should(signedup).equal(true)
    })

    it('Should create a group', async function () {
      this.timeout(10000)
      await n.click('#CreateGroup')
      const created = await n
        .insert('input[name="groupName"]', 'Test Group')
        .insert('textarea[name="sharedValues"]', 'Testing this software')
        .insert('input[name="groupName"]', 'Test Group')
        .insert('input[name="incomeProvided"]', 200)
        // .insert('input[name="proxyChangePercentage"]', 75)
        // .insert('input[name="proxyMemberApprovalPercentage"]', 75)
        // .insert('input[name="proxyMemberRemovalPercentage"]', 75)
        .select('select[name="contributionPrivacy"]', 'Very Private')
        .click('button[type="submit"]')
        // Should get to invite page:
        .wait(500)
        .wait(() => !!document.getElementById('addButton'))
        .evaluate(() => !!document.getElementById('addButton'))
      should(created).equal(true)
    })

    it('Should invite members to group', async function () {
      this.timeout(4000)

      const count = await n
        .wait(() => Boolean(document.querySelector('#addButton')))
        .insert('#searchUser', username)
        .click('#addButton')
        .wait(() => document.querySelectorAll('.member').length > 0)
        .click('.delete')
        .wait(() => document.querySelectorAll('.member').length < 1)
        .evaluate(() => +document.querySelectorAll('.member').length)
      should(count).equal(0)

      const created = await n
        .insert('#searchUser', username)
        .click('#addButton')
        .wait(() => document.querySelectorAll('.member').length > 0)
        .insert('#searchUser', username + '2')
        .click('#addButton')
        .wait(() => document.querySelectorAll('.member').length > 1)
        .click('button[type="submit"]')
        .wait(() => !!document.querySelector('.notification.is-success'))
        .evaluate(() => !!document.querySelector('.notification.is-success'))
      should(created).equal(true)
    })

    it('Should Receive Message and Invite', async function () {
      this.timeout(80000)
      await n
        .goto(page('mailbox'))
        .wait('#Inbox')
        .click('#ComposeLink')
        .wait('#AddRecipient')
        .insert('#AddRecipient', username)
        .insert('#ComposedMessage', 'Best test ever!!')
        .click('#SendButton')
        .wait('#Inbox')

        // Logout
        .click('#LogoutBtn')

        // Login
        .wait('#LoginBtn')
        .click('#LoginBtn')
        .wait(() => Boolean(document.querySelector('#LoginModal')))
        .insert('#LoginName', username)
        .insert('#LoginPassword', 'testtest')
        .click('#LoginButton')
        .wait(() => Boolean(document.querySelector('#LogoutBtn')))
        .wait('#MailboxLink')
        .click('#MailboxLink')
        .wait(300)
      const alert = await n.evaluate(() => !!document.getElementById('AlertNotification'))
      should(alert).equal(true)
      const unread = await n.evaluate(() => +document.querySelector('.unread').innerText)
      should(unread).equal(2)
      const hasInvite = await n.evaluate(() => !!document.getElementsByClassName('invite-message'))
      should(hasInvite).equal(true)
      const hasMessage = await n.evaluate(() => !!document.getElementsByClassName('inbox-message'))
      should(hasMessage).equal(true)
      const newUnread = await n.click('.invite-message')
        .wait(300)
        .evaluate(() => +document.querySelector('.unread').innerText)
      should(newUnread).equal(1)
    })
  })

  describe('Test Localization Gathering Function', function () {
    it('Verify output of transform functions', function () {
      const script = `
        <template>
            <i18n comment = "Amazing Test">A test of sorts</i18n>
            <i18n comment="Amazing Test2">A test of wit</i18n>
            <i18n>A test of strength</i18n>
        </template>
        <script>
            L('this is some translatable Text','this is relevant commentary')
            L('this text lacks a comment')
        </script>

         `
      const path = 'script.vue'
      fs.writeFileSync(path, script)
      const output = 'translation.json'
      const args = ['scripts/i18n.js', path, output]
      exec('node', args)
      const json = fs.readFileSync(output, 'utf8')
      const localeObject = JSON.parse(json)
      should(localeObject).have.property('A test of sorts')
      should(localeObject).have.property('A test of wit')
      should(localeObject).have.property('A test of strength')
      should(localeObject).have.property('this is some translatable Text')
      should(localeObject).have.property('this text lacks a comment')
      should(localeObject['A test of sorts']).have.property('comment', 'Amazing Test')
      should(localeObject['A test of wit']).have.property('comment', 'Amazing Test2')
      should(localeObject['this is some translatable Text']).have.property('comment', 'this is relevant commentary')
      should(localeObject['this is some translatable Text']).have.property('text', 'this is some translatable Text')
      should(localeObject['this text lacks a comment']).have.property('text', 'this text lacks a comment')
      should(localeObject['A test of sorts']).have.property('text', 'A test of sorts')
      should(localeObject['A test of wit']).have.property('text', 'A test of wit')
      should(localeObject['A test of strength']).have.property('text', 'A test of strength')
      fs.unlinkSync(path)
      fs.unlinkSync(output)
    })
  })

  describe('EJS test page', function () {
    it('List should have at least two items', function () {
      return n.goto(page('ejs-page'))
        .wait(() => typeof $ === 'function' && !!$().prevUntil)
        .evaluate(() => $('#todo').children().length)
        .should.finally.greaterThan(1)
    })
  })
})

function page (page) {
  return url.resolve(process.env.FRONTEND_URL, `simple/${page}`)
}

// returns the size of the document
Nightmare.action('size', function (done) {
  this.evaluate_now(() => {
    const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
    const height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
    return { height, width }
  }, done)
})
<|endoftext|>


<commit_before>'use strict';

var tap = require('tap');
var pz = require('promzard');
var spawn = require('child_process').spawn;
var utils = require('./util/utils');
var path = require('path');
var helpers = require('./util/helpers');

var node = process.execPath;
var output = '';
function respond(c, lastOutput, custom) {
    custom = custom || function () {
        c.stdin.write('\n');
    };
    var regex = new RegExp(lastOutput + ' $');
    if (output.match(regex)) {
        c.stdin.write('\n');
        c.stdin.end();
        return;
    }

    custom(c, output);
    return;
}

function spawnProcess(t, process, expect, lastAttributeOutput, custom) {
    var c = spawn(node, [process], { customFds: [-1, -1, -1] });
    output = '';
    c.stdout.on('data', function (d) {
        output += d;
        respond(c, lastAttributeOutput, custom);
    });

    var actual = '';
    c.stderr.on('data', function (d) {
        actual += d;
    });

    c.on('close', function () {
//        console.error('actual', actual);
//        console.error('expect', expect);
        actual = JSON.parse(actual);
        t.deepEqual(actual, expect);
        t.end();
    });
}

tap.test('bikini', function (t) {

    var process = require.resolve('./process/bikini.js');

    var expect = {
        'name': '',
        'description': '',
        'endpoint': '',
        'model': '',
        'idAttribute': ''
    };

    var lastAttributeOutput = 'Models ID Attribute:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('connectionconfiguration', function (t) {

    var process = require.resolve('./process/connectionconfiguration.js');

    var expect = {
        'name': '',
        'description': '',
        'configurationId': '',
        'configurationDescription': '',
        'authentication': '',
        'client': '',
        'user': '',
        'alias': '',
        'password': '',
        'language': '',
        'sapCookie': '',
        'x.509Certificate': '',
        'externalIdData': '',
        'externalIdType': '',
        'applicationServer': '',
        'systemNumber': '',
        'messageServer': '',
        'messagePort': '',
        'systemId': '',
        'logonGroup': '',
        'gatewayServer': '',
        'gatewayPort': '',
        'sapRouterString': '',
        'logonCheck': '',
        'logonCodepage': '',
        'logonCodepageType': '',
        'tableParameterDeltaMngmt': '',
        'getSsoTicket': '',
        'denyInitialPwds': '',
        'activeConnections': '',
        'idleConnections': '',
        'expirationTimeout': '',
        'expirationPeriod': '',
        'queuedTimeout': '',
        'sncMode': '',
        'sncPartner': '',
        'sncLevel': '',
        'sncName': '',
        'sncLibrary': '',
        'sncSso': '',
        'repositoryDestination': '',
        'repositoryUser': '',
        'repositoryPassword': '',
        'repositorySncMode': '',
        'repositoryRoundtripOptimization': '',
        'properties': {
            'key': {
                'type': 'type',
                'value': 'value'
            },
            'key1': {
                'type': 'type1',
                'value': 'value1'
            }
        },
        'factoryPid': '',
        'bundleLocation': ''
    };

    var lastAttributeOutput = 'Bundle Location:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput, function (c, output) {
        if (output.match(/Properties \(key:type:value key:type:value\): $/)) {
            c.stdin.write('key:type:value key1:type1:value1\n');
            return;
        }
        c.stdin.write('\n');
    });
});

tap.test('model', function (t) {

    var process = require.resolve('./process/model.js');

    var expect = {
        'name': 'book',
        'label': 'book',
        'attributes': {
            'author': {
                'type': 'string',
                'mandatory': false,
                'key': false
            },
            'isbn': {
                'type': 'integer',
                'mandatory': 'mandatory',
                'key': 'key'
            }
        }
    };

    var lastAttributeOutput = 'Models ID Attribute:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput, function (c, output) {
        if (output.match(/Name: $/)) {
            c.stdin.write('book\n');
            return;
        }
        if (output.match(/Attributes \(name:type:mandatory:key name:type:mandatory:key\): $/)) {
            c.stdin.write('author:string isbn:integer:mandatory:key\n');
            c.stdin.end();
            return;
        }
        c.stdin.write('\n');
    });
});

tap.test('rest', function (t) {

    var process = require.resolve('./process/rest.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'descriptorUrl': '',
            'userName': '',
            'password': '',
            'jsonMediaType': '',
            'xmlMediaType': '',
            'oauthProviderType': ''
        }
    };

    var lastAttributeOutput = 'OAuth Provider Type:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('saprfc', function (t) {

    var process = require.resolve('./process/saprfc.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'authentication': '',
            'client': '',
            'user': '',
            'alias': '',
            'password': '',
            'language': '',
            'sapCookie': '',
            'x.509Certificate': '',
            'externalIdDate': '',
            'externalIdType': '',
            'programServer': '',
            'programId': '',
            'rfcTrace': '',
            'cpicTrace': '',
            'repositoryDestination': '',
            'repositoryUser': '',
            'repositoryPassword': '',
            'repositorySncMode': '',
            'repositoryRoundtripOptimization': ''
        }
    };

    var lastAttributeOutput = 'Repository Roundtrip Optimization:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('soap', function (t) {

    var process = require.resolve('./process/soap.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'descriptorUrl': '',
            'userName': '',
            'password': '',
            'messageSuffix': '',
            'soapVersion': '',
            'adressingFeautre': '',
            'mtomFeature': '',
            'oauthProviderType': ''
        }
    };

    var lastAttributeOutput = 'OAuth Provider Type:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('sql', function (t) {

    var process = require.resolve('./process/sql.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'schema': ''
        }
    };

    var lastAttributeOutput = 'Schema:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('calling mcap generate model should throw an error if you\'re not in a mcap-project',function(t) {
    utils.executeCommand('../../cli.js', ['generate', 'model'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
        t.end();
    });
});
<commit_msg>add more tests (generate)<commit_after>'use strict';

var tap = require('tap');
var pz = require('promzard');
var spawn = require('child_process').spawn;
var utils = require('./util/utils');
var path = require('path');
var helpers = require('./util/helpers');

var node = process.execPath;
var output = '';
function respond(c, lastOutput, custom) {
    custom = custom || function () {
        c.stdin.write('\n');
    };
    var regex = new RegExp(lastOutput + ' $');
    if (output.match(regex)) {
        c.stdin.write('\n');
        c.stdin.end();
        return;
    }

    custom(c, output);
    return;
}

function spawnProcess(t, process, expect, lastAttributeOutput, custom) {
    var c = spawn(node, [process], { customFds: [-1, -1, -1] });
    output = '';
    c.stdout.on('data', function (d) {
        output += d;
        respond(c, lastAttributeOutput, custom);
    });

    var actual = '';
    c.stderr.on('data', function (d) {
        actual += d;
    });

    c.on('close', function () {
//        console.error('actual', actual);
//        console.error('expect', expect);
        actual = JSON.parse(actual);
        t.deepEqual(actual, expect);
        t.end();
    });
}

tap.test('bikini', function (t) {

    var process = require.resolve('./process/bikini.js');

    var expect = {
        'name': '',
        'description': '',
        'endpoint': '',
        'model': '',
        'idAttribute': ''
    };

    var lastAttributeOutput = 'Models ID Attribute:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('connectionconfiguration', function (t) {

    var process = require.resolve('./process/connectionconfiguration.js');

    var expect = {
        'name': '',
        'description': '',
        'configurationId': '',
        'configurationDescription': '',
        'authentication': '',
        'client': '',
        'user': '',
        'alias': '',
        'password': '',
        'language': '',
        'sapCookie': '',
        'x.509Certificate': '',
        'externalIdData': '',
        'externalIdType': '',
        'applicationServer': '',
        'systemNumber': '',
        'messageServer': '',
        'messagePort': '',
        'systemId': '',
        'logonGroup': '',
        'gatewayServer': '',
        'gatewayPort': '',
        'sapRouterString': '',
        'logonCheck': '',
        'logonCodepage': '',
        'logonCodepageType': '',
        'tableParameterDeltaMngmt': '',
        'getSsoTicket': '',
        'denyInitialPwds': '',
        'activeConnections': '',
        'idleConnections': '',
        'expirationTimeout': '',
        'expirationPeriod': '',
        'queuedTimeout': '',
        'sncMode': '',
        'sncPartner': '',
        'sncLevel': '',
        'sncName': '',
        'sncLibrary': '',
        'sncSso': '',
        'repositoryDestination': '',
        'repositoryUser': '',
        'repositoryPassword': '',
        'repositorySncMode': '',
        'repositoryRoundtripOptimization': '',
        'properties': {
            'key': {
                'type': 'type',
                'value': 'value'
            },
            'key1': {
                'type': 'type1',
                'value': 'value1'
            }
        },
        'factoryPid': '',
        'bundleLocation': ''
    };

    var lastAttributeOutput = 'Bundle Location:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput, function (c, output) {
        if (output.match(/Properties \(key:type:value key:type:value\): $/)) {
            c.stdin.write('key:type:value key1:type1:value1\n');
            return;
        }
        c.stdin.write('\n');
    });
});

tap.test('model', function (t) {

    var process = require.resolve('./process/model.js');

    var expect = {
        'name': 'book',
        'label': 'book',
        'attributes': {
            'author': {
                'type': 'string',
                'mandatory': false,
                'key': false
            },
            'isbn': {
                'type': 'integer',
                'mandatory': 'mandatory',
                'key': 'key'
            }
        }
    };

    var lastAttributeOutput = 'Models ID Attribute:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput, function (c, output) {
        if (output.match(/Name: $/)) {
            c.stdin.write('book\n');
            return;
        }
        if (output.match(/Attributes \(name:type:mandatory:key name:type:mandatory:key\): $/)) {
            c.stdin.write('author:string isbn:integer:mandatory:key\n');
            c.stdin.end();
            return;
        }
        c.stdin.write('\n');
    });
});

tap.test('rest', function (t) {

    var process = require.resolve('./process/rest.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'descriptorUrl': '',
            'userName': '',
            'password': '',
            'jsonMediaType': '',
            'xmlMediaType': '',
            'oauthProviderType': ''
        }
    };

    var lastAttributeOutput = 'OAuth Provider Type:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('saprfc', function (t) {

    var process = require.resolve('./process/saprfc.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'authentication': '',
            'client': '',
            'user': '',
            'alias': '',
            'password': '',
            'language': '',
            'sapCookie': '',
            'x.509Certificate': '',
            'externalIdDate': '',
            'externalIdType': '',
            'programServer': '',
            'programId': '',
            'rfcTrace': '',
            'cpicTrace': '',
            'repositoryDestination': '',
            'repositoryUser': '',
            'repositoryPassword': '',
            'repositorySncMode': '',
            'repositoryRoundtripOptimization': ''
        }
    };

    var lastAttributeOutput = 'Repository Roundtrip Optimization:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('soap', function (t) {

    var process = require.resolve('./process/soap.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'descriptorUrl': '',
            'userName': '',
            'password': '',
            'messageSuffix': '',
            'soapVersion': '',
            'adressingFeautre': '',
            'mtomFeature': '',
            'oauthProviderType': ''
        }
    };

    var lastAttributeOutput = 'OAuth Provider Type:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('sql', function (t) {

    var process = require.resolve('./process/sql.js');

    var expect = {
        'name': '',
        'description': '',
        'connector': '',
        'protocol': '',
        'properties': {
            'schema': ''
        }
    };

    var lastAttributeOutput = 'Schema:';

    //console.error('%s %s', node, process);
    spawnProcess(t, process, expect, lastAttributeOutput);
});

tap.test('calling mcap generate should throw an error if you\'re not in a mcap-project',function(t) {
    utils.executeCommand('../../cli.js', ['generate', 'model'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
    });

    utils.executeCommand('../../cli.js', ['generate', 'bikini'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
    });

    utils.executeCommand('../../cli.js', ['generate', 'connectionconfiguration'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
    });

    utils.executeCommand('../../cli.js', ['generate', 'rest'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
    });

    utils.executeCommand('../../cli.js', ['generate', 'saprfc'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
    });

    utils.executeCommand('../../cli.js', ['generate', 'soap'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
    });

    utils.executeCommand('../../cli.js', ['generate', 'sql'], false, function(output, tmpPath) {
        t.equal(output.toString(), 'Not inside a mCAP Project\n');
        t.end();
    });
});
<|endoftext|>


<commit_before>(function(env) {
    "use strict";
    env.ddg_spice_recipes = function (res) {

    var query = DDG.get_query(),
        query_encoded = DDG.get_query_encoded();

    // delete for spice
    // if(!res.matches || !res.matches.length){ return; }

    var normalize = function(item) {

            // skip any results that don't have images:
            if (!item.imageUrlsBySize) {
                return null;
            }

            var m = { };

            /* item */

            m.title = item.recipeName.replace(/ recipe/i,"");
            m.url = "http://www.yummly.com/recipe/" + item.id + '?prm-v1';

            m.image = item.imageUrlsBySize['250'];
            m.ratingText = item.sourceDisplayName;

            /* detail */

            m.cuisine = item.attributes && item.attributes.cuisine && item.attributes.cuisine[0];

            if(item.totalTimeInSeconds){
                m.cookingTime = item.totalTimeInSeconds / 60;
            }

            if(item.ingredients && item.ingredients.length){
                var searchTermContainsRecipe = rq.match(/recipe/i),
                    len = item.ingredients.length;

                m.ingredientString = item.ingredients.join(", ");
                m.ingredientDetails = item.ingredients.map(function(ingredient,i) {
                    var refinedTerm = (searchTermContainsRecipe) ? query.replace(/ recipe/i,' ' + ingredient + ' recipe') : query + ' ' + ingredient,
                        displayName = ingredient;

                    // append comman to all but the last:
                    if(i!==len-1){
                        displayName += ", ";
                    }

                    return {
                        name: ingredient,
                        displayName: displayName,
                        url: '?q=' + encodeURIComponent(refinedTerm)
                    }
                });

                // normalization: description for item, ingredientString for custom detail template
                m.description = m.ingredientString;
            }

            m.flavors = sortAndFormatFlavors(item.flavors);

            return m;

    }; // normalize()

    /**
     * takes a hash of flavor names + strength [0,1]
     * and returns a sparse array used to render bar
     * chart
     *
     * @param {Object} flavors
     * @return {Array}
     */
    var sortAndFormatFlavors = function(flavors){
        if(!flavors){ return null; }

        var sparse = [];

        for(var key in flavors){
            var strength = flavors[key];

            if(strength){

                // replace some of yummly flavor words,
                // for some reason the api doesn't match
                // their UI and we like the words on their UI better:
                if(key==="piquant"){
                    key = "spicy";
                }
                if(key==="meaty"){
                    key = "savory";
                }

                sparse.push({
                    name: key,
                    url: '/?q=' + key + '+' + query_encoded.replace(/ /g,'+'),
                    strength: Math.round(strength * 100)
                });
            }
        }

        if(!sparse.length){ return null; }

        return sparse.sort(function(a,b){
            return (a.strength > b.strength) ? -1 : 1;
        });
    }

    var //normalizedData = normalizeData(res.matches),

        searchContainedRecipe = !!(query_encoded.match(/recipe/i)),
        searchTerm = query.replace(/recipes|recipe/i,'').trim(),
        moreUrl = res.attribution.url + '?q=' + searchTerm + '&prm-v1'; // should replace trigger word or use the same logic that is used for the api call

    Spice.add({
        id: 'recipes',
        name: 'Recipes',

        data: res.matches,  //normalizedData,

        trump: searchContainedRecipe,   // XXX: signal?

        meta: {
            // count: normalizedData.length,
            total: res.totalMatchCount,
            searchTerm: searchTerm,
            itemType: 'Recipes',
            detailBg: 'image',
            detailClass: 'detail--i',

            // TODO: the following metadata will be injected by spice
            sourceIconUrl: DDG.get_asset_path('recipes','yummly.com.ico'), // temp fix for pb
            sourceUrl: moreUrl,
            sourceName: 'Yummly'
        },

        normalize: normalize,

        sort_fields: {
            rating: function(a,b){
                return (a.rating > b.rating) ? -1 : 1;
            }
        },
        sort_default: 'rating',

        /*
        relevancy: {

            skip_words: [ "recipes", "recipe", "ingredient", "ingredients" ],

            primary: [
                { key: 'recipeName' },
                { key: 'attributes.cuisine.0' },
                { key: 'ingredients' }
            ]

        },
        */

        templates: {
            group: 'products_simple',
            detail: Spice.recipes.recipes_detail,
            item_detail: Spice.recipes.recipes_detail,
            options: {
                brand: true,
                rating: true
            }
        }
    });
};
}(this));
<commit_msg>handle empty response<commit_after>(function(env) {
    "use strict";
    env.ddg_spice_recipes = function (res) {

    if (!res) {
        return Spice.failed('recipes');
    }

    var query = DDG.get_query(),
        query_encoded = DDG.get_query_encoded();

    // delete for spice
    // if(!res.matches || !res.matches.length){ return; }

    var normalize = function(item) {

            // skip any results that don't have images:
            if (!item.imageUrlsBySize) {
                return null;
            }

            var m = { };

            /* item */

            m.title = item.recipeName.replace(/ recipe/i,"");
            m.url = "http://www.yummly.com/recipe/" + item.id + '?prm-v1';

            m.image = item.imageUrlsBySize['250'];
            m.ratingText = item.sourceDisplayName;

            /* detail */

            m.cuisine = item.attributes && item.attributes.cuisine && item.attributes.cuisine[0];

            if(item.totalTimeInSeconds){
                m.cookingTime = item.totalTimeInSeconds / 60;
            }

            if(item.ingredients && item.ingredients.length){
                var searchTermContainsRecipe = rq.match(/recipe/i),
                    len = item.ingredients.length;

                m.ingredientString = item.ingredients.join(", ");
                m.ingredientDetails = item.ingredients.map(function(ingredient,i) {
                    var refinedTerm = (searchTermContainsRecipe) ? query.replace(/ recipe/i,' ' + ingredient + ' recipe') : query + ' ' + ingredient,
                        displayName = ingredient;

                    // append comman to all but the last:
                    if(i!==len-1){
                        displayName += ", ";
                    }

                    return {
                        name: ingredient,
                        displayName: displayName,
                        url: '?q=' + encodeURIComponent(refinedTerm)
                    }
                });

                // normalization: description for item, ingredientString for custom detail template
                m.description = m.ingredientString;
            }

            m.flavors = sortAndFormatFlavors(item.flavors);

            return m;

    }; // normalize()

    /**
     * takes a hash of flavor names + strength [0,1]
     * and returns a sparse array used to render bar
     * chart
     *
     * @param {Object} flavors
     * @return {Array}
     */
    var sortAndFormatFlavors = function(flavors){
        if(!flavors){ return null; }

        var sparse = [];

        for(var key in flavors){
            var strength = flavors[key];

            if(strength){

                // replace some of yummly flavor words,
                // for some reason the api doesn't match
                // their UI and we like the words on their UI better:
                if(key==="piquant"){
                    key = "spicy";
                }
                if(key==="meaty"){
                    key = "savory";
                }

                sparse.push({
                    name: key,
                    url: '/?q=' + key + '+' + query_encoded.replace(/ /g,'+'),
                    strength: Math.round(strength * 100)
                });
            }
        }

        if(!sparse.length){ return null; }

        return sparse.sort(function(a,b){
            return (a.strength > b.strength) ? -1 : 1;
        });
    }

    var //normalizedData = normalizeData(res.matches),

        searchContainedRecipe = !!(query_encoded.match(/recipe/i)),
        searchTerm = query.replace(/recipes|recipe/i,'').trim(),
        moreUrl = res.attribution.url + '?q=' + searchTerm + '&prm-v1'; // should replace trigger word or use the same logic that is used for the api call

    Spice.add({
        id: 'recipes',
        name: 'Recipes',

        data: res.matches,  //normalizedData,

        trump: searchContainedRecipe,   // XXX: signal?

        meta: {
            // count: normalizedData.length,
            total: res.totalMatchCount,
            searchTerm: searchTerm,
            itemType: 'Recipes',
            detailBg: 'image',
            detailClass: 'detail--i',

            // TODO: the following metadata will be injected by spice
            sourceIconUrl: DDG.get_asset_path('recipes','yummly.com.ico'), // temp fix for pb
            sourceUrl: moreUrl,
            sourceName: 'Yummly'
        },

        normalize: normalize,

        sort_fields: {
            rating: function(a,b){
                return (a.rating > b.rating) ? -1 : 1;
            }
        },
        sort_default: 'rating',

        /*
        relevancy: {

            skip_words: [ "recipes", "recipe", "ingredient", "ingredients" ],

            primary: [
                { key: 'recipeName' },
                { key: 'attributes.cuisine.0' },
                { key: 'ingredients' }
            ]

        },
        */

        templates: {
            group: 'products_simple',
            detail: Spice.recipes.recipes_detail,
            item_detail: Spice.recipes.recipes_detail,
            options: {
                brand: true,
                rating: true
            }
        }
    });
};
}(this));
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */

var should = require('should');
var jsonHttp = require('json-http');
var omdbApi = require('../index');
var sinon = require('sinon');

var _shouldNotHaveError = function(params, done) {
  omdbApi.get(params, function(err) {
    should.not.exist(err);
    done();
  });
};

var _shouldHaveErrorMessage = function(params, message, done) {
  omdbApi.get(params, function(err) {
    err.should.eql(message);
    done();
  });
};

var _shouldBeCalledWithUrl = function(params, expectedUrl, done) {
  omdbApi.get(params, function() {
    jsonHttp.getJson
    .calledWith(expectedUrl)
    .should.equal(true);
    done();
  });
};

describe('Get Film', function() {

  beforeEach(function() {
    sinon.stub(jsonHttp, 'getJson');
    jsonHttp.getJson.yields(null, { title: 'film title' });
  });

  afterEach(function() {
    jsonHttp.getJson.restore();
  });

  it('sets user defined timeout value', function(done) {
      var params = {
          apiKey: 'foo',
          title: 'Terminator',
          timeout: 3000
      };
      var url = 'http://www.omdbapi.com/?t=Terminator&apiKey=foo&r=json&v=1';
      omdbApi.get(params, function() {
          jsonHttp.getJson.calledWith(url, 3000).should.equal(true);
          done();
      });
  });

  it('uses default timeout of 10 seconds if not user defined', function(done) {
    var params = {
      apiKey: 'foo',
      title: 'Terminator'
    };
    var url = 'http://www.omdbapi.com/?t=Terminator&apiKey=foo&r=json&v=1';
    omdbApi.get(params, function() {
      jsonHttp.getJson.calledWith(url, 10000).should.equal(true);
      done();
    });
  });

  it('with http error returns error message', function(done) {

    var url = 'http://www.omdbapi.com/?t=Terminator&r=json&v=1';
    var params = {
      title: 'Terminator'
    };

    jsonHttp.getJson.withArgs(url).yields('timeout error', null);

    _shouldHaveErrorMessage(
      params,
      'timeout error',
      done);
  });

  it('with imdb error returns error message', function(done) {

    var url = 'http://www.omdbapi.com/?t=Alcatraz&r=json&v=1';

    var response = {
      Error: 'message from imdb server'
    };

    var params = {
      title: 'Alcatraz'
    };

    jsonHttp.getJson.withArgs(url).yields(null, response);

    _shouldHaveErrorMessage(
      params,
      'message from imdb server',
      done);
  });

  it('with imdb data returns response data', function(done) {

    var url = 'http://www.omdbapi.com/?t=The%20Brain%20Terminator&r=json&v=1';

    var response = {
      Title: 'The Brain Terminator',
      Year: '2012'
    };

    var params = {
      title: 'The Brain Terminator'
    };

    jsonHttp.getJson.withArgs(url).yields(null, response);

    omdbApi.get(params, function(err, data) {
      data.should.eql(response);
      done();
    });
  });

  describe('with invalid mandatory params', function() {

    it('returns error when params object is null', function(done) {
      _shouldHaveErrorMessage(
        null,
        'params cannot be null.',
        done);
    });

    it('returns error when id and title is empty', function(done) {
      _shouldHaveErrorMessage(
        {},
        'id or title param required.',
        done);
    });

  });

  describe('with "title" parameter', function() {

    it('returns error for invalid string', function(done) {
      var params = {
        title: 50
      };

      _shouldHaveErrorMessage(params, 'title must be a string.', done);
    });

    it('calls omdbapi with title querystring param', function(done) {
      var params = {
        title: 'Milk'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?t=Milk&r=json&v=1',
        done);
    });

    it('calls omdbapi with title and id querystring param', function(done) {
      var params = {
        title: 'Milk',
        id: 'tt1013753'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&t=Milk&r=json&v=1',
        done);
    });
  });

  describe('with "id" parameter', function() {

    it('returns error for invalid string', function(done) {
      var params = {
        id: 123
      };

      _shouldHaveErrorMessage(params, 'id must be a string.', done);
    });

    it('calls omdbapi with id querystring param', function(done) {
      var params = {
        id: 'tt1013753'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&r=json&v=1',
        done);
    });
  });

  describe('with "year" parameter', function() {

    it('returns error when type is not number', function(done) {
      var params = {
        title: 'Milk',
        year: 'movie'
      };

      _shouldHaveErrorMessage(
        params,
        'year must be a valid number',
        done);
    });

    it('calls omdbapi with year querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        year: 2008
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&y=2008&r=json&v=1',
        done);
    });

  });

  describe('with "incTomatoes" parameter', function() {

    it('returns error when type is not boolean', function(done) {
      var params = {
        title: 'Milk',
        incTomatoes: 'yes'
      };

      _shouldHaveErrorMessage(
        params,
        'incTomatoes must be a boolean.',
        done);
    });

    it('calls omdbapi with incTomatoes querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        incTomatoes: true,
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&tomatoes=true&r=json&v=1',
        done);
    });

  });

  describe('with "type" parameter', function() {

    it('returns error for invalid value', function(done) {
      var params = {
        title: 'Milk',
        type: 'film'
      };

      _shouldHaveErrorMessage(
        params,
        'type must be: movie, series, episode.',
        done);
    });

    it('returns no error for valid type', function(done) {
      var params = {
        title: 'Milk',
        type: 'movie'
      };

      _shouldNotHaveError(params, done);
    });

    it('calls omdbapi with type querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        type: 'episode'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&type=episode&r=json&v=1',
        done);
    });

  });

  describe('with "plot" parameter', function() {

    it('returns error for invalid value', function(done) {
      var params = {
        title: 'Milk',
        plot: 'big'
      };

      _shouldHaveErrorMessage(
        params,
        'plot must be: short, full.',
        done);
    });

    it('returns no error for valid plot', function(done) {
      var params = {
        title: 'Milk',
        plot: 'short'
      };

      _shouldNotHaveError(params, done);
    });

    it('calls omdbapi with plot querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        plot: 'full'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&plot=full&r=json&v=1',
        done);
    });

  });

});
<commit_msg>checking tests to remove dependencies<commit_after>'use strict';

/**
 * Module dependencies.
 */

var should = require('should');
var jsonHttp = require('json-http');
var omdbApi = require('../index');
var sinon = require('sinon');

var _shouldNotHaveError = function(params, done) {
  omdbApi.get(params, function(err) {
    should.not.exist(err);
    done();
  });
};

var _shouldHaveErrorMessage = function(params, message, done) {
  omdbApi.get(params, function(err) {
    err.should.eql(message);
    done();
  });
};

var _shouldBeCalledWithUrl = function(params, expectedUrl, done) {
  omdbApi.get(params, function() {
    jsonHttp.getJson
    .calledWith(expectedUrl)
    .should.equal(true);
    done();
  });
};

describe('Get Film', function() {

  beforeEach(function() {
    sinon.stub(jsonHttp, 'getJson');
    jsonHttp.getJson.yields(null, { title: 'film title' });
  });

  afterEach(function() {
    jsonHttp.getJson.restore();
  });

  it('sets user defined timeout value', function(done) {
      var params = {
          title: 'Terminator',
          timeout: 3000
      };
      var url = 'http://www.omdbapi.com/?t=Terminator&r=json&v=1';
      omdbApi.get(params, function() {
          jsonHttp.getJson.calledWith(url, 3000).should.equal(true);
          done();
      });
  });

  it('uses default timeout of 10 seconds if not user defined', function(done) {
    var params = {
      apiKey: 'foo',
      title: 'Terminator'
    };
    var url = 'http://www.omdbapi.com/?t=Terminator&apiKey=foo&r=json&v=1';
    omdbApi.get(params, function() {
      jsonHttp.getJson.calledWith(url, 10000).should.equal(true);
      done();
    });
  });

  it('with http error returns error message', function(done) {

    var url = 'http://www.omdbapi.com/?t=Terminator&r=json&v=1';
    var params = {
      title: 'Terminator'
    };

    jsonHttp.getJson.withArgs(url).yields('timeout error', null);

    _shouldHaveErrorMessage(
      params,
      'timeout error',
      done);
  });

  it('with imdb error returns error message', function(done) {

    var url = 'http://www.omdbapi.com/?t=Alcatraz&r=json&v=1';

    var response = {
      Error: 'message from imdb server'
    };

    var params = {
      title: 'Alcatraz'
    };

    jsonHttp.getJson.withArgs(url).yields(null, response);

    _shouldHaveErrorMessage(
      params,
      'message from imdb server',
      done);
  });

  it('with imdb data returns response data', function(done) {

    var url = 'http://www.omdbapi.com/?t=The%20Brain%20Terminator&r=json&v=1';

    var response = {
      Title: 'The Brain Terminator',
      Year: '2012'
    };

    var params = {
      title: 'The Brain Terminator'
    };

    jsonHttp.getJson.withArgs(url).yields(null, response);

    omdbApi.get(params, function(err, data) {
      data.should.eql(response);
      done();
    });
  });

  describe('with invalid mandatory params', function() {

    it('returns error when params object is null', function(done) {
      _shouldHaveErrorMessage(
        null,
        'params cannot be null.',
        done);
    });

    it('returns error when id and title is empty', function(done) {
      _shouldHaveErrorMessage(
        {},
        'id or title param required.',
        done);
    });

  });

  describe('with "title" parameter', function() {

    it('returns error for invalid string', function(done) {
      var params = {
        title: 50
      };

      _shouldHaveErrorMessage(params, 'title must be a string.', done);
    });

    it('calls omdbapi with title querystring param', function(done) {
      var params = {
        title: 'Milk'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?t=Milk&r=json&v=1',
        done);
    });

    it('calls omdbapi with title and id querystring param', function(done) {
      var params = {
        title: 'Milk',
        id: 'tt1013753'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&t=Milk&r=json&v=1',
        done);
    });
  });

  describe('with "id" parameter', function() {

    it('returns error for invalid string', function(done) {
      var params = {
        id: 123
      };

      _shouldHaveErrorMessage(params, 'id must be a string.', done);
    });

    it('calls omdbapi with id querystring param', function(done) {
      var params = {
        id: 'tt1013753'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&r=json&v=1',
        done);
    });
  });

  describe('with "year" parameter', function() {

    it('returns error when type is not number', function(done) {
      var params = {
        title: 'Milk',
        year: 'movie'
      };

      _shouldHaveErrorMessage(
        params,
        'year must be a valid number',
        done);
    });

    it('calls omdbapi with year querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        year: 2008
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&y=2008&r=json&v=1',
        done);
    });

  });

  describe('with "incTomatoes" parameter', function() {

    it('returns error when type is not boolean', function(done) {
      var params = {
        title: 'Milk',
        incTomatoes: 'yes'
      };

      _shouldHaveErrorMessage(
        params,
        'incTomatoes must be a boolean.',
        done);
    });

    it('calls omdbapi with incTomatoes querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        incTomatoes: true,
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&tomatoes=true&r=json&v=1',
        done);
    });

  });

  describe('with "type" parameter', function() {

    it('returns error for invalid value', function(done) {
      var params = {
        title: 'Milk',
        type: 'film'
      };

      _shouldHaveErrorMessage(
        params,
        'type must be: movie, series, episode.',
        done);
    });

    it('returns no error for valid type', function(done) {
      var params = {
        title: 'Milk',
        type: 'movie'
      };

      _shouldNotHaveError(params, done);
    });

    it('calls omdbapi with type querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        type: 'episode'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&type=episode&r=json&v=1',
        done);
    });

  });

  describe('with "plot" parameter', function() {

    it('returns error for invalid value', function(done) {
      var params = {
        title: 'Milk',
        plot: 'big'
      };

      _shouldHaveErrorMessage(
        params,
        'plot must be: short, full.',
        done);
    });

    it('returns no error for valid plot', function(done) {
      var params = {
        title: 'Milk',
        plot: 'short'
      };

      _shouldNotHaveError(params, done);
    });

    it('calls omdbapi with plot querystring param', function(done) {
      var params = {
        id: 'tt1013753',
        plot: 'full'
      };

      _shouldBeCalledWithUrl(
        params,
        'http://www.omdbapi.com/' +
          '?i=tt1013753&plot=full&r=json&v=1',
        done);
    });

  });

});
<|endoftext|>


<commit_before>// CartState.factory.js
/*
	CartState:

		State that contains the current shopping and totals.
		Note: total price is derived value from cartLines, the same
		that list that is a subset.

*/
'use strict';

module.exports = CartStateFactory;

/* @ngInject */
function CartStateFactory(cartLinesState, drpxUpdateable, $rootScope) {
	var state = {
		list: [],
		totalItems: 0,
		totalPrice: 0,
		$update: drpxUpdateable('scd.CartUpdate'),
	};

	// it depenends of cart lines and product prices
	$rootScope.$on('scd.productsUpdate', _update);
	$rootScope.$on('scd.cartLinesUpdate', _update);
	// trigger always the action of the event, just if you missed it
	_update();

	return state;

	//////

	//////

	// update its state depending of others, it should be repeatable
	// in short: it computes the "invariants"
	// do you want to optimize it? (really? not needed): use one for loop
	function _update() {

		state.list.length = 0;
		cartLinesState.list.forEach(function(cartLine){
			if (cartLine.quantity) {
				state.list.push(cartLine);
			}
		});

		state.totalItems = state.list.reduce(function(sum, cartLine) {
			return sum + cartLine.quantity;
		}, 0);

		state.totalPrice = state.list.reduce(function(sum, cartLine) {
			return sum + cartLine.product.price * cartLine.quantity;
		}, 0);
	}

}<commit_msg>add cartState.$update in cartState derive values<commit_after>// CartState.factory.js
/*
	CartState:

		State that contains the current shopping and totals.
		Note: total price is derived value from cartLines, the same
		that list that is a subset.

*/
'use strict';

module.exports = CartStateFactory;

/* @ngInject */
function CartStateFactory(cartLinesState, drpxUpdateable, $rootScope) {
	var state = {
		list: [],
		totalItems: 0,
		totalPrice: 0,
		$update: drpxUpdateable('scd.CartUpdate'),
	};

	// it depenends of cart lines and product prices
	$rootScope.$on('scd.productsUpdate', _update);
	$rootScope.$on('scd.cartLinesUpdate', _update);
	// trigger always the action of the event, just if you missed it
	_update();

	return state;

	//////

	//////

	// update its state depending of others, it should be repeatable
	// in short: it computes the "invariants"
	// do you want to optimize it? (really? not needed): use one for loop
	function _update() {

		state.list.length = 0;
		cartLinesState.list.forEach(function(cartLine){
			if (cartLine.quantity) {
				state.list.push(cartLine);
			}
		});

		state.totalItems = state.list.reduce(function(sum, cartLine) {
			return sum + cartLine.quantity;
		}, 0);

		state.totalPrice = state.list.reduce(function(sum, cartLine) {
			return sum + cartLine.product.price * cartLine.quantity;
		}, 0);

		// cartState is updated... so, we notify
		state.$update();
	}

}<|endoftext|>


<commit_before>import Immutable from 'immutable';

const defaultState = Immutable.Map({loaded: false});

export default function authReducer(state = defaultState, action = {}) {
  switch (action.type) {
    case 'LOAD':
      return state.merge({loaded: true, user: action.res.data});

    case 'LOGIN_REQUEST':
      return state.set('loggingIn', true);

    case 'LOGIN':
      return state.merge({loggingIn: false, user: action.res.data, error: false});

    case 'LOGIN_FAILURE':
      return state.merge({loggingIn: false, user: '', error: true});

    case 'LOGOUT':
      return state.set('user', '');

    default:
      return state;
  }
}
<commit_msg>Set another default value for `user`<commit_after>import Immutable from 'immutable';

const defaultState = Immutable.Map({loaded: false, user: null});

export default function authReducer(state = defaultState, action = {}) {
  switch (action.type) {
    case 'LOAD':
      return state.merge({loaded: true, user: action.res.data || null});

    case 'LOGIN_REQUEST':
      return state.set('loggingIn', true);

    case 'LOGIN':
      return state.merge({loggingIn: false, user: action.res.data || null, error: false});

    case 'LOGIN_FAILURE':
      return state.merge({loggingIn: false, user: null, error: true});

    case 'LOGOUT':
      return state.set('user', null);

    default:
      return state;
  }
}
<|endoftext|>


<commit_before>var db = require(global.paths.lib + 'database-handler').db;

var viewQueue = function(bot, message) {
  db.serialize(function() {
    db.all('SELECT q.position AS position, s.id AS id, s.type AS type, s.url AS url, s.artist AS artist, s.title AS title, s.metadata AS metadata FROM UserQueue q INNER JOIN Song s ON q.songId = s.id WHERE q.userId = ? ORDER BY q.position LIMIT 10',
      [ message.author.id ],
      function(error, rows) {
        var description = '';

        for (var i in rows) {
          var row = rows[i];
          description += (row.position) + ') [' + row.title + '](' + row.url + ')\n';
        }

        // TODO: Move back into qview
        var Discord = require('discord.js');

        var embed = new Discord.RichEmbed();

        embed.setFooter('Requested by ' + message.author.username, message.author.avatarURL);
        embed.setTimestamp(new Date());

        embed.setAuthor(message.author.username + '\'s Queue', message.author.avatarURL);
        embed.setDescription(description);

        if (rows.length > 0) {
          var firstSongMetadata = JSON.parse(rows[0].metadata);
          embed.setThumbnail(firstSongMetadata.snippet.thumbnails.medium.url);
        }

        message.channel.send('', { embed: embed });
      });
  });
};

var removeSong = function(bot, message, queuePosition) {
  if (queuePosition < 1) {
    return message.reply('please provide a valid queue position.');
  }

  db.serialize(function() {
    db.get('SELECT s.title AS title FROM UserQueue q INNER JOIN Song s ON q.songId = s.id WHERE q.userId = ? AND q.position = ?',
      [
        message.author.id,
        queuePosition
      ],
      function(getErr, row) {
        if (getErr || !row) {
          return message.reply('please provide a valid queue position.');
        }

        db.run('DELETE FROM UserQueue WHERE userId = ? AND position = ?',
          [
            message.author.id,
            queuePosition
          ], function(runErr) {
            if (!runErr) {
              db.run('UPDATE UserQueue SET position=position-1 WHERE userId = ? AND position > ?',
                [
                  message.author.id,
                  queuePosition
                ]);

              message.reply('_' + row.title + '_ was removed from your queue.');
            }
        });
    });
  });
};

var queueSong = function(bot, message, song) {
  var user = message.author;

  db.serialize(function() {
    // Add user account if not already exists
    db.run('INSERT OR REPLACE INTO User (id, serverId, username, avatar, upvotes, downvotes, points, lastActive) VALUES (?, ?, ?, ?, (SELECT upvotes FROM User WHERE id = ?), (SELECT downvotes FROM User WHERE id = ?), (SELECT points FROM User WHERE id = ?), ?)',
      [
        user.id,
        message.guild.id,
        user.username,
        user.avatarURL,
        user.id,
        user.id,
        user.id,
        new Date()
      ]);

    // Insert/replace song data
    // Since Youtube is our only provider, we're using the Youtube ID as a unique PK
    // Replace since we want any updated metadata
    db.run('INSERT OR REPLACE INTO Song (id, type, url, title, metadata) VALUES (?, ?, ?, ?, ?)',
      [
        song.id.videoId,
        'youtube',
        'https://www.youtube.com/watch?v=' + song.id.videoId,
        song.snippet.title,
        JSON.stringify(song)
      ]);

    // Insert into this user's queue at position 1, shift all other playlist entries down one
    db.run('UPDATE UserQueue SET position=position+1 WHERE userId = ?', [ user.id ]);
    db.run('INSERT INTO UserQueue (userId, position, songId, dateAdded) VALUES (?, ?, ?, ?)',
      [
        user.id,
        1,
        song.id.videoId,
        new Date()
      ]);

    message.reply('I\'ve added the song to the top of your queue!');
  });
};

var getQueuedSong = function(userId, callback) {
  db.serialize(function() {
    db.get('SELECT s.id AS id, s.type AS type, s.url AS url, s.artist AS artist, s.title AS title, s.metadata AS metadata FROM UserQueue q INNER JOIN Song s ON q.songId = s.id WHERE q.userId = ? AND q.position = 1 LIMIT 1',
      [ userId ], function(err, row) {
        callback(row);
      });
  })
};

var cycleQueue = function(userId) {
  db.serialize(function() {
    //db.run('DELETE FROM UserQueue WHERE userId = ? AND position = 1', [ userId ]);
    db.run('UPDATE UserQueue SET position = (SELECT count(*) + 1 FROM UserQueue WHERE userId = ?) WHERE userId = ? AND position = 1',
      [ userId, userId ]);

    db.run('UPDATE UserQueue SET position=position-1 WHERE userId = ?', [ userId ]);
  })
}

module.exports = {
  queueSong: queueSong,
  removeSong: removeSong,
  viewQueue: viewQueue,
  cycleQueue: cycleQueue,
  getQueuedSong: getQueuedSong
};

<commit_msg>Add getQueueLength db fn<commit_after>var db = require(global.paths.lib + 'database-handler').db;

var viewQueue = function(bot, message) {
  db.serialize(function() {
    db.all('SELECT q.position AS position, s.id AS id, s.type AS type, s.url AS url, s.artist AS artist, s.title AS title, s.metadata AS metadata FROM UserQueue q INNER JOIN Song s ON q.songId = s.id WHERE q.userId = ? ORDER BY q.position LIMIT 10',
      [ message.author.id ],
      function(error, rows) {
        var description = '';

        for (var i in rows) {
          var row = rows[i];
          description += (row.position) + ') [' + row.title + '](' + row.url + ')\n';
        }

        // TODO: Move back into qview
        var Discord = require('discord.js');

        var embed = new Discord.RichEmbed();

        embed.setFooter('Requested by ' + message.author.username, message.author.avatarURL);
        embed.setTimestamp(new Date());

        embed.setAuthor(message.author.username + '\'s Queue', message.author.avatarURL);
        embed.setDescription(description);

        if (rows.length > 0) {
          var firstSongMetadata = JSON.parse(rows[0].metadata);
          embed.setThumbnail(firstSongMetadata.snippet.thumbnails.medium.url);
        }

        message.channel.send('', { embed: embed });
      });
  });
};

var removeSong = function(bot, message, queuePosition) {
  if (queuePosition < 1) {
    return message.reply('please provide a valid queue position.');
  }

  db.serialize(function() {
    db.get('SELECT s.title AS title FROM UserQueue q INNER JOIN Song s ON q.songId = s.id WHERE q.userId = ? AND q.position = ?',
      [
        message.author.id,
        queuePosition
      ],
      function(getErr, row) {
        if (getErr || !row) {
          return message.reply('please provide a valid queue position.');
        }

        db.run('DELETE FROM UserQueue WHERE userId = ? AND position = ?',
          [
            message.author.id,
            queuePosition
          ], function(runErr) {
            if (!runErr) {
              db.run('UPDATE UserQueue SET position=position-1 WHERE userId = ? AND position > ?',
                [
                  message.author.id,
                  queuePosition
                ]);

              message.reply('_' + row.title + '_ was removed from your queue.');
            }
        });
    });
  });
};

var queueSong = function(bot, message, song) {
  var user = message.author;

  db.serialize(function() {
    // Add user account if not already exists
    db.run('INSERT OR REPLACE INTO User (id, serverId, username, avatar, upvotes, downvotes, points, lastActive) VALUES (?, ?, ?, ?, (SELECT upvotes FROM User WHERE id = ?), (SELECT downvotes FROM User WHERE id = ?), (SELECT points FROM User WHERE id = ?), ?)',
      [
        user.id,
        message.guild.id,
        user.username,
        user.avatarURL,
        user.id,
        user.id,
        user.id,
        new Date()
      ]);

    // Insert/replace song data
    // Since Youtube is our only provider, we're using the Youtube ID as a unique PK
    // Replace since we want any updated metadata
    db.run('INSERT OR REPLACE INTO Song (id, type, url, title, metadata) VALUES (?, ?, ?, ?, ?)',
      [
        song.id.videoId,
        'youtube',
        'https://www.youtube.com/watch?v=' + song.id.videoId,
        song.snippet.title,
        JSON.stringify(song)
      ]);

    // Insert into this user's queue at position 1, shift all other playlist entries down one
    db.run('UPDATE UserQueue SET position=position+1 WHERE userId = ?', [ user.id ]);
    db.run('INSERT INTO UserQueue (userId, position, songId, dateAdded) VALUES (?, ?, ?, ?)',
      [
        user.id,
        1,
        song.id.videoId,
        new Date()
      ]);

    message.reply('I\'ve added the song to the top of your queue!');
  });
};

var getQueuedSong = function(userId, callback) {
  db.serialize(function() {
    db.get('SELECT s.id AS id, s.type AS type, s.url AS url, s.artist AS artist, s.title AS title, s.metadata AS metadata FROM UserQueue q INNER JOIN Song s ON q.songId = s.id WHERE q.userId = ? AND q.position = 1 LIMIT 1',
      [ userId ], function(err, row) {
        callback(row);
      });
  })
};

var cycleQueue = function(userId) {
  db.serialize(function() {
    //db.run('DELETE FROM UserQueue WHERE userId = ? AND position = 1', [ userId ]);
    db.run('UPDATE UserQueue SET position = (SELECT count(*) + 1 FROM UserQueue WHERE userId = ?) WHERE userId = ? AND position = 1',
      [ userId, userId ]);

    db.run('UPDATE UserQueue SET position=position-1 WHERE userId = ?', [ userId ]);
  });
};

var getQueueLength = function(userid, callback) {
  db.serialize(function() {
    db.get('SELECT count(*) AS count FROM UserQueue WHERE userId = ?', [ userid ], function(err, row) {
      callback(row.count);
    });
  })
};

module.exports = {
  queueSong: queueSong,
  removeSong: removeSong,
  viewQueue: viewQueue,
  cycleQueue: cycleQueue,
  getQueuedSong: getQueuedSong,
  getQueueLength: getQueueLength
};

<|endoftext|>


<commit_before>/*global Quintus:false, AudioContext:false, window:false */

Quintus.Audio = function(Q) {

  Q.audio = {
    channels: [],
    channelMax:  Q.options.channelMax || 10,
    active: {},
    play: function() {}
  };

  Q.hasWebAudio = (typeof AudioContext !== "undefined") || (typeof webkitAudioContext !== "undefined");

  if(Q.hasWebAudio) {
    if(typeof AudioContext !== "undefined") {
      Q.audioContext = new AudioContext();
    } else {
      Q.audioContext = new window.webkitAudioContext();
    }
  }

  Q.enableSound = function() {
    var hasTouch =  !!('ontouchstart' in window);

    if(Q.hasWebAudio) {
      Q.audio.enableWebAudioSound();
    } else {
      Q.audio.enableHTML5Sound();
    }
    return Q;
  };

  Q.audio.enableWebAudioSound = function() {
    Q.audio.type = "WebAudio";

    Q.audio.soundID = 0;

    Q.audio.playingSounds = {};

    Q.audio.removeSound = function(soundID) {
      delete Q.audio.playingSounds[soundID];
    };

    // Play a list of sounds.
    Q.audio.playlist = function(playlist,options) {
        var playlistIndex = 0;

        //play first sound
        if (typeof playlist[playlistIndex] !== 'undefined') {
          Q.audio.play(playlist[playlistIndex], options, function() {
              Q.audio.playNextTitle(playlist, playlistIndex, options);
          });
        }
    };

    Q.audio.playNextTitle = function(playlist, playlistIndex, options) {
        //increase playlist index
        playlistIndex++;

        //if next sound exists, play it
        if(typeof playlist[playlistIndex] !== 'undefined') {
            Q.audio.play(playlist[playlistIndex], options, function() {
                Q.audio.playNextTitle(playlist, playlistIndex, options);
            });
        }
    };

    // Play a single sound, optionally debounced
    // to prevent repeated plays in a short time
    Q.audio.play = function(s,options, callback) {
      var now = new Date().getTime();

      // See if this audio file is currently being debounced, if
      // it is, don't do anything and just return
      if(Q.audio.active[s] && Q.audio.active[s] > now) { return; }

      // If any options were passed in, check for a debounce,
      // which is the number of milliseconds to debounce this sound
      if(options && options['debounce']) {
        Q.audio.active[s] = now + options['debounce'];
      } else {
        delete Q.audio.active[s];
      }

      var soundID = Q.audio.soundID++;

      var source = Q.audioContext.createBufferSource();
      source.buffer = Q.asset(s);
      source.connect(Q.audioContext.destination);
      if(options && options['loop']) {
        source.loop = true;
      } else {
        setTimeout(function() {
          Q.audio.removeSound(soundID);
          if(typeof callback !== 'undefined') {
            callback();
          }

        },source.buffer.duration * 1000);
      }
      source.assetName = s;
      if(source.start) { source.start(0); } else { source.noteOn(0); }

      Q.audio.playingSounds[soundID] = source;

    };

    Q.audio.stop = function(s) {
      for(var key in Q.audio.playingSounds) {
        var snd = Q.audio.playingSounds[key];
        if(!s || s === snd.assetName) {
          if(snd.stop) { snd.stop(0);  } else {  snd.noteOff(0); }
        }
      }
    };

  };

  Q.audio.enableHTML5Sound = function() {
    Q.audio.type = "HTML5";

    for (var i=0;i<Q.audio.channelMax;i++) {
      Q.audio.channels[i] = {};
      Q.audio.channels[i]['channel'] = new Audio();
      Q.audio.channels[i]['finished'] = -1;
    }

    // Play a single sound, optionally debounced
    // to prevent repeated plays in a short time
    Q.audio.play = function(s,options,callback) {
      var now = new Date().getTime();

      // See if this audio file is currently being debounced, if
      // it is, don't do anything and just return
      if(Q.audio.active[s] && Q.audio.active[s] > now) { return; }

      // If any options were passed in, check for a debounce,
      // which is the number of milliseconds to debounce this sound
      if(options && options['debounce']) {
        Q.audio.active[s] = now + options['debounce'];
      } else {
        delete Q.audio.active[s];
      }

      // Find a free audio channel and play the sound
      for (var i=0;i<Q.audio.channels.length;i++) {
        // Check the channel is either finished or not looping
        if (!Q.audio.channels[i]['loop'] && Q.audio.channels[i]['finished'] < now) {

          Q.audio.channels[i]['channel'].src = Q.asset(s).src;

          if(typeof callback !== 'undefined') {
            Q.audio.channels[i].addEventListener('ended', callback());
          }

          // If we're looping - just set loop to true to prevent this channcel
          // from being used.
          if(options && options['loop']) {
            Q.audio.channels[i]['loop'] = true;
            Q.audio.channels[i]['channel'].loop = true;
          } else {
            Q.audio.channels[i]['finished'] = now + Q.asset(s).duration*1000;
          }
          Q.audio.channels[i]['channel'].load();
          Q.audio.channels[i]['channel'].play();
          break;
        }
      }
    };

    // Stop a single sound asset or stop all sounds currently playing
    Q.audio.stop = function(s) {
      var src = s ? Q.asset(s).src : null;
      var tm = new Date().getTime();
      for (var i=0;i<Q.audio.channels.length;i++) {
        if ((!src || Q.audio.channels[i]['channel'].src === src) &&
            (Q.audio.channels[i]['loop'] || Q.audio.channels[i]['finished'] >= tm)) {
          Q.audio.channels[i]['channel'].pause();
          Q.audio.channels[i]['loop'] = false;
        }
      }
    };

  };

};

<commit_msg>Refactoring<commit_after>/*global Quintus:false, AudioContext:false, window:false */

Quintus.Audio = function(Q) {

  Q.audio = {
    channels: [],
    channelMax:  Q.options.channelMax || 10,
    active: {},
    play: function() {}
  };

  Q.hasWebAudio = (typeof AudioContext !== "undefined") || (typeof webkitAudioContext !== "undefined");

  if(Q.hasWebAudio) {
    if(typeof AudioContext !== "undefined") {
      Q.audioContext = new AudioContext();
    } else {
      Q.audioContext = new window.webkitAudioContext();
    }
  }

  Q.enableSound = function() {
    var hasTouch =  !!('ontouchstart' in window);

    if(Q.hasWebAudio) {
      Q.audio.enableWebAudioSound();
    } else {
      Q.audio.enableHTML5Sound();
    }
    return Q;
  };

  // Play a list of sounds.
    Q.audio.playlist = function(playlist,options) {
        var playlistIndex = 0;

        //play first sound
        if (typeof playlist[playlistIndex] !== 'undefined') {
          Q.audio.play(playlist[playlistIndex], options, function() {
              Q.audio.playNextTitle(playlist, playlistIndex, options);
          });
        }
    };

    Q.audio.playNextTitle = function(playlist, playlistIndex, options) {
        //increase playlist index
        playlistIndex++;

        //if next sound exists, play it
        if(typeof playlist[playlistIndex] !== 'undefined') {
            Q.audio.play(playlist[playlistIndex], options, function() {
                Q.audio.playNextTitle(playlist, playlistIndex, options);
            });
        }
    };

  Q.audio.enableWebAudioSound = function() {
    Q.audio.type = "WebAudio";

    Q.audio.soundID = 0;

    Q.audio.playingSounds = {};

    Q.audio.removeSound = function(soundID) {
      delete Q.audio.playingSounds[soundID];
    };

    // Play a single sound, optionally debounced
    // to prevent repeated plays in a short time
    Q.audio.play = function(s,options, callback) {
      var now = new Date().getTime();

      // See if this audio file is currently being debounced, if
      // it is, don't do anything and just return
      if(Q.audio.active[s] && Q.audio.active[s] > now) { return; }

      // If any options were passed in, check for a debounce,
      // which is the number of milliseconds to debounce this sound
      if(options && options['debounce']) {
        Q.audio.active[s] = now + options['debounce'];
      } else {
        delete Q.audio.active[s];
      }

      var soundID = Q.audio.soundID++;

      var source = Q.audioContext.createBufferSource();
      source.buffer = Q.asset(s);
      source.connect(Q.audioContext.destination);
      if(options && options['loop']) {
        source.loop = true;
      } else {
        setTimeout(function() {
          Q.audio.removeSound(soundID);
          if(typeof callback !== 'undefined') {
            callback();
          }

        },source.buffer.duration * 1000);
      }
      source.assetName = s;
      if(source.start) { source.start(0); } else { source.noteOn(0); }

      Q.audio.playingSounds[soundID] = source;

    };

    Q.audio.stop = function(s) {
      for(var key in Q.audio.playingSounds) {
        var snd = Q.audio.playingSounds[key];
        if(!s || s === snd.assetName) {
          if(snd.stop) { snd.stop(0);  } else {  snd.noteOff(0); }
        }
      }
    };

  };

  Q.audio.enableHTML5Sound = function() {
    Q.audio.type = "HTML5";

    for (var i=0;i<Q.audio.channelMax;i++) {
      Q.audio.channels[i] = {};
      Q.audio.channels[i]['channel'] = new Audio();
      Q.audio.channels[i]['finished'] = -1;
    }

    // Play a single sound, optionally debounced
    // to prevent repeated plays in a short time
    Q.audio.play = function(s,options,callback) {
      var now = new Date().getTime();

      // See if this audio file is currently being debounced, if
      // it is, don't do anything and just return
      if(Q.audio.active[s] && Q.audio.active[s] > now) { return; }

      // If any options were passed in, check for a debounce,
      // which is the number of milliseconds to debounce this sound
      if(options && options['debounce']) {
        Q.audio.active[s] = now + options['debounce'];
      } else {
        delete Q.audio.active[s];
      }

      // Find a free audio channel and play the sound
      for (var i=0;i<Q.audio.channels.length;i++) {
        // Check the channel is either finished or not looping
        if (!Q.audio.channels[i]['loop'] && Q.audio.channels[i]['finished'] < now) {

          Q.audio.channels[i]['channel'].src = Q.asset(s).src;

          if(typeof callback !== 'undefined') {
            Q.audio.channels[i].channel.addEventListener('ended', callback());
          }

          // If we're looping - just set loop to true to prevent this channcel
          // from being used.
          if(options && options['loop']) {
            Q.audio.channels[i]['loop'] = true;
            Q.audio.channels[i]['channel'].loop = true;
          } else {
            Q.audio.channels[i]['finished'] = now + Q.asset(s).duration*1000;
          }
          Q.audio.channels[i]['channel'].load();
          Q.audio.channels[i]['channel'].play();
          break;
        }
      }
    };

    // Stop a single sound asset or stop all sounds currently playing
    Q.audio.stop = function(s) {
      var src = s ? Q.asset(s).src : null;
      var tm = new Date().getTime();
      for (var i=0;i<Q.audio.channels.length;i++) {
        if ((!src || Q.audio.channels[i]['channel'].src === src) &&
            (Q.audio.channels[i]['loop'] || Q.audio.channels[i]['finished'] >= tm)) {
          Q.audio.channels[i]['channel'].pause();
          Q.audio.channels[i]['loop'] = false;
        }
      }
    };

  };

};

<|endoftext|>