<commit_before>// @flow
import { autorun } from 'mobx'
import { createMemoryHistory } from 'history'
import delay from './util/delay'
import { EventTypes } from './events'
import { install, Middleware } from './'

describe('Public API', () => {
  let router

  beforeEach(() => {
    router = install({
      history: createMemoryHistory({ initialEntries: ['/initial'], initialIndex: 0 }),
      getContext: () => ({ message: 'Hello' }),
      routes: [{ path: ':whatever' }]
    })
  })

  test('context chain', () => {
    expect(router.store.state.root.value.getContext()).toEqual({ message: 'Hello' })
  })

  test('reaction to push navigation', async () => {
    const changes = []
    await router.start()

    autorun(() => changes.push(router.store.location.pathname))

    router.push('/foo')
    await router.push('/bar')

    expect(router.store.location.pathname).toEqual('/bar/')

    await router.goBack()

    expect(router.store.location.pathname).toEqual('/foo/')

    router.push('/bar')
    await router.replace('/quux')

    expect(router.store.location.pathname).toEqual('/quux/')

    expect(changes).toEqual(['/initial/', '/foo/', '/bar/', '/foo/', '/bar/', '/quux/'])

    expect(router.store.routes.map(route => route.node.value.path)).toEqual([
      '',
      ':whatever'
    ])

    router.stop()
  })

  test('multiple navigation', async () => {
    const changes = []
    await router.start()

    autorun(() => changes.push(router.store.location.pathname))

    router.push('/1')
    router.push('/2')
    router.push('/3')
    router.push('/4')
    router.push('/5')
    router.push('/6')
    router.push('/7')
    router.push('/8')
    router.push('/9')
    await router.push('/10')

    expect(changes).toEqual([
      '/initial/',
      '/1/',
      '/2/',
      '/3/',
      '/4/',
      '/5/',
      '/6/',
      '/7/',
      '/8/',
      '/9/',
      '/10/'
    ])

    expect(router.store.routes.map(route => route.node.value.path)).toEqual([
      '',
      ':whatever'
    ])

    router.stop()
  })

  test('callback API', done => {
    const changes = []
    router.start(_router => {
      expect(_router).toBe(router)

      autorun(() => changes.push(_router.store.location.pathname))

      _router.push('/1').then(() => {
        expect(changes).toEqual(['/initial/', '/1/'])

        expect(_router.store.routes.map(route => route.node.value.path)).toEqual([
          '',
          ':whatever'
        ])

        _router.stop()
        done()
      })
    })
  })

  describe('Events', () => {
    test('subscription', async () => {
      const spy = jest.fn()
      const dispose = router.subscribeEvent(spy)
      await router.start()

      await router.push('/bar')

      expect(spy).toHaveBeenCalled()

      expect(spy.mock.calls.map(x => x[0].type)).toEqual(
        expect.arrayContaining([EventTypes.NAVIGATION_START, EventTypes.NAVIGATION_END])
      )

      expect(
        spy.mock.calls.map(x => x[0].navigation && x[0].navigation.to.pathname)
      ).toEqual(expect.arrayContaining(['/initial/', '/bar/']))

      dispose()
    })
  })

  describe('total ordering', () => {
    test('navigation is guaranteed to be processed sequentially and in order', async () => {
      const MAX_DURATION = 50
      const spy = jest.fn(() => {
        return delay(Math.random() * MAX_DURATION)
      })
      const router = install({
        history: createMemoryHistory({ initialEntries: ['/initial'], initialIndex: 0 }),
        getContext: () => ({ message: 'Hello' }),
        routes: [{ path: ':whatever', onTransition: spy }]
      })

      const changes = []
      await router.start()

      autorun(() => changes.push(router.store.location.pathname))

      router.push('/a')
      router.push('/b')
      router.push('/c')
      router.push('/d')

      await delay(MAX_DURATION * 4)

      expect(changes).toEqual(['/initial/', '/a/', '/b/', '/c/', '/d/'])

      router.stop()
    })
  })

  test('catch-all routes', async () => {
    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a'], initialIndex: 0 }),
      getContext: () => ({}),
      routes: [{ path: 'a' }, { path: '**' }]
    })

    const changes = []
    await router.start()

    autorun(() => changes.push(router.store.location.pathname))

    await router.push('/404')

    expect(changes).toEqual(['/a/', '/404/'])

    router.stop()
  })

  test('willResolve hook', async () => {
    const willActivateSpy = jest.fn(() => Promise.resolve())
    const willResolveSpy = jest.fn(() => Promise.resolve())
    const willDeactivateSpy = jest.fn(() => Promise.resolve())

    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a/1'], initialIndex: 0 }),
      getContext: () => ({}),
      routes: [
        {
          path: 'a/:id',
          query: ['q'],
          willActivate: willActivateSpy,
          willResolve: willResolveSpy,
          willDeactivate: willDeactivateSpy
        }
      ]
    })

    await router.start()
    await router.push('/a/2')
    await router.push('/a/2?q=hey')
    await router.push('/a/2?q=now')
    await router.push('/a/2?q=now&w=what')

    expect(willActivateSpy.mock.calls.length).toBe(1)
    expect(willResolveSpy.mock.calls.length).toBe(4)
    expect(willDeactivateSpy.mock.calls.length).toBe(0)

    await router.push('/')

    expect(willDeactivateSpy.mock.calls.length).toBe(1)

    router.stop()
  })

  test('middleware', async () => {
    const middleware = (evt): any => {
      if (evt.type === 'CHILDREN_CONFIG_LOAD') {
        return {
          ...evt,
          module: evt.module.routes
        }
      }
      return evt
    }

    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a'], initialIndex: 0 }),
      getContext: () => ({}),
      middleware: Middleware(middleware),
      routes: [
        {
          path: 'a',
          loadChildren: () => {
            return Promise.resolve({
              routes: [{ path: 'b' }]
            })
          }
        }
      ]
    })

    await router.start()
    await router.push('/a/b')

    expect(router.store.location).toEqual(
      expect.objectContaining({
        pathname: '/a/b/'
      })
    )
  })
})
<commit_msg>Adding getContext test.<commit_after>// @flow
import { autorun } from 'mobx'
import { createMemoryHistory } from 'history'
import delay from './util/delay'
import { EventTypes } from './events'
import { install, Middleware } from './'

describe('Public API', () => {
  let router

  beforeEach(() => {
    router = install({
      history: createMemoryHistory({ initialEntries: ['/initial'], initialIndex: 0 }),
      getContext: () => ({ message: 'Hello' }),
      routes: [{ path: ':whatever' }]
    })
  })

  test('context chain', () => {
    expect(router.store.state.root.value.getContext()).toEqual({ message: 'Hello' })
  })

  test('reaction to push navigation', async () => {
    const changes = []
    await router.start()

    autorun(() => changes.push(router.store.location.pathname))

    router.push('/foo')
    await router.push('/bar')

    expect(router.store.location.pathname).toEqual('/bar/')

    await router.goBack()

    expect(router.store.location.pathname).toEqual('/foo/')

    router.push('/bar')
    await router.replace('/quux')

    expect(router.store.location.pathname).toEqual('/quux/')

    expect(changes).toEqual(['/initial/', '/foo/', '/bar/', '/foo/', '/bar/', '/quux/'])

    expect(router.store.routes.map(route => route.node.value.path)).toEqual([
      '',
      ':whatever'
    ])

    router.stop()
  })

  test('multiple navigation', async () => {
    const changes = []
    await router.start()

    autorun(() => changes.push(router.store.location.pathname))

    router.push('/1')
    router.push('/2')
    router.push('/3')
    router.push('/4')
    router.push('/5')
    router.push('/6')
    router.push('/7')
    router.push('/8')
    router.push('/9')
    await router.push('/10')

    expect(changes).toEqual([
      '/initial/',
      '/1/',
      '/2/',
      '/3/',
      '/4/',
      '/5/',
      '/6/',
      '/7/',
      '/8/',
      '/9/',
      '/10/'
    ])

    expect(router.store.routes.map(route => route.node.value.path)).toEqual([
      '',
      ':whatever'
    ])

    router.stop()
  })

  test('callback API', done => {
    const changes = []
    router.start(_router => {
      expect(_router).toBe(router)

      autorun(() => changes.push(_router.store.location.pathname))

      _router.push('/1').then(() => {
        expect(changes).toEqual(['/initial/', '/1/'])

        expect(_router.store.routes.map(route => route.node.value.path)).toEqual([
          '',
          ':whatever'
        ])

        _router.stop()
        done()
      })
    })
  })

  describe('Events', () => {
    test('subscription', async () => {
      const spy = jest.fn()
      const dispose = router.subscribeEvent(spy)
      await router.start()

      await router.push('/bar')

      expect(spy).toHaveBeenCalled()

      expect(spy.mock.calls.map(x => x[0].type)).toEqual(
        expect.arrayContaining([EventTypes.NAVIGATION_START, EventTypes.NAVIGATION_END])
      )

      expect(
        spy.mock.calls.map(x => x[0].navigation && x[0].navigation.to.pathname)
      ).toEqual(expect.arrayContaining(['/initial/', '/bar/']))

      dispose()
    })
  })

  describe('total ordering', () => {
    test('navigation is guaranteed to be processed sequentially and in order', async () => {
      const MAX_DURATION = 50
      const spy = jest.fn(() => {
        return delay(Math.random() * MAX_DURATION)
      })
      const router = install({
        history: createMemoryHistory({ initialEntries: ['/initial'], initialIndex: 0 }),
        getContext: () => ({ message: 'Hello' }),
        routes: [{ path: ':whatever', onTransition: spy }]
      })

      const changes = []
      await router.start()

      autorun(() => changes.push(router.store.location.pathname))

      router.push('/a')
      router.push('/b')
      router.push('/c')
      router.push('/d')

      await delay(MAX_DURATION * 4)

      expect(changes).toEqual(['/initial/', '/a/', '/b/', '/c/', '/d/'])

      router.stop()
    })
  })

  test('catch-all routes', async () => {
    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a'], initialIndex: 0 }),
      getContext: () => ({}),
      routes: [{ path: 'a' }, { path: '**' }]
    })

    const changes = []
    await router.start()

    autorun(() => changes.push(router.store.location.pathname))

    await router.push('/404')

    expect(changes).toEqual(['/a/', '/404/'])

    router.stop()
  })

  test('willResolve hook', async () => {
    const willActivateSpy = jest.fn(() => Promise.resolve())
    const willResolveSpy = jest.fn(() => Promise.resolve())
    const willDeactivateSpy = jest.fn(() => Promise.resolve())

    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a/1'], initialIndex: 0 }),
      getContext: () => ({}),
      routes: [
        {
          path: 'a/:id',
          query: ['q'],
          willActivate: willActivateSpy,
          willResolve: willResolveSpy,
          willDeactivate: willDeactivateSpy
        }
      ]
    })

    await router.start()
    await router.push('/a/2')
    await router.push('/a/2?q=hey')
    await router.push('/a/2?q=now')
    await router.push('/a/2?q=now&w=what')

    expect(willActivateSpy.mock.calls.length).toBe(1)
    expect(willResolveSpy.mock.calls.length).toBe(4)
    expect(willDeactivateSpy.mock.calls.length).toBe(0)

    await router.push('/')

    expect(willDeactivateSpy.mock.calls.length).toBe(1)

    router.stop()
  })

  test('middleware', async () => {
    const middleware = (evt): any => {
      if (evt.type === 'CHILDREN_CONFIG_LOAD') {
        return {
          ...evt,
          module: evt.module.routes
        }
      }
      return evt
    }

    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a'], initialIndex: 0 }),
      getContext: () => ({}),
      middleware: Middleware(middleware),
      routes: [
        {
          path: 'a',
          loadChildren: () => {
            return Promise.resolve({
              routes: [{ path: 'b' }]
            })
          }
        }
      ]
    })

    await router.start()
    await router.push('/a/b')

    expect(router.store.location).toEqual(
      expect.objectContaining({
        pathname: '/a/b/'
      })
    )
  })

  test('getContext() is available on hooks', async () => {
    let stores
    const router = install({
      history: createMemoryHistory({ initialEntries: ['/a'], initialIndex: 0 }),
      getContext: () => ({ stores: { a: false, b: 123 } }),
      routes: [
        {
          path: 'a',
          query: ['q'],
          canActivate: (route) => {
            stores = route.context.stores
            return Promise.resolve()
          }
        }
      ]
    })

    await router.start()

    expect(stores.a).toEqual(false)
    expect(stores.b).toEqual(123)
  })
})
<|endoftext|>


<commit_before>import _ from 'underscore';
import loadTemplate from '../utils/loadTemplate';
import BaseVw from './baseVw';

export default class extends BaseVw {
  constructor(options = {}) {
    super(options);

    this._state = {
      curRating: 0,
      maxRating: 5,
      hoverIndex: 0,
      iconClrClass: 'clrTEm',
      numberClrClass: 'clrT2',
      ...options.initialState || {},
    };
  }

  className() {
    return 'ratingStrip';
  }

  events() {
    return {
      'click .js-ratingIcon': 'onClickRatingIcon',
      // 'mouseover .js-ratingIcon': 'onMouseOverRatingIcon',
      // 'mouseout .js-ratingIcon': 'onMouseOutRatingIcon',
    };
  }

  onClickRatingIcon(e) {
    // Important!!! In order to simulate a previous sibling selector in
    // CSS and hover all the previos icons on hover, the icons are
    // displayed in reverse order via flex-direction. This requires index
    // calculations to be computed from the end.
    const totalIcons = this.getState().maxRating;
    this.setState({ curRating: totalIcons - $(e.target).index() });
  }

  // onMouseOverRatingIcon(e) {
  //   this.setState({ hoverIndex: $(e.target).index() + 1 });
  // }

  // onMouseOutRatingIcon(e) {
  //   this.setState({ hoverIndex: 0 });
  // }

  get rating() {
    return this.getState().curRating;
  }

  getState() {
    return this._state;
  }

  setState(state, replace = false, renderOnChange = true) {
    let newState;

    if (replace) {
      this._state = {};
    } else {
      newState = _.extend({}, this._state, state);
    }

    if (renderOnChange && !_.isEqual(this._state, newState)) {
      this._state = newState;
      this.render();
    }

    return this;
  }

  render() {
    loadTemplate('ratingsStrip.html', (t) => {
      this.$el.html(t({
        ...this._state,
      }));
    });

    return this;
  }
}
<commit_msg>added in missing import<commit_after>import $ from 'jquery';
import _ from 'underscore';
import loadTemplate from '../utils/loadTemplate';
import BaseVw from './baseVw';

export default class extends BaseVw {
  constructor(options = {}) {
    super(options);

    this._state = {
      curRating: 0,
      maxRating: 5,
      hoverIndex: 0,
      iconClrClass: 'clrTEm',
      numberClrClass: 'clrT2',
      ...options.initialState || {},
    };
  }

  className() {
    return 'ratingStrip';
  }

  events() {
    return {
      'click .js-ratingIcon': 'onClickRatingIcon',
      // 'mouseover .js-ratingIcon': 'onMouseOverRatingIcon',
      // 'mouseout .js-ratingIcon': 'onMouseOutRatingIcon',
    };
  }

  onClickRatingIcon(e) {
    // Important!!! In order to simulate a previous sibling selector in
    // CSS and hover all the previos icons on hover, the icons are
    // displayed in reverse order via flex-direction. This requires index
    // calculations to be computed from the end.
    const totalIcons = this.getState().maxRating;
    this.setState({ curRating: totalIcons - $(e.target).index() });
  }

  // onMouseOverRatingIcon(e) {
  //   this.setState({ hoverIndex: $(e.target).index() + 1 });
  // }

  // onMouseOutRatingIcon(e) {
  //   this.setState({ hoverIndex: 0 });
  // }

  get rating() {
    return this.getState().curRating;
  }

  getState() {
    return this._state;
  }

  setState(state, replace = false, renderOnChange = true) {
    let newState;

    if (replace) {
      this._state = {};
    } else {
      newState = _.extend({}, this._state, state);
    }

    if (renderOnChange && !_.isEqual(this._state, newState)) {
      this._state = newState;
      this.render();
    }

    return this;
  }

  render() {
    loadTemplate('ratingsStrip.html', (t) => {
      this.$el.html(t({
        ...this._state,
      }));
    });

    return this;
  }
}
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'gdot' })
})

router.get('/logout', function(req, res, next) {
  req.logout() // Removes req.user
  res.locals.user = null // It seems we have to do this too. Weird.
  console.log('======= user still here? ========', req.user)
  res.render('index', { title: 'logged out' })
})

module.exports = router;
<commit_msg>Take out debug console.log. Change page title.<commit_after>var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'gdot' })
})

router.get('/logout', function(req, res, next) {
  req.logout() // Removes req.user
  res.locals.user = null // It seems we have to do this too. Weird.
  res.render('index', { title: 'gdot' })
})

module.exports = router;
<|endoftext|>


<commit_before>/*
 * MIT License
 *
 * Copyright (c) 2017-2017 Jan Dockx
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* eslint-env mocha */

const lookupInstance = require('../../lib/lookupInstance')
const ServiceInstance = require('../../lib/ServiceInstance')
const verifyPostconditions = require('../_util/verifyPostconditions')

const nameCompletion = '._tcp.dns-sd-lookup.toryt.org'

const typeName = '_t1i-no-sub' + nameCompletion

const instanceName = 'instance 1.' + typeName

// see ../terraform/cases.tf
const expected = {
  aDetail: 'This is a detail 1',
  host: 'host-of-instance-1.dns-sd-lookup.toryt.org',
  instance: instanceName,
  port: 4141,
  priority: 42,
  txtvers: 44,
  type: typeName,
  weight: 43
}

function mustBeNotFoundError (baseMessage) {
  // noinspection JSUnresolvedVariable
  return error => {
    error.must.be.an.instanceof(Error)
    // noinspection JSUnresolvedVariable
    error.message.must.match(lookupInstance.contract.notValidMessage)
    error.cause.must.be.an.instanceof(Error)
    error.cause.message.must.match(baseMessage)
    console.log(error)
  }
}

describe('lookupInstance', function () {
  this.timeout(6000) // DNS lookups can take a long time on Travis
  verifyPostconditions(lookupInstance)

  it('works in the nominal case', function () {
    const now = new Date()
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.fulfill(response => {
      response.must.be.an.instanceof(ServiceInstance)
      console.log(response)
      response.type.must.equal(expected.type)
      response.instance.must.equal(expected.instance)
      response.host.must.equal(expected.host)
      response.port.must.equal(expected.port)
      response.priority.must.equal(expected.priority)
      response.weight.must.equal(expected.weight)
      // no coercion happens on details
      Object.keys(response.details).forEach(key => response.details[key].must.be.a.string())
      response.details.txtvers.must.be.a.string()
      // noinspection JSCheckFunctionSignatures
      Number.parseInt(response.details.txtvers).must.equal(expected.txtvers)
      response.details['aDetail'.toLowerCase()].must.equal(expected.aDetail)
      const at = new Date(response.details.at)
      at.must.be.before(now)
    })
  })

  it('fails with non-existent instance', function () {
    // noinspection JSUnresolvedVariable
    return lookupInstance('does-not-exist.' + typeName).must.betray(mustBeNotFoundError('ENOTFOUND'))
  })

  it('fails with an instance with 2 TXTs', function () {
    const instanceName = 'instance 2._t2i-2-txt' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(error => {
      error.must.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.message.must.match(lookupInstance.contract.notValidMessage)
      error.cause.must.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.cause.message.must.equal(lookupInstance.contract.moreThen1Message.TXT)
      error.cause.instance.must.equal(instanceName)
      error.cause.count.must.equal(2)
      console.log(error)
    })
  })

  it('fails with an instance with 2 SRVs', function () {
    const instanceName = 'instance 3._t3i-2-srv' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(error => {
      error.must.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.message.must.match(lookupInstance.contract.notValidMessage)
      error.cause.must.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.cause.message.must.equal(lookupInstance.contract.moreThen1Message.SRV)
      error.cause.instance.must.equal(instanceName)
      error.cause.count.must.equal(2)
      console.log(error)
    })
  })

  it('fails with an instance with 2 TXTs and 2 SRVs', function () {
    const instanceName = 'instance 4._t4i-2-txt-srv' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(error => {
      error.must.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.message.must.match(lookupInstance.contract.notValidMessage)
      error.cause.must.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.cause.message.must.match(
        new RegExp(lookupInstance.contract.moreThen1Message.TXT + '|' + lookupInstance.contract.moreThen1Message.SRV)
      )
      error.cause.instance.must.equal(instanceName)
      error.cause.count.must.equal(2)
      console.log(error)
    })
  })

  it('fails with an instance without a TXT', function () {
    const instanceName = 'instance 5._t5i-no-txt' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(mustBeNotFoundError('ENODATA'))
  })

  it('fails with an instance without a SRV', function () {
    const instanceName = 'instance 6._t6i-no-srv' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(mustBeNotFoundError('ENODATA'))
  })
})
<commit_msg>change 5th test file from must to should, except fullfil and betray<commit_after>/*
 * MIT License
 *
 * Copyright (c) 2017-2017 Jan Dockx
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* eslint-env mocha */

const lookupInstance = require('../../lib/lookupInstance')
const ServiceInstance = require('../../lib/ServiceInstance')
const verifyPostconditions = require('../_util/verifyPostconditions')

const nameCompletion = '._tcp.dns-sd-lookup.toryt.org'

const typeName = '_t1i-no-sub' + nameCompletion

const instanceName = 'instance 1.' + typeName

// see ../terraform/cases.tf
const expected = {
  aDetail: 'This is a detail 1',
  host: 'host-of-instance-1.dns-sd-lookup.toryt.org',
  instance: instanceName,
  port: 4141,
  priority: 42,
  txtvers: 44,
  type: typeName,
  weight: 43
}

function mustBeNotFoundError (baseMessage) {
  // noinspection JSUnresolvedVariable
  return error => {
    error.should.be.an.Error()
    // noinspection JSUnresolvedVariable
    error.message.should.equal(lookupInstance.contract.notValidMessage)
    error.cause.should.be.an.Error()
    error.cause.message.should.containEql(baseMessage)
    console.log(error)
  }
}

describe('lookupInstance', function () {
  this.timeout(6000) // DNS lookups can take a long time on Travis
  verifyPostconditions(lookupInstance)

  it('works in the nominal case', function () {
    const now = new Date()
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.fulfill(response => {
      response.should.be.an.instanceof(ServiceInstance)
      console.log(response)
      response.type.should.equal(expected.type)
      response.instance.should.equal(expected.instance)
      response.host.should.equal(expected.host)
      response.port.should.equal(expected.port)
      response.priority.should.equal(expected.priority)
      response.weight.should.equal(expected.weight)
      // no coercion happens on details
      Object.keys(response.details).forEach(key => response.details[key].should.be.a.String())
      response.details.txtvers.should.be.a.String()
      // noinspection JSCheckFunctionSignatures
      Number.parseInt(response.details.txtvers).should.equal(expected.txtvers)
      response.details['aDetail'.toLowerCase()].should.equal(expected.aDetail)
      const at = new Date(response.details.at)
      at.should.be.before(now)
    })
  })

  it('fails with non-existent instance', function () {
    // noinspection JSUnresolvedVariable
    return lookupInstance('does-not-exist.' + typeName).must.betray(mustBeNotFoundError('ENOTFOUND'))
  })

  it('fails with an instance with 2 TXTs', function () {
    const instanceName = 'instance 2._t2i-2-txt' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(error => {
      error.should.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.message.should.equal(lookupInstance.contract.notValidMessage)
      error.cause.should.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.cause.message.should.equal(lookupInstance.contract.moreThen1Message.TXT)
      error.cause.instance.should.equal(instanceName)
      error.cause.count.should.equal(2)
      console.log(error)
    })
  })

  it('fails with an instance with 2 SRVs', function () {
    const instanceName = 'instance 3._t3i-2-srv' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(error => {
      error.should.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.message.should.equal(lookupInstance.contract.notValidMessage)
      error.cause.should.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.cause.message.should.equal(lookupInstance.contract.moreThen1Message.SRV)
      error.cause.instance.should.equal(instanceName)
      error.cause.count.should.equal(2)
      console.log(error)
    })
  })

  it('fails with an instance with 2 TXTs and 2 SRVs', function () {
    const instanceName = 'instance 4._t4i-2-txt-srv' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(error => {
      error.should.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.message.should.equal(lookupInstance.contract.notValidMessage)
      error.cause.should.be.an.instanceof(Error)
      // noinspection JSUnresolvedVariable
      error.cause.message.should.match(
        new RegExp(lookupInstance.contract.moreThen1Message.TXT + '|' + lookupInstance.contract.moreThen1Message.SRV)
      )
      error.cause.instance.should.equal(instanceName)
      error.cause.count.should.equal(2)
      console.log(error)
    })
  })

  it('fails with an instance without a TXT', function () {
    const instanceName = 'instance 5._t5i-no-txt' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(mustBeNotFoundError('ENODATA'))
  })

  it('fails with an instance without a SRV', function () {
    const instanceName = 'instance 6._t6i-no-srv' + nameCompletion
    // noinspection JSUnresolvedVariable
    return lookupInstance(instanceName).must.betray(mustBeNotFoundError('ENODATA'))
  })
})
<|endoftext|>


<commit_before>import {homedir} from 'os';
import path from 'path';
import {rename as fsRename} from 'fs';

import {app, dialog, BrowserWindow, ipcMain, Menu} from 'electron';
import settings from 'electron-settings';
import isDev from 'electron-is-dev';
import mkdirp from 'mkdirp';

import {init as initErrorReporter} from '../common/reporter';
import logger from '../common/logger';

// import autoUpdater from './auto-updater';
import analytics from './analytics';
import {applicationMenu, cogMenu} from './menus';

const menubar = require('menubar')({
  index: `file://${__dirname}/../renderer/html/index.html`,
  icon: path.join(__dirname, '..', '..', 'static', 'menubarDefaultTemplate.png'),
  width: 320,
  height: 500,
  preloadWindow: true,
  transparent: true,
  resizable: false,
  minWidth: 320
});

let appState = 'initial';
let cropperWindow;
const cropperWindowBuffer = 4;
let mainWindowIsDetached = false;
let mainWindow;
let mainWindowIsNew = true;
let positioner;
let shouldStopWhenTrayIsClicked = false;
let tray;

let recording = false;

if (isDev) {
  const electronExecutable = `${__dirname}/../../../node_modules/electron/dist/Electron.app/Contents/MacOS/Electron`; // TODO send a PR
  require('electron-reload')(__dirname, {electron: electronExecutable}); // eslint-disable-line import/newline-after-import
  // menubar.setOption('alwaysOnTop', true);
}

ipcMain.on('set-main-window-size', (event, args) => {
  if (args.width && args.height && mainWindow) {
    [args.width, args.height] = [parseInt(args.width, 10), parseInt(args.height, 10)];
    mainWindow.setSize(args.width, args.height, true); // true == animate
  }
});

ipcMain.on('set-cropper-window-size', (event, args) => {
  if (args.width && args.height && cropperWindow) {
    [args.width, args.height] = [parseInt(args.width, 10), parseInt(args.height, 10)];
    cropperWindow.setSize(args.width + cropperWindowBuffer, args.height + cropperWindowBuffer, true); // true == animate
  }
});

ipcMain.on('show-options-menu', (event, coordinates) => {
  if (coordinates && coordinates.x && coordinates.y) {
    coordinates.x = parseInt(coordinates.x.toFixed(), 10);
    coordinates.y = parseInt(coordinates.y.toFixed(), 10);

    cogMenu.popup(coordinates.x + 4, coordinates.y); // 4 is the magic number ✨
  }
});

function setCropperWindowOnBlur() {
  cropperWindow.on('blur', () => {
    if (!mainWindow.isFocused() &&
        !cropperWindow.webContents.isDevToolsFocused() &&
        !mainWindow.webContents.isDevToolsFocused() &&
        !recording) {
      cropperWindow.close();
    }
  });
}

ipcMain.on('open-cropper-window', (event, size) => {
  mainWindow.setAlwaysOnTop(true); // TODO send a PR to `menubar`
  menubar.setOption('alwaysOnTop', true);
  if (cropperWindow) {
    cropperWindow.focus();
  } else {
    cropperWindow = new BrowserWindow({
      width: size.width + cropperWindowBuffer,
      height: size.height + cropperWindowBuffer,
      frame: false,
      transparent: true,
      resizable: true,
      shadow: false
    });
    cropperWindow.loadURL(`file://${__dirname}/../renderer/html/cropper.html`);
    cropperWindow.setIgnoreMouseEvents(false); // TODO this should be false by default

    if (isDev) {
      cropperWindow.openDevTools({mode: 'detach'});
      cropperWindow.webContents.on('devtools-opened', () => {
        setCropperWindowOnBlur();
      });
    } else {
      setCropperWindowOnBlur();
    }

    cropperWindow.on('closed', () => {
      cropperWindow = undefined;
      mainWindow.webContents.send('cropper-window-closed');
    });

    cropperWindow.on('resize', () => {
      const size = {};
      [size.width, size.height] = cropperWindow.getSize();
      mainWindow.webContents.send('cropper-window-new-size', size);
    });
  }
});

ipcMain.on('close-cropper-window', () => {
  if (cropperWindow && !recording) {
    mainWindow.setAlwaysOnTop(false); // TODO send a PR to `menubar`
    menubar.setOption('alwaysOnTop', false);
    cropperWindow.close(); // TODO: cropperWindow.hide()
  }
});

function resetMainWindowShadow() {
  const size = mainWindow.getSize();
  setTimeout(() => {
    size[1]++;
    mainWindow.setSize(...size, true);
  }, 100);
  setTimeout(() => {
    size[1]--;
    mainWindow.setSize(...size, true);
  }, 110);
}

function resetTrayIcon() {
  appState = 'initial'; // if the icon is being reseted, we are not recording anymore
  shouldStopWhenTrayIsClicked = false;
  tray.setImage(path.join(__dirname, '..', '..', 'static', 'menubarDefaultTemplate.png'));
}

menubar.on('after-create-window', () => {
  let expectedWindowPosition;
  const currentWindowPosition = {};
  mainWindow = menubar.window;
  if (isDev) {
    mainWindow.openDevTools({mode: 'detach'});
  }

  function recomputeExpectedWindowPosition() {
    expectedWindowPosition = positioner.calculate('trayCenter', tray.getBounds());
  }

  function recomputeCurrentWindowPosition() {
    [currentWindowPosition.x, currentWindowPosition.y] = mainWindow.getPosition();
  }

  mainWindow.on('blur', () => {
    if (cropperWindow && !cropperWindow.isFocused() && !recording) {
      // close the cropper window if the main window loses focus and the cropper window
      // is not focused
      cropperWindow.close();
    }

    recomputeExpectedWindowPosition();
    recomputeCurrentWindowPosition();
    if (expectedWindowPosition.x !== currentWindowPosition.x || expectedWindowPosition.y !== currentWindowPosition.y) { // this line is too long
      menubar.setOption('x', currentWindowPosition.x);
      menubar.setOption('y', currentWindowPosition.y);
    } else { // reset the position if the window is back at it's original position
      menubar.setOption('x', undefined);
      menubar.setOption('y', undefined);
    }
  });

  let wasStuck = true;
  mainWindow.on('move', () => { // unfortunately this is just an alias for 'moved'
    recomputeExpectedWindowPosition();
    recomputeCurrentWindowPosition();
    const diff = {
      x: Math.abs(expectedWindowPosition.x - currentWindowPosition.x),
      y: Math.abs(expectedWindowPosition.y - currentWindowPosition.y)
    };

    if (diff.y < 50 && diff.x < 50) {
      if (!wasStuck) {
        mainWindow.webContents.send('stick-to-menubar');
        app.dock.hide();
        resetMainWindowShadow();
        wasStuck = true;
        mainWindowIsDetached = false;
      }
      // the `move` event is called when the user reselases the mouse button
      // because of that, we need to move the window to it's expected position, since the
      // user will never release the mouse in the *right* position (diff.[x, y] === 0)
      tray.setHighlightMode('always');
      positioner.move('trayCenter', tray.getBounds());
    } else if (wasStuck) {
      mainWindow.webContents.send('unstick-from-menubar');
      app.dock.show();
      setTimeout(() => mainWindow.show(), 250);
      setTimeout(() => resetMainWindowShadow(), 100);
      tray.setHighlightMode('never');
      wasStuck = false;
      mainWindowIsDetached = true;
    }
  });

  tray = menubar.tray;
  positioner = menubar.positioner;

  tray.on('click', () => {
    if (mainWindowIsNew) {
      mainWindowIsNew = false;
      positioner.move('trayCenter', tray.getBounds()); // not sure why the fuck this is needed (ﾉಠдಠ)ﾉ︵┻━┻
    }
    if (appState === 'recording' && shouldStopWhenTrayIsClicked) {
      mainWindow.webContents.send('stop-recording');
    } else if (app.dock.isVisible()) {
      mainWindow.show();
    }
  });

  mainWindow.on('hide', () => {
    if (appState === 'recording') {
      tray.setImage(path.join(__dirname, '..', '..', 'static', 'menubarStopTemplate.png'));
      shouldStopWhenTrayIsClicked = true;
    }
  });

  menubar.on('show', () => {
    if (mainWindowIsDetached) {
      tray.setHighlightMode('never');
    }
  });

  app.on('activate', () => { // == dockIcon.onclick
    if (!mainWindow.isVisible()) {
      mainWindow.show();
    }
  });

  mainWindow.once('ready-to-show', () => {
    positioner.move('trayCenter', tray.getBounds()); // not sure why the fuck this is needed (ﾉಠдಠ)ﾉ︵┻━┻
    mainWindow.show();
  });

  mainWindowIsNew = true;
  // autoUpdater.init(mainWindow);
  analytics.init();
  initErrorReporter();
  logger.init(mainWindow);
  Menu.setApplicationMenu(applicationMenu);
});

ipcMain.on('get-cropper-bounds', event => {
  if (cropperWindow) {
    event.returnValue = cropperWindow.getContentBounds();
  }
});

ipcMain.on('is-cropper-active', event => {
  event.returnValue = Boolean(cropperWindow);
});

ipcMain.on('will-start-recording', () => {
  recording = true;
  if (cropperWindow) {
    cropperWindow.setResizable(false);
    cropperWindow.setIgnoreMouseEvents(true);
    cropperWindow.setAlwaysOnTop(true);
  }
});

ipcMain.on('started-recording', () => {
  appState = 'recording';
  if (!mainWindowIsDetached) {
    mainWindow.hide();
    tray.setHighlightMode('never');
  }
});

ipcMain.on('stopped-recording', () => {
  resetTrayIcon();
  analytics.track('recording/finished');
});

ipcMain.on('will-stop-recording', () => {
  recording = false;
  if (cropperWindow) {
    cropperWindow.close();
  }
});

ipcMain.on('hide-main-window', () => {
  mainWindow.hide();
});

ipcMain.on('minimize-main-window', () => {
  mainWindow.minimize();
});

ipcMain.on('move-cropper-window', (event, data) => {
  if (!data.direction || !data.amount) {
    return;
  }

  const position = cropperWindow.getPosition();
  const amount = data.amount;

  switch (data.direction) {
    case 'left':
      position[0] -= amount;
      break;
    case 'up':
      position[1] -= amount;
      break;
    case 'right':
      position[0] += amount;
      break;
    case 'down':
      position[1] += amount;
      break;
    default:
      // Catch occasions where direction is not defined for whatever reason (should never happen).
      break;
  }

  cropperWindow.setPosition(...position);
});

ipcMain.on('ask-user-to-save-file', (event, data) => {
  const kapturesDir = settings.getSync('output-destination') || `${homedir()}/Movies/Kaptures`; // TODO
  const filters = data.type === 'mp4' ? [{name: 'Movies', extensions: ['mp4']}] : [{name: 'Images', extensions: ['gif']}];
  mkdirp(kapturesDir, err => {
    if (err) {
      // can be ignored
    }
    dialog.showSaveDialog({
      title: data.fileName,
      defaultPath: `${kapturesDir}/${data.fileName}`,
      filters
    }, fileName => {
      if (fileName) {
        fsRename(data.filePath, fileName);
      }
      mainWindow.webContents.send('save-dialog-closed');
    });
  });
});
<commit_msg>Re-enable auto updates :fire:<commit_after>import {homedir} from 'os';
import path from 'path';
import {rename as fsRename} from 'fs';

import {app, dialog, BrowserWindow, ipcMain, Menu} from 'electron';
import settings from 'electron-settings';
import isDev from 'electron-is-dev';
import mkdirp from 'mkdirp';

import {init as initErrorReporter} from '../common/reporter';
import logger from '../common/logger';

import autoUpdater from './auto-updater';
import analytics from './analytics';
import {applicationMenu, cogMenu} from './menus';

const menubar = require('menubar')({
  index: `file://${__dirname}/../renderer/html/index.html`,
  icon: path.join(__dirname, '..', '..', 'static', 'menubarDefaultTemplate.png'),
  width: 320,
  height: 500,
  preloadWindow: true,
  transparent: true,
  resizable: false,
  minWidth: 320
});

let appState = 'initial';
let cropperWindow;
const cropperWindowBuffer = 4;
let mainWindowIsDetached = false;
let mainWindow;
let mainWindowIsNew = true;
let positioner;
let shouldStopWhenTrayIsClicked = false;
let tray;

let recording = false;

if (isDev) {
  const electronExecutable = `${__dirname}/../../../node_modules/electron/dist/Electron.app/Contents/MacOS/Electron`; // TODO send a PR
  require('electron-reload')(__dirname, {electron: electronExecutable}); // eslint-disable-line import/newline-after-import
  // menubar.setOption('alwaysOnTop', true);
}

ipcMain.on('set-main-window-size', (event, args) => {
  if (args.width && args.height && mainWindow) {
    [args.width, args.height] = [parseInt(args.width, 10), parseInt(args.height, 10)];
    mainWindow.setSize(args.width, args.height, true); // true == animate
  }
});

ipcMain.on('set-cropper-window-size', (event, args) => {
  if (args.width && args.height && cropperWindow) {
    [args.width, args.height] = [parseInt(args.width, 10), parseInt(args.height, 10)];
    cropperWindow.setSize(args.width + cropperWindowBuffer, args.height + cropperWindowBuffer, true); // true == animate
  }
});

ipcMain.on('show-options-menu', (event, coordinates) => {
  if (coordinates && coordinates.x && coordinates.y) {
    coordinates.x = parseInt(coordinates.x.toFixed(), 10);
    coordinates.y = parseInt(coordinates.y.toFixed(), 10);

    cogMenu.popup(coordinates.x + 4, coordinates.y); // 4 is the magic number ✨
  }
});

function setCropperWindowOnBlur() {
  cropperWindow.on('blur', () => {
    if (!mainWindow.isFocused() &&
        !cropperWindow.webContents.isDevToolsFocused() &&
        !mainWindow.webContents.isDevToolsFocused() &&
        !recording) {
      cropperWindow.close();
    }
  });
}

ipcMain.on('open-cropper-window', (event, size) => {
  mainWindow.setAlwaysOnTop(true); // TODO send a PR to `menubar`
  menubar.setOption('alwaysOnTop', true);
  if (cropperWindow) {
    cropperWindow.focus();
  } else {
    cropperWindow = new BrowserWindow({
      width: size.width + cropperWindowBuffer,
      height: size.height + cropperWindowBuffer,
      frame: false,
      transparent: true,
      resizable: true,
      shadow: false
    });
    cropperWindow.loadURL(`file://${__dirname}/../renderer/html/cropper.html`);
    cropperWindow.setIgnoreMouseEvents(false); // TODO this should be false by default

    if (isDev) {
      cropperWindow.openDevTools({mode: 'detach'});
      cropperWindow.webContents.on('devtools-opened', () => {
        setCropperWindowOnBlur();
      });
    } else {
      setCropperWindowOnBlur();
    }

    cropperWindow.on('closed', () => {
      cropperWindow = undefined;
      mainWindow.webContents.send('cropper-window-closed');
    });

    cropperWindow.on('resize', () => {
      const size = {};
      [size.width, size.height] = cropperWindow.getSize();
      mainWindow.webContents.send('cropper-window-new-size', size);
    });
  }
});

ipcMain.on('close-cropper-window', () => {
  if (cropperWindow && !recording) {
    mainWindow.setAlwaysOnTop(false); // TODO send a PR to `menubar`
    menubar.setOption('alwaysOnTop', false);
    cropperWindow.close(); // TODO: cropperWindow.hide()
  }
});

function resetMainWindowShadow() {
  const size = mainWindow.getSize();
  setTimeout(() => {
    size[1]++;
    mainWindow.setSize(...size, true);
  }, 100);
  setTimeout(() => {
    size[1]--;
    mainWindow.setSize(...size, true);
  }, 110);
}

function resetTrayIcon() {
  appState = 'initial'; // if the icon is being reseted, we are not recording anymore
  shouldStopWhenTrayIsClicked = false;
  tray.setImage(path.join(__dirname, '..', '..', 'static', 'menubarDefaultTemplate.png'));
}

menubar.on('after-create-window', () => {
  let expectedWindowPosition;
  const currentWindowPosition = {};
  mainWindow = menubar.window;
  if (isDev) {
    mainWindow.openDevTools({mode: 'detach'});
  }

  function recomputeExpectedWindowPosition() {
    expectedWindowPosition = positioner.calculate('trayCenter', tray.getBounds());
  }

  function recomputeCurrentWindowPosition() {
    [currentWindowPosition.x, currentWindowPosition.y] = mainWindow.getPosition();
  }

  mainWindow.on('blur', () => {
    if (cropperWindow && !cropperWindow.isFocused() && !recording) {
      // close the cropper window if the main window loses focus and the cropper window
      // is not focused
      cropperWindow.close();
    }

    recomputeExpectedWindowPosition();
    recomputeCurrentWindowPosition();
    if (expectedWindowPosition.x !== currentWindowPosition.x || expectedWindowPosition.y !== currentWindowPosition.y) { // this line is too long
      menubar.setOption('x', currentWindowPosition.x);
      menubar.setOption('y', currentWindowPosition.y);
    } else { // reset the position if the window is back at it's original position
      menubar.setOption('x', undefined);
      menubar.setOption('y', undefined);
    }
  });

  let wasStuck = true;
  mainWindow.on('move', () => { // unfortunately this is just an alias for 'moved'
    recomputeExpectedWindowPosition();
    recomputeCurrentWindowPosition();
    const diff = {
      x: Math.abs(expectedWindowPosition.x - currentWindowPosition.x),
      y: Math.abs(expectedWindowPosition.y - currentWindowPosition.y)
    };

    if (diff.y < 50 && diff.x < 50) {
      if (!wasStuck) {
        mainWindow.webContents.send('stick-to-menubar');
        app.dock.hide();
        resetMainWindowShadow();
        wasStuck = true;
        mainWindowIsDetached = false;
      }
      // the `move` event is called when the user reselases the mouse button
      // because of that, we need to move the window to it's expected position, since the
      // user will never release the mouse in the *right* position (diff.[x, y] === 0)
      tray.setHighlightMode('always');
      positioner.move('trayCenter', tray.getBounds());
    } else if (wasStuck) {
      mainWindow.webContents.send('unstick-from-menubar');
      app.dock.show();
      setTimeout(() => mainWindow.show(), 250);
      setTimeout(() => resetMainWindowShadow(), 100);
      tray.setHighlightMode('never');
      wasStuck = false;
      mainWindowIsDetached = true;
    }
  });

  tray = menubar.tray;
  positioner = menubar.positioner;

  tray.on('click', () => {
    if (mainWindowIsNew) {
      mainWindowIsNew = false;
      positioner.move('trayCenter', tray.getBounds()); // not sure why the fuck this is needed (ﾉಠдಠ)ﾉ︵┻━┻
    }
    if (appState === 'recording' && shouldStopWhenTrayIsClicked) {
      mainWindow.webContents.send('stop-recording');
    } else if (app.dock.isVisible()) {
      mainWindow.show();
    }
  });

  mainWindow.on('hide', () => {
    if (appState === 'recording') {
      tray.setImage(path.join(__dirname, '..', '..', 'static', 'menubarStopTemplate.png'));
      shouldStopWhenTrayIsClicked = true;
    }
  });

  menubar.on('show', () => {
    if (mainWindowIsDetached) {
      tray.setHighlightMode('never');
    }
  });

  app.on('activate', () => { // == dockIcon.onclick
    if (!mainWindow.isVisible()) {
      mainWindow.show();
    }
  });

  mainWindow.once('ready-to-show', () => {
    positioner.move('trayCenter', tray.getBounds()); // not sure why the fuck this is needed (ﾉಠдಠ)ﾉ︵┻━┻
    mainWindow.show();
  });

  mainWindowIsNew = true;
  autoUpdater.init(mainWindow);
  analytics.init();
  initErrorReporter();
  logger.init(mainWindow);
  Menu.setApplicationMenu(applicationMenu);
});

ipcMain.on('get-cropper-bounds', event => {
  if (cropperWindow) {
    event.returnValue = cropperWindow.getContentBounds();
  }
});

ipcMain.on('is-cropper-active', event => {
  event.returnValue = Boolean(cropperWindow);
});

ipcMain.on('will-start-recording', () => {
  recording = true;
  if (cropperWindow) {
    cropperWindow.setResizable(false);
    cropperWindow.setIgnoreMouseEvents(true);
    cropperWindow.setAlwaysOnTop(true);
  }
});

ipcMain.on('started-recording', () => {
  appState = 'recording';
  if (!mainWindowIsDetached) {
    mainWindow.hide();
    tray.setHighlightMode('never');
  }
});

ipcMain.on('stopped-recording', () => {
  resetTrayIcon();
  analytics.track('recording/finished');
});

ipcMain.on('will-stop-recording', () => {
  recording = false;
  if (cropperWindow) {
    cropperWindow.close();
  }
});

ipcMain.on('hide-main-window', () => {
  mainWindow.hide();
});

ipcMain.on('minimize-main-window', () => {
  mainWindow.minimize();
});

ipcMain.on('move-cropper-window', (event, data) => {
  if (!data.direction || !data.amount) {
    return;
  }

  const position = cropperWindow.getPosition();
  const amount = data.amount;

  switch (data.direction) {
    case 'left':
      position[0] -= amount;
      break;
    case 'up':
      position[1] -= amount;
      break;
    case 'right':
      position[0] += amount;
      break;
    case 'down':
      position[1] += amount;
      break;
    default:
      // Catch occasions where direction is not defined for whatever reason (should never happen).
      break;
  }

  cropperWindow.setPosition(...position);
});

ipcMain.on('ask-user-to-save-file', (event, data) => {
  const kapturesDir = settings.getSync('output-destination') || `${homedir()}/Movies/Kaptures`; // TODO
  const filters = data.type === 'mp4' ? [{name: 'Movies', extensions: ['mp4']}] : [{name: 'Images', extensions: ['gif']}];
  mkdirp(kapturesDir, err => {
    if (err) {
      // can be ignored
    }
    dialog.showSaveDialog({
      title: data.fileName,
      defaultPath: `${kapturesDir}/${data.fileName}`,
      filters
    }, fileName => {
      if (fileName) {
        fsRename(data.filePath, fileName);
      }
      mainWindow.webContents.send('save-dialog-closed');
    });
  });
});
<|endoftext|>


<commit_before>/*global require: false, module: false */
'use strict';

var React = require('react');
var Input = require('react-bootstrap/lib/Input');
var Button = require('react-bootstrap/lib/Button');
var SplitButton = require('react-bootstrap/lib/SplitButton');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var {deepPureRenderMixin} = require('../react-utils');

var SampleSearch = React.createClass({
	mixins: [deepPureRenderMixin],
	componentWillReceiveProps: function (newProps) {
		if (this.state.value === this.props.value) {
			this.setState({value: newProps.value});
		}
		// otherwise we have buffered changes to state, and
		// updating from props would revert the user input
		// and move the carat to the end.
	},
	getInitialState: function () {
		return {value: this.props.value};
	},
	onChange: function (ev) {
		var {onChange} = this.props,
			value = ev.target.value;
		this.setState({value});
		onChange(value);
	},
	render: function () {
		var {matches, help, onFilter, onZoom, onCreateColumn, mode} = this.props,
			{value} = this.state,
			noshow = (mode !== "heatmap"),
			filterButton = onFilter ?
					(<span
						 title='Apply as filter'
						 className='glyphicon glyphicon-filter'
						 aria-hidden='true'/>) : null;
		return (
			<form className='form-inline'>
				<Input style={{width: '26em'}}
					type='text'
					value={value}
					title={value}
					placeholder='Samples to highlight. e.g. TCGA-DB-A4XH, missense'
					onChange={this.onChange}
					disabled={noshow}/>
				{` Matching samples: ${matches}`}
				{filterButton ?
					(<SplitButton onClick={onFilter} bsSize='sm' title={filterButton} disabled={noshow}>
						<MenuItem title='Apply to filter' onClick={onFilter}>Filter</MenuItem>
						<MenuItem title='Apply to zoom' onClick={onZoom}>Zoom</MenuItem>
						<MenuItem title='Create column from' onClick={onCreateColumn}>Sort</MenuItem>
					</SplitButton>) : null}
				{help ? <Button bsStyle='link' target='_blank' href={help}>Help with search</Button> : null}
			</form>
		);
	}
});

module.exports = SampleSearch;
<commit_msg>Prevent reload on carriage return.<commit_after>/*global require: false, module: false */
'use strict';

var React = require('react');
var Input = require('react-bootstrap/lib/Input');
var Button = require('react-bootstrap/lib/Button');
var SplitButton = require('react-bootstrap/lib/SplitButton');
var MenuItem = require('react-bootstrap/lib/MenuItem');
var {deepPureRenderMixin} = require('../react-utils');

var SampleSearch = React.createClass({
	mixins: [deepPureRenderMixin],
	componentWillReceiveProps: function (newProps) {
		if (this.state.value === this.props.value) {
			this.setState({value: newProps.value});
		}
		// otherwise we have buffered changes to state, and
		// updating from props would revert the user input
		// and move the carat to the end.
	},
	getInitialState: function () {
		return {value: this.props.value};
	},
	onChange: function (ev) {
		var {onChange} = this.props,
			value = ev.target.value;
		this.setState({value});
		onChange(value);
	},
	render: function () {
		var {matches, help, onFilter, onZoom, onCreateColumn, mode} = this.props,
			{value} = this.state,
			noshow = (mode !== "heatmap"),
			filterButton = onFilter ?
					(<span
						 title='Apply as filter'
						 className='glyphicon glyphicon-filter'
						 aria-hidden='true'/>) : null;
		return (
			<form className='form-inline' onSubmit={ev => ev.preventDefault()}>
				<Input style={{width: '26em'}}
					type='text'
					value={value}
					title={value}
					placeholder='Samples to highlight. e.g. TCGA-DB-A4XH, missense'
					onChange={this.onChange}
					disabled={noshow}/>
				{` Matching samples: ${matches}`}
				{filterButton ?
					(<SplitButton onClick={onFilter} bsSize='sm' title={filterButton} disabled={noshow}>
						<MenuItem title='Apply to filter' onClick={onFilter}>Filter</MenuItem>
						<MenuItem title='Apply to zoom' onClick={onZoom}>Zoom</MenuItem>
						<MenuItem title='Create column from' onClick={onCreateColumn}>Sort</MenuItem>
					</SplitButton>) : null}
				{help ? <Button bsStyle='link' target='_blank' href={help}>Help with search</Button> : null}
			</form>
		);
	}
});

module.exports = SampleSearch;
<|endoftext|>


<commit_before>function getChromeApiMock(mocks) {
    var chrome = {};
    chrome.runtime = {
        sendMessage: jasmine.createSpy("runtime.sendMessage"),
        onMessage: mocks.createEvent(),
        onMessageExternal: mocks.createEvent(),
        connect: jasmine.createSpy("runtime.connect").andCallFake(function () {
            return mocks.createMockPort();
        }),
        getManifest: jasmine.createSpy("runtime.getManifest")
    };
    chrome.devtools = {
        inspectedWindow: {
            "eval": jasmine.createSpy("devtools.inspectedWindow.eval")
        }
    };
    chrome.extension = {
        onConnect: mocks.createEvent(),
        onConnectExternal: mocks.createEvent(),
        connect: jasmine.createSpy("extension.connect").andCallFake(function () {
            return mocks.createMockPort();
        })
    };
    chrome.runtime.onConnectExternal = chrome.extension.onConnectExternal = mocks.createEvent();

    chrome.storage = {};
    chrome.storage.onChanged = mocks.createEvent();
    ['local', 'managed', 'sync'].forEach(function (storageName) {
        var area = chrome.storage[storageName] = {};
        ['set', 'remove', 'clear', 'get', 'getBytesInUse'].forEach(function (methodName) {
            area[methodName] = jasmine.createSpy("storage." + storageName + "." + methodName);
        });
    });

    return chrome;
}

chrome.mocks = {
    createEvent: function () {
        var event = function () {
            var args = [];
            for (var i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }

            event.listeners.forEach(function (listener) {
                listener.apply(null, args);
            });
        };
        event.listeners = [];
        event.addListener = function () {
            event.listeners.push(arguments[0]);
        };
        event.hasListeners = function () {
            return event.listeners.length > 0;
        };
        event.removeListener = function (target) {
            event.listeners.splice(event.listeners.indexOf(target), 1);
        };

        return event;
    },

    createMockPort: function createMockPort(id) {

        return {
            sender: {id: id},
            onMessage: chrome.mocks.createEvent(),
            onDisconnect: chrome.mocks.createEvent(),
            postMessage: jasmine.createSpy("port.postMessage"),
            disconnect: jasmine.createSpy("port.disconnect").andCallFake(function () {
                this.onDisconnect();
            })
        };
    }

};
chrome.mocks.mockChromeApi = getChromeApiMock.bind(null, chrome.mocks);
<commit_msg>tests: fixing broken appContext test<commit_after>function getChromeApiMock(mocks) {
    var chrome = {};
    chrome.runtime = {
        sendMessage: jasmine.createSpy("runtime.sendMessage"),
        onMessage: mocks.createEvent(),
        onMessageExternal: mocks.createEvent(),
        connect: jasmine.createSpy("runtime.connect").andCallFake(function () {
            return mocks.createMockPort();
        }),
        getManifest: jasmine.createSpy("runtime.getManifest").andCallFake(function () {
            return {
                permissions: ["storage"],
                optional_permissions: []
            };
        })
    };
    chrome.devtools = {
        inspectedWindow: {
            "eval": jasmine.createSpy("devtools.inspectedWindow.eval")
        }
    };
    chrome.extension = {
        onConnect: mocks.createEvent(),
        onConnectExternal: mocks.createEvent(),
        connect: jasmine.createSpy("extension.connect").andCallFake(function () {
            return mocks.createMockPort();
        })
    };
    chrome.runtime.onConnectExternal = chrome.extension.onConnectExternal = mocks.createEvent();

    chrome.storage = {};
    chrome.storage.onChanged = mocks.createEvent();
    ['local', 'managed', 'sync'].forEach(function (storageName) {
        var area = chrome.storage[storageName] = {};
        ['set', 'remove', 'clear', 'get', 'getBytesInUse'].forEach(function (methodName) {
            area[methodName] = jasmine.createSpy("storage." + storageName + "." + methodName);
        });
    });

    return chrome;
}

chrome.mocks = {
    createEvent: function () {
        var event = function () {
            var args = [];
            for (var i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }

            event.listeners.forEach(function (listener) {
                listener.apply(null, args);
            });
        };
        event.listeners = [];
        event.addListener = function () {
            event.listeners.push(arguments[0]);
        };
        event.hasListeners = function () {
            return event.listeners.length > 0;
        };
        event.removeListener = function (target) {
            event.listeners.splice(event.listeners.indexOf(target), 1);
        };

        return event;
    },

    createMockPort: function createMockPort(id) {

        return {
            sender: {id: id},
            onMessage: chrome.mocks.createEvent(),
            onDisconnect: chrome.mocks.createEvent(),
            postMessage: jasmine.createSpy("port.postMessage"),
            disconnect: jasmine.createSpy("port.disconnect").andCallFake(function () {
                this.onDisconnect();
            })
        };
    }

};
chrome.mocks.mockChromeApi = getChromeApiMock.bind(null, chrome.mocks);
<|endoftext|>


<commit_before>var app = angular.module('SpfyApp', [])

app.controller('SpfyController', [
    '$scope',
    '$log',
    '$http',
    '$timeout',
    function($scope, $log, $http, $timeout) {

        $scope.loading = false;
        $scope.urlerror = false;

        // for table sort/search
        $scope.sortType     = 'filename'; // set the default sort type
        $scope.sortReverse  = false;  // set the default sort order
        $scope.searchFish   = '';     // set the default search/filter term

        // define form in scope
        $scope.formData={};

        $scope.getResults = function() {

            // get the URL from the input
            var userInput = $scope.myFile;

            // fire the API request
            var fd = new FormData();
            fd.append('file', userInput);
            fd.append('options', formData.options);
            $http.post('/upload', fd, {
                transformRequest: angular.identity,
                headers: {
                    'Content-Type': undefined
                }
            }).success(function(results) {
                $log.log(results);
                $scope.spits = [];
                getSpfySpit(results);
                $scope.loading = true;
                $scope.urlerror = false;
                $scope.message = data.message;
            }).error(function(error) {
                $log.log(error);
            });

        };

        function getSpfySpit(results) {

            var timeout = '';

            var poller = function(key) {
                // fire another request
                if (key !== undefined) {
                    $http.get('/results/' + key).success(function(data, status, headers, config) {
                        if (status === 200) {
                            $log.log(data);
                            $scope.loading = false;
                            $scope.submitButtonText = "Submit";
                            $scope.spits = $scope.spits.concat(data);
                            $log.log($scope.spits)
                            $timeout.cancel(timeout);
                            return false;
                        } else if (status == 202){
                          $scope.loading = true;
                        }
                        // continue to call the poller() function every 2 seconds
                        // until the timeout is cancelled
                        timeout = $timeout(poller(key), 2000);
                    }).error(function(error) {
                        $log.log(error);
                        $scope.loading = false;
                        $scope.submitButtonText = "Submit";
                        $scope.urlerror = true;

                    });
                };

            }

            angular.forEach(results, function(value, key) {
                $log.log(value, key);
                poller(key)
            });

        }

    }
])

app.directive('fileModel', [
    '$parse',
    function($parse) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                var model = $parse(attrs.fileModel);
                var modelSetter = model.assign;

                element.bind('change', function() {
                    scope.$apply(function() {
                        modelSetter(scope, element[0].files[0]);
                    });
                });
            }
        };
    }
]);

app.directive('exportToCsv', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var el = element[0];
            element.bind('click', function(e) {
                var table = e.target.nextElementSibling;
                var csvString = '';
                for (var i = 0; i < table.rows.length; i++) {
                    var rowData = table.rows[i].cells;
                    for (var j = 0; j < rowData.length; j++) {
                        csvString = csvString + rowData[j].textContent.trim() + ",";
                    }
                    csvString = csvString.substring(0, csvString.length - 1);
                    csvString = csvString + "\n";
                }
                csvString = csvString.substring(0, csvString.length - 1);
                var a = $('<a/>', {
                    style: 'display:none',
                    href: 'data:application/octet-stream;base64,' + btoa(csvString),
                    download: 'results.csv'
                }).appendTo('body')
                a[0].click()
                a.remove();
            });
        }
    }
});
<commit_msg>debugging<commit_after>var app = angular.module('SpfyApp', [])

app.controller('SpfyController', [
    '$scope',
    '$log',
    '$http',
    '$timeout',
    function($scope, $log, $http, $timeout) {

        $scope.loading = false;
        $scope.urlerror = false;

        // for table sort/search
        $scope.sortType     = 'filename'; // set the default sort type
        $scope.sortReverse  = false;  // set the default sort order
        $scope.searchFish   = '';     // set the default search/filter term

        // define form in scope
        $scope.formData={};

        $scope.getResults = function() {

            // get the URL from the input
            var userInput = $scope.myFile;

            // fire the API request
            var fd = new FormData();
            fd.append('file', userInput);
            fd.append('options', $scope.formData.options);
            $http.post('/upload', fd, {
                transformRequest: angular.identity,
                headers: {
                    'Content-Type': undefined
                }
            }).success(function(results) {
                $log.log(results);
                $scope.spits = [];
                getSpfySpit(results);
                $scope.loading = true;
                $scope.urlerror = false;
                $scope.message = data.message;
            }).error(function(error) {
                $log.log(error);
            });

        };

        function getSpfySpit(results) {

            var timeout = '';

            var poller = function(key) {
                // fire another request
                if (key !== undefined) {
                    $http.get('/results/' + key).success(function(data, status, headers, config) {
                        if (status === 200) {
                            $log.log(data);
                            $scope.loading = false;
                            $scope.submitButtonText = "Submit";
                            $scope.spits = $scope.spits.concat(data);
                            $log.log($scope.spits)
                            $timeout.cancel(timeout);
                            return false;
                        } else if (status == 202){
                          $scope.loading = true;
                        }
                        // continue to call the poller() function every 2 seconds
                        // until the timeout is cancelled
                        timeout = $timeout(poller(key), 2000);
                    }).error(function(error) {
                        $log.log(error);
                        $scope.loading = false;
                        $scope.submitButtonText = "Submit";
                        $scope.urlerror = true;

                    });
                };

            }

            angular.forEach(results, function(value, key) {
                $log.log(value, key);
                poller(key)
            });

        }

    }
])

app.directive('fileModel', [
    '$parse',
    function($parse) {
        return {
            restrict: 'A',
            link: function(scope, element, attrs) {
                var model = $parse(attrs.fileModel);
                var modelSetter = model.assign;

                element.bind('change', function() {
                    scope.$apply(function() {
                        modelSetter(scope, element[0].files[0]);
                    });
                });
            }
        };
    }
]);

app.directive('exportToCsv', function() {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var el = element[0];
            element.bind('click', function(e) {
                var table = e.target.nextElementSibling;
                var csvString = '';
                for (var i = 0; i < table.rows.length; i++) {
                    var rowData = table.rows[i].cells;
                    for (var j = 0; j < rowData.length; j++) {
                        csvString = csvString + rowData[j].textContent.trim() + ",";
                    }
                    csvString = csvString.substring(0, csvString.length - 1);
                    csvString = csvString + "\n";
                }
                csvString = csvString.substring(0, csvString.length - 1);
                var a = $('<a/>', {
                    style: 'display:none',
                    href: 'data:application/octet-stream;base64,' + btoa(csvString),
                    download: 'results.csv'
                }).appendTo('body')
                a[0].click()
                a.remove();
            });
        }
    }
});
<|endoftext|>


<commit_before>import PropTypes from 'prop-types'
import React from 'react'
import { connect as connectRedux } from 'react-redux'
import { connect as connectFela } from 'react-fela'
import { Field, FieldArray, formValueSelector, reduxForm as connectForm } from 'redux-form'
import { flow } from 'lodash'
import { TextField } from 'redux-form-material-ui'

import styles from '../styles/MemberInvites'
import Button from '../../app/components/Button'

function renderMembers ({ fields, meta: { error, submitFailed }, formProps }) {
  const { memberVals } = formProps
  if (memberVals) {
    const memberKeys = Object.keys(memberVals)
    if (memberKeys.length > 0) {
      const lastMember = memberVals[memberKeys[memberKeys.length - 1]]
      if (Object.keys(lastMember).length > 0) {
        fields.push({})
      }
    }
  }
  return (

      {submitFailed && error && {error}}
      {fields.map((member, index) => (

          <Field
            name={`${member}.name`}
            floatingLabelText='Name'
            component={TextField}
          />
          <Field
            name={`${member}.email`}
            floatingLabelText='Email'
            component={TextField}
          />
          <Field
            name={`${member}.role`}
            floatingLabelText='Role'
            component={TextField}
          />
          <Button type='button' onClick={() => fields.remove(index)}>Remove Member</Button>

      )
    )}
      <Button type='button' onClick={() => fields.push({})}>Add Member</Button>

  )
}

function MemberInvites (props) {
  return (
    <form className={styles.container}>
      <Field
        name='groupName'
        type='text'
        component={TextField}
        floatingLabelText='Group Name'
      />
      <FieldArray name='members' component={renderMembers} formProps={props} />
    </form>
  )
}

const selector = formValueSelector('memberInvites')

export default flow(
  connectFela(styles),
  connectForm({
    form: 'memberInvites'
  }),
  connectRedux(
    state => ({
      memberVals: selector(state, 'members')
    })
  )
)(MemberInvites)
<commit_msg>added TODOs to fix this up a bit<commit_after>import PropTypes from 'prop-types'
import React from 'react'
import { connect as connectRedux } from 'react-redux'
import { connect as connectFela } from 'react-fela'
import { Field, FieldArray, formValueSelector, reduxForm as connectForm } from 'redux-form'
import { flow } from 'lodash'
import { TextField } from 'redux-form-material-ui'

import styles from '../styles/MemberInvites'
import Button from '../../app/components/Button'

function renderMembers ({ fields, meta: { error, submitFailed }, formProps }) {
  const { memberVals } = formProps
  // TODO: currently this is an anti-pattern as it occurs within the render cycle
  // TODO: mikey's idea was to not push state until the first edit to the empty row
  if (memberVals) {
    const memberKeys = Object.keys(memberVals)
    if (memberKeys.length > 0) {
      const lastMember = memberVals[memberKeys[memberKeys.length - 1]]
      if (Object.keys(lastMember).length > 0) {
        fields.push({})
      }
    }
  }
  return (

      {submitFailed && error && {error}}
      {fields.map((member, index) => (

          <Field
            name={`${member}.name`}
            floatingLabelText='Name'
            component={TextField}
          />
          <Field
            name={`${member}.email`}
            floatingLabelText='Email'
            component={TextField}
          />
          <Field
            name={`${member}.role`}
            floatingLabelText='Role'
            component={TextField}
          />
          <Button type='button' onClick={() => fields.remove(index)}>Remove Member</Button>

      )
    )}
      <Button type='button' onClick={() => fields.push({})}>Add Member</Button>

  )
}

function MemberInvites (props) {
  return (
    <form className={styles.container}>
      <Field
        name='groupName'
        type='text'
        component={TextField}
        floatingLabelText='Group Name'
      />
      <FieldArray name='members' component={renderMembers} formProps={props} />
    </form>
  )
}

const selector = formValueSelector('memberInvites')

export default flow(
  connectFela(styles),
  connectForm({
    form: 'memberInvites'
  }),
  connectRedux(
    state => ({
      memberVals: selector(state, 'members')
    })
  )
)(MemberInvites)
<|endoftext|>


<commit_before>(function() {
    var chai = require('chai');
    var sinon = require('sinon');
    chai.use(require('sinon-chai'));
    var expect = chai.expect;
    var NotificationList = require('../src/lib/notification-list');

    describe('notification-list', function() {

        var list;
        var initialObject;

        beforeEach(function() {
            this.brain = {
                get: sinon.stub(),
                set: sinon.spy()
            };
            initialObject = {username: 'init-username', channels: ['#init-channel']};
            this.brain.get.returns([initialObject]);
            list = new NotificationList(this.brain);
        });

        it('gets the initial information from the brain', function() {
            expect(this.brain.get).to.have.been.calledOnce;
            expect(list.notifications).to.be.deep.equal([initialObject]);
            expect(this.brain.set).to.have.not.been.called;
        });

        it('saves when adding a single notification', function() {
            var passed = list.addNotificationsFor('test-username', '#testchannel');

            var expectedObject = {username: 'test-username', channels: ['#testchannel']};
            expect(passed).to.be.ok;
            expect(list.notifications).to.have.length(2);
            expect(list.notifications[0]).to.deep.equal(initialObject);
            expect(list.notifications[1]).to.deep.equal(expectedObject);
            expect(this.brain.set).to.have.been.calledWithExactly('youtubeFeed.notifyFor', [initialObject, expectedObject])
        });

        it('correctly identifies duplicates and doesnt save or add', function() {
            //duplicated
            var passed = list.addNotificationsFor('init-username', '#init-channel');
            expect(passed).to.not.be.ok;
            expect(list.notifications).to.be.deep.equal([initialObject]);
            expect(this.brain.set).to.have.not.been.called;

            //change channel
            passed = list.addNotificationsFor('init-username', '#init-channel-changed');
            var expected = [{
                username: 'init-username',
                channels: ['#init-channel', '#init-channel-changed']
            }];
            expect(passed).to.be.ok;
            expect(list.notifications).to.be.deep.equal(expected);
            expect(this.brain.set).to.have.been.calledOnce;

            //change username
            this.brain.set.reset();
            passed = list.addNotificationsFor('init-username-changed', '#init-channel');
            expected.push({
                username: 'init-username-changed',
                channels: ['#init-channel']
            });
            expect(passed).to.be.ok;
            expect(list.notifications).to.be.deep.equal(expected);
            expect(this.brain.set).to.have.been.calledOnce;
        });

        it('saves when removing a single notification', function() {
            var passed = list.removeNotificationsFor('init-username', '#init-channel');

            expect(passed).to.be.ok;
            expect(list.notifications).to.have.length(0);
            expect(this.brain.set).to.have.been.calledWithExactly('youtubeFeed.notifyFor', [])
        });

        it('doesnt save or remove anything when no match', function() {
            var passed = list.removeNotificationsFor('init-username', '#init-channel-changed');

            expect(passed).to.not.be.ok;
            expect(list.notifications).to.have.length(1);
            expect(this.brain.set).to.have.not.been.called;

            passed = list.removeNotificationsFor('init-username-changed', '#init-channel');

            expect(passed).to.not.be.ok;
            expect(list.notifications).to.have.length(1);
            expect(this.brain.set).to.have.not.been.called;
        });

        it('can check if we are notifying for a username and channel', function() {
            list.addNotificationsFor('test-username-1', '#test-channel-1');
            list.addNotificationsFor('test-username-2', '#test-channel-2');
            list.addNotificationsFor('test-username-3', '#test-channel-3');
            expect(list.notifications).to.have.length(4);

            expect(list.isNotifyingFor('init-username', '#init-channel')).to.be.ok;
            expect(list.isNotifyingFor('init-username', '#init-channel-changed')).to.not.be.ok;
            expect(list.isNotifyingFor('init-username-changed', '#init-channel')).to.not.be.ok;
        });

        it('should set initial data if none already set', function() {
            var customBrain = {
                get: sinon.stub(),
                set: sinon.spy()
            };
            customBrain.get.returns(null);
            list = require('../src/lib/notification-list')(customBrain);

            expect(customBrain.get).to.have.been.calledOnce
            expect(customBrain.set).to.have.been.calledWithExactly('youtubeFeed.notifyFor', []);
        });
    });

}).call(this);
<commit_msg>fix: use constructor properly<commit_after>(function() {
    var chai = require('chai');
    var sinon = require('sinon');
    chai.use(require('sinon-chai'));
    var expect = chai.expect;
    var NotificationList = require('../src/lib/notification-list');

    describe('notification-list', function() {

        var list;
        var initialObject;

        beforeEach(function() {
            this.brain = {
                get: sinon.stub(),
                set: sinon.spy()
            };
            initialObject = {username: 'init-username', channels: ['#init-channel']};
            this.brain.get.returns([initialObject]);
            list = new NotificationList(this.brain);
        });

        it('gets the initial information from the brain', function() {
            expect(this.brain.get).to.have.been.calledOnce;
            expect(list.notifications).to.be.deep.equal([initialObject]);
            expect(this.brain.set).to.have.not.been.called;
        });

        it('saves when adding a single notification', function() {
            var passed = list.addNotificationsFor('test-username', '#testchannel');

            var expectedObject = {username: 'test-username', channels: ['#testchannel']};
            expect(passed).to.be.ok;
            expect(list.notifications).to.have.length(2);
            expect(list.notifications[0]).to.deep.equal(initialObject);
            expect(list.notifications[1]).to.deep.equal(expectedObject);
            expect(this.brain.set).to.have.been.calledWithExactly('youtubeFeed.notifyFor', [initialObject, expectedObject])
        });

        it('correctly identifies duplicates and doesnt save or add', function() {
            //duplicated
            var passed = list.addNotificationsFor('init-username', '#init-channel');
            expect(passed).to.not.be.ok;
            expect(list.notifications).to.be.deep.equal([initialObject]);
            expect(this.brain.set).to.have.not.been.called;

            //change channel
            passed = list.addNotificationsFor('init-username', '#init-channel-changed');
            var expected = [{
                username: 'init-username',
                channels: ['#init-channel', '#init-channel-changed']
            }];
            expect(passed).to.be.ok;
            expect(list.notifications).to.be.deep.equal(expected);
            expect(this.brain.set).to.have.been.calledOnce;

            //change username
            this.brain.set.reset();
            passed = list.addNotificationsFor('init-username-changed', '#init-channel');
            expected.push({
                username: 'init-username-changed',
                channels: ['#init-channel']
            });
            expect(passed).to.be.ok;
            expect(list.notifications).to.be.deep.equal(expected);
            expect(this.brain.set).to.have.been.calledOnce;
        });

        it('saves when removing a single notification', function() {
            var passed = list.removeNotificationsFor('init-username', '#init-channel');

            expect(passed).to.be.ok;
            expect(list.notifications).to.have.length(0);
            expect(this.brain.set).to.have.been.calledWithExactly('youtubeFeed.notifyFor', [])
        });

        it('doesnt save or remove anything when no match', function() {
            var passed = list.removeNotificationsFor('init-username', '#init-channel-changed');

            expect(passed).to.not.be.ok;
            expect(list.notifications).to.have.length(1);
            expect(this.brain.set).to.have.not.been.called;

            passed = list.removeNotificationsFor('init-username-changed', '#init-channel');

            expect(passed).to.not.be.ok;
            expect(list.notifications).to.have.length(1);
            expect(this.brain.set).to.have.not.been.called;
        });

        it('can check if we are notifying for a username and channel', function() {
            list.addNotificationsFor('test-username-1', '#test-channel-1');
            list.addNotificationsFor('test-username-2', '#test-channel-2');
            list.addNotificationsFor('test-username-3', '#test-channel-3');
            expect(list.notifications).to.have.length(4);

            expect(list.isNotifyingFor('init-username', '#init-channel')).to.be.ok;
            expect(list.isNotifyingFor('init-username', '#init-channel-changed')).to.not.be.ok;
            expect(list.isNotifyingFor('init-username-changed', '#init-channel')).to.not.be.ok;
        });

        it('should set initial data if none already set', function() {
            var customBrain = {
                get: sinon.stub(),
                set: sinon.spy()
            };
            customBrain.get.returns(null);
            list = new NotificationList(customBrain);

            expect(customBrain.get).to.have.been.calledOnce
            expect(customBrain.set).to.have.been.calledWithExactly('youtubeFeed.notifyFor', []);
        });
    });

}).call(this);
<|endoftext|>


<commit_before>var __ = require('underscore'),
    Backbone = require('backbone'),
    $ = require('jquery'),
    numberSpinners = require('../utils/numberSpinners'),
    loadTemplate = require('../utils/loadTemplate');
Backbone.$ = $;

module.exports = Backbone.View.extend({

  events: {
    'change .js-buyWizardQuantity': 'changeQuantity'
  },

  initialize: function() {
    "use strict";
    var currentShippingPrice = 0,
        currentShippingBTCPrice = 0,
        recipient = this.model.get('page').profile.handle || this.model.get('page').profile.guid;
    if(this.model.get('vendor_offer').listing.shipping.free !== true) {
      if(this.model.get('userCountry') != this.model.get('vendor_offer').listing.shipping.shipping_origin) {
        currentShippingPrice = this.model.get('internationalShipping');
        currentShippingBTCPrice = this.model.get('internationalShippingBTC');
        this.model.set('shippingType', 'international');
      } else {
        currentShippingPrice = this.model.get('domesticShipping');
        currentShippingBTCPrice = this.model.get('domesticShippingBTC');
        this.model.set('shippingType', 'domestic');
      }
    }

    this.model.set('currentShippingPrice', currentShippingPrice);
    this.model.set('currentShippingBTCPrice', currentShippingBTCPrice);

    this.model.set('currentShippingDisplayPrice', new Intl.NumberFormat(window.lang, {
      style: 'currency',
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
      currency: this.model.get('userCurrencyCode')
    }).format(currentShippingPrice));

    this.model.set('recipient', recipient);

    this.listenTo(this.model, 'change:selectedModerator change:selectedAddress', this.render);
    this.render();
  },

  render: function(){
    var self = this;
    loadTemplate('./js/templates/buyDetails.html', function(loadedTemplate) {
      self.$el.html(loadedTemplate(self.model.toJSON()));
      //this does not add it to the DOM, that is done by the parent view
      self.setQuantity(1);
      numberSpinners(self.$el);
    });
    return this;
  },

  changeQuantity: function(e) {
    "use strict";
    this.setQuantity($(e.target).val());
  },

  setQuantity: function(quantity){
    "use strict";
    var self = this,
        newAttributes = {},
        userCurrency = this.model.get('userCurrencyCode'),
        totalItemPrice = this.model.get('price') * quantity,
        totalShipping = this.model.get('currentShippingPrice') * quantity,
        moderatorPercentage = this.model.get('selectedModerator') ? (this.model.get('selectedModerator').fee).replace("%", "") * 0.01 : 0,
        moderatorPrice = moderatorPercentage ? totalItemPrice * moderatorPercentage : 0,
        moderatorTotal = moderatorPrice * quantity,
        totalPrice = totalItemPrice + totalShipping,
        newDisplayPrice = (userCurrency == "BTC") ? totalItemPrice.toFixed(6) + " BTC" : new Intl.NumberFormat(window.lang, {
          style: 'currency',
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
          currency: userCurrency
        }).format(totalItemPrice),
        newDisplayShippingPrice = (userCurrency == "BTC") ? totalShipping.toFixed(6) + " BTC" : new Intl.NumberFormat(window.lang, {
          style: 'currency',
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
          currency: userCurrency
        }).format(totalShipping),
        newDisplayModeratorPrice = (userCurrency == "BTC") ? moderatorTotal.toFixed(8) + " BTC" : new Intl.NumberFormat(window.lang, {
          style: 'currency',
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
          currency: userCurrency
        }).format(moderatorTotal),
        totalBTCDisplayPrice = (this.model.get('vendorBTCPrice') + this.model.get('currentShippingBTCPrice')) * quantity,
        moderatorPriceBTC = moderatorPercentage ? totalBTCDisplayPrice * moderatorPercentage : 0,
        moderatorPriceString = this.model.get('userCurrencyCode') == 'BTC' ?
                               moderatorPriceBTC.toFixed(8) + " BTC" : moderatorPriceBTC.toFixed(8) + " BTC (" + newDisplayModeratorPrice + ")";

    this.$el.find('.js-buyWizardPrice').html(newDisplayPrice);
    this.$el.find('.js-buyWizardShippingPrice').html(newDisplayShippingPrice);
    this.$('.js-buyWizardModeratorPrice').attr('data-tooltip', moderatorPriceString);
    //this.$el.find('.js-buyWizardModeratorPrice').html(newDisplayModeratorPrice);
    //this.$el.find('.js-buyWizardModeratorBTCPrice').html(moderatorPriceBTC.toFixed(8));
    newAttributes.quantity = quantity;
    newAttributes.totalPrice = totalPrice;
    newAttributes.totalBTCDisplayPrice = totalBTCDisplayPrice;
    this.model.set(newAttributes);
  },

  lockForm: function(){
    "use strict";
    this.$el.find('.js-buyWizardQuantity').prop('disabled', true);
    this.$el.find('#buyWizardQuantity .numberSpinnerUp, #buyWizardQuantity .numberSpinnerDown').addClass('hide');
  },

  close: function(){
    __.each(this.subViews, function(subView) {
      if(subView.close){
        subView.close();
      }else{
        subView.unbind();
        subView.remove();
      }
    });
    this.unbind();
    this.remove();
  }

});<commit_msg>clean up<commit_after>var __ = require('underscore'),
    Backbone = require('backbone'),
    $ = require('jquery'),
    numberSpinners = require('../utils/numberSpinners'),
    loadTemplate = require('../utils/loadTemplate');
Backbone.$ = $;

module.exports = Backbone.View.extend({

  events: {
    'change .js-buyWizardQuantity': 'changeQuantity'
  },

  initialize: function() {
    "use strict";
    var currentShippingPrice = 0,
        currentShippingBTCPrice = 0,
        recipient = this.model.get('page').profile.handle || this.model.get('page').profile.guid;
    if(this.model.get('vendor_offer').listing.shipping.free !== true) {
      if(this.model.get('userCountry') != this.model.get('vendor_offer').listing.shipping.shipping_origin) {
        currentShippingPrice = this.model.get('internationalShipping');
        currentShippingBTCPrice = this.model.get('internationalShippingBTC');
        this.model.set('shippingType', 'international');
      } else {
        currentShippingPrice = this.model.get('domesticShipping');
        currentShippingBTCPrice = this.model.get('domesticShippingBTC');
        this.model.set('shippingType', 'domestic');
      }
    }

    this.model.set('currentShippingPrice', currentShippingPrice);
    this.model.set('currentShippingBTCPrice', currentShippingBTCPrice);

    this.model.set('currentShippingDisplayPrice', new Intl.NumberFormat(window.lang, {
      style: 'currency',
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
      currency: this.model.get('userCurrencyCode')
    }).format(currentShippingPrice));

    this.model.set('recipient', recipient);

    this.listenTo(this.model, 'change:selectedModerator change:selectedAddress', this.render);
    this.render();
  },

  render: function(){
    var self = this;
    loadTemplate('./js/templates/buyDetails.html', function(loadedTemplate) {
      self.$el.html(loadedTemplate(self.model.toJSON()));
      //this does not add it to the DOM, that is done by the parent view
      self.setQuantity(1);
      numberSpinners(self.$el);
    });
    return this;
  },

  changeQuantity: function(e) {
    "use strict";
    this.setQuantity($(e.target).val());
  },

  setQuantity: function(quantity){
    "use strict";
    var self = this,
        newAttributes = {},
        userCurrency = this.model.get('userCurrencyCode'),
        totalItemPrice = this.model.get('price') * quantity,
        totalShipping = this.model.get('currentShippingPrice') * quantity,
        moderatorPercentage = this.model.get('selectedModerator') ? (this.model.get('selectedModerator').fee).replace("%", "") * 0.01 : 0,
        moderatorPrice = moderatorPercentage ? totalItemPrice * moderatorPercentage : 0,
        moderatorTotal = moderatorPrice * quantity,
        totalPrice = totalItemPrice + totalShipping,
        newDisplayPrice = (userCurrency == "BTC") ? totalItemPrice.toFixed(6) + " BTC" : new Intl.NumberFormat(window.lang, {
          style: 'currency',
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
          currency: userCurrency
        }).format(totalItemPrice),
        newDisplayShippingPrice = (userCurrency == "BTC") ? totalShipping.toFixed(6) + " BTC" : new Intl.NumberFormat(window.lang, {
          style: 'currency',
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
          currency: userCurrency
        }).format(totalShipping),
        newDisplayModeratorPrice = (userCurrency == "BTC") ? moderatorTotal.toFixed(8) + " BTC" : new Intl.NumberFormat(window.lang, {
          style: 'currency',
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
          currency: userCurrency
        }).format(moderatorTotal),
        totalBTCDisplayPrice = (this.model.get('vendorBTCPrice') + this.model.get('currentShippingBTCPrice')) * quantity,
        moderatorPriceBTC = moderatorPercentage ? totalBTCDisplayPrice * moderatorPercentage : 0,
        moderatorPriceString = this.model.get('userCurrencyCode') == 'BTC' ?
                               moderatorPriceBTC.toFixed(8) + " BTC" : moderatorPriceBTC.toFixed(8) + " BTC (" + newDisplayModeratorPrice + ")";

    this.$el.find('.js-buyWizardPrice').html(newDisplayPrice);
    this.$el.find('.js-buyWizardShippingPrice').html(newDisplayShippingPrice);
    this.$('.js-buyWizardModeratorPrice').attr('data-tooltip', moderatorPriceString);
    newAttributes.quantity = quantity;
    newAttributes.totalPrice = totalPrice;
    newAttributes.totalBTCDisplayPrice = totalBTCDisplayPrice;
    this.model.set(newAttributes);
  },

  lockForm: function(){
    "use strict";
    this.$el.find('.js-buyWizardQuantity').prop('disabled', true);
    this.$el.find('#buyWizardQuantity .numberSpinnerUp, #buyWizardQuantity .numberSpinnerDown').addClass('hide');
  },

  close: function(){
    __.each(this.subViews, function(subView) {
      if(subView.close){
        subView.close();
      }else{
        subView.unbind();
        subView.remove();
      }
    });
    this.unbind();
    this.remove();
  }

});<|endoftext|>