<commit_before>import 'babel-polyfill'
import React, { Component, PropTypes } from 'react'
import autobind from 'autobind-decorator'
import isNodeInRoot from './nodeInRoot'
import getBoundsForNode from './getBoundsForNode'
import doObjectsCollide from './doObjectsCollide'
import Selectbox from './Selectbox'

class SelectableGroup extends Component {
  static propTypes = {
    scale: PropTypes.number,
    globalMouse: PropTypes.bool,
    whiteList: PropTypes.array,
    scrollSpeed: PropTypes.number,
    minimumSpeedFactor: PropTypes.number,
    allowClickWithoutSelected: PropTypes.bool,
    clickableClassName: PropTypes.string,
    style: PropTypes.object,
    selectionModeClass: PropTypes.string,
    onSelectionClear: PropTypes.func,

    /**
     * Scroll container selector
     */
    scrollContainer: PropTypes.string,

    /**
     * Event that will fire rapidly during selection (while the selector is
     * being dragged). Passes an array of keys.
     */
    duringSelection: PropTypes.func,

    /**
     * Event that will fire when items are selected. Passes an array of keys.
     */
    onSelectionFinish: PropTypes.func,

    /**
     * The component that will represent the Selectable DOM node
     */
    component: PropTypes.node,

    /**
     * Amount of forgiveness an item will offer to the selectbox before registering
     * a selection, i.e. if only 1px of the item is in the selection, it shouldn't be
     * included.
     */
    tolerance: PropTypes.number,

    /**
     * In some cases, it the bounding box may need fixed positioning, if your layout
     * is relying on fixed positioned elements, for instance.
     * @type boolean
     */
    fixedPosition: PropTypes.bool,

    /**
     * When enabled, makes all new selections add to the already selected items,
     * except for selections that contain only previously selected items--in this case
     * it unselects those items.
     */
    dontClearSelection: PropTypes.bool,
  }

  static defaultProps = {
    component: 'div',
    tolerance: 0,
    globalMouse: false,
    whiteList: [],
    scale: 1,
    scrollSpeed: 0.25,
    minimumSpeedFactor: 60,
    duringSelection: () => {},
    onSelectionFinish: () => {},
    onSelectionClear: () => {},
    dontClearSelection: true,
    allowClickWithoutSelected: true,
    selectionModeClass: 'in-selection-mode',
  }

  static childContextTypes = {
    selectable: React.PropTypes.object,
  }

  constructor(props) {
    super(props)
    this.state = { selectionMode: false }

    this.mouseDownStarted = false
    this.mouseMoveStarted = false
    this.mouseUpStarted = false
    this.mouseDownData = null

    this.registry = new Set()
    this.selectedItems = new Set()
    this.selectingItems = new Set()
    this.whiteList = this.props.whiteList.concat(
      ['.selectable-select-all', '.selectable-deselect-all']
    )
  }

  getChildContext() {
    return {
      selectable: {
        register: this.registerSelectable,
        unregister: this.unregisterSelectable,
        selectAll: this.selectAll,
        clearSelection: this.clearSelection,
        getScrolledContainer: () => this.scrollContainer,
      },
    }
  }

  componentDidMount() {
    this.rootNode = this.refs.selectableGroup
    this.scrollContainer = document.querySelector(this.props.scrollContainer) || this.rootNode
    this.initialRootBounds = this.rootNode.getBoundingClientRect()
    this.rootNode.addEventListener('mousedown', this.mouseDown)
    this.rootNode.addEventListener('touchstart', this.mouseDown)
    document.addEventListener('keydown', this.keyListener)
    document.addEventListener('keyup', this.keyListener)
  }

  componentWillUnmount() {
    this.rootNode.removeEventListener('mousedown', this.mouseDown)
    this.rootNode.removeEventListener('touchstart', this.mouseDown)
    document.removeEventListener('keydown', this.keyListener)
    document.removeEventListener('keyup', this.keyListener)
  }

  @autobind
  applyScale(top, left) {
    return {
      scaledTop: top / this.props.scale,
      scaledLeft: left / this.props.scale,
    }
  }

  @autobind
  setScollTop(e) {
    const scrollTop = this.scrollContainer.scrollTop
    this.checkScrollUp(e, scrollTop)
    this.checkScrollDown(e, scrollTop)
  }

  @autobind
  checkScrollUp(e, currentTop) {
    const { minimumSpeedFactor, scrollSpeed } = this.props
    const offset = this.scrollBounds.top - e.clientY

    if (offset > 0 || e.clientY < 0) {
      const newTop = currentTop - (Math.max(offset, minimumSpeedFactor)) * scrollSpeed
      this.scrollContainer.scrollTop = newTop
    }
  }

  @autobind
  checkScrollDown(e, currentTop) {
    const { minimumSpeedFactor, scrollSpeed } = this.props
    const offset = e.clientY - this.scrollBounds.bottom

    if (offset > 0 || e.clientY > window.innerHeight) {
      const newTop = currentTop + (Math.max(offset, minimumSpeedFactor)) * scrollSpeed
      this.scrollContainer.scrollTop = Math.min(newTop, this.maxScroll)
    }
  }

  updateRootBounds() {
    if (this.scrollBounds) {
      this.oldScrollBounds = this.scrollBounds
    }
    this.scrollBounds = this.scrollContainer.getBoundingClientRect()
    this.maxScroll = this.scrollContainer.scrollHeight - this.scrollContainer.clientHeight
  }

  @autobind
  updateRegistry() {
    const containerScroll = {
      scrollTop: this.scrollContainer.scrollTop,
      scrollLeft: this.scrollContainer.scrollLeft,
    }

    for (const selectableItem of this.registry.values()) {
      selectableItem.registerSelectable(containerScroll)
    }
  }

  @autobind
  registerSelectable(selectableItem) {
    this.registry.add(selectableItem)
  }

  @autobind
  unregisterSelectable(selectableItem) {
    this.registry.delete(selectableItem)
  }

  toggleSelectionMode() {
    const { selectedItems, state: { selectionMode } } = this

    if (selectedItems.size && !selectionMode) {
      this.setState({ selectionMode: true })
    }
    if (!selectedItems.size && selectionMode) {
      this.setState({ selectionMode: false })
    }
  }

  @autobind
  openSelectbox(event) {
    const e = this.desktopEventCoords(event)
    this.setScollTop(e)

    if (this.mouseMoveStarted) return
    this.mouseMoveStarted = true

    const scrollTop = this.scrollContainer.scrollTop
    const applyContainerScroll = (top, scroll) => top + scroll / this.props.scale
    const { scaledTop, scaledLeft } = this.applyScale(e.pageY, e.pageX)

    const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)
    const windowScroll = isChrome ? window.scrollY : document.documentElement.scrollTop

    const top = applyContainerScroll(scaledTop - this.scrollBounds.top, scrollTop - windowScroll)
    let boxTop = applyContainerScroll(
      this.mouseDownData.boxTop - this.scrollBounds.top, this.mouseDownData.scrollTop - windowScroll
    )
    const h = boxTop - top
    boxTop = Math.min(boxTop - h, boxTop)

    const w = this.mouseDownData.boxLeft - scaledLeft
    const leftContainerRelative = this.mouseDownData.boxLeft - this.scrollBounds.left
    const boxLeft = Math.min(
      leftContainerRelative - w / this.props.scale, leftContainerRelative / this.props.scale
    )

    this.updatedSelecting()

    this.refs.selectbox.setState({
      isBoxSelecting: true,
      boxWidth: Math.abs(w),
      boxHeight: Math.abs(h),
      boxLeft,
      boxTop,
    }, () => {
      this.mouseMoveStarted = false
    })

    this.props.duringSelection([...this.selectingItems])
  }

  @autobind
  updatedSelecting() {
    const selectbox = this.refs.selectbox.getRef()
    if (!selectbox) return

    const selectboxBounds = getBoundsForNode(selectbox)
    this.selectItems(selectboxBounds)
  }

  @autobind
  selectItems(selectboxBounds, { click } = {}) {
    const { tolerance, dontClearSelection } = this.props
    selectboxBounds.top += this.scrollContainer.scrollTop
    selectboxBounds.left += this.scrollContainer.scrollLeft

    for (const item of this.registry.values()) {
      this.processItem(item, tolerance, dontClearSelection, selectboxBounds, click)
    }
  }

  processItem(item, tolerance, dontClearSelection, selectboxBounds, click) {
    if (this.inWhiteList(item.node)) {
      return null
    }

    const isCollided = doObjectsCollide(selectboxBounds, item.bounds, tolerance)
    const { selecting, selected } = item.state

    if (click && isCollided) {
      if (selected) {
        this.selectedItems.delete(item)
      } else {
        this.selectedItems.add(item)
      }
      item.setState({ selected: !selected })
      return this.clickedItem = item
    }

    if (!click && isCollided && !selecting && !selected) {
      item.setState({ selecting: true })
      return this.selectingItems.add(item)
    }

    if (!click && !isCollided && selecting) {
      if (this.selectingItems.has(item)) {
        item.setState({ selecting: false })
        this.selectingItems.delete(item)
      } else {
        if (!dontClearSelection) {
          item.setState({ selecting: false })
          this.selectedItems.delete(item)
        }
      }
    }

    return null
  }

  @autobind
  clearSelection() {
    for (const item of this.selectedItems.values()) {
      item.setState({ selected: false })
      this.selectedItems.delete(item)
    }
    this.setState({ selectionMode: false })
    this.props.onSelectionFinish([...this.selectedItems])
    this.props.onSelectionClear()
  }

  @autobind
  selectAll() {
    this.updateWhiteListNodes()
    for (const item of this.registry.values()) {
      if (!this.inWhiteList(item.node) && !item.state.selected) {
        item.setState({ selected: true })
        this.selectedItems.add(item)
      }
    }
    this.setState({ selectionMode: true })
    this.props.onSelectionFinish([...this.selectedItems])
  }

  inWhiteList(target) {
    return [...this.whiteListNodes].some(node => (
      target === node || node.contains(target)
    ))
  }

  updateWhiteListNodes() {
    this.whiteListNodes = document.querySelectorAll(this.whiteList.join(', '))
  }

  @autobind
  mouseDown(e) {
    if (this.mouseDownStarted) return
    this.mouseDownStarted = true
    this.mouseUpStarted = false
    e = this.desktopEventCoords(e)

    this.updateWhiteListNodes()
    if (this.inWhiteList(e.target)) {
      this.mouseDownStarted = false
      return
    }

    const node = this.refs.selectableGroup
    if (!this.props.globalMouse && !isNodeInRoot(e.target, node)) {
      const offsetData = getBoundsForNode(node)
      const collides = doObjectsCollide(
        {
          top: offsetData.top,
          left: offsetData.left,
          bottom: offsetData.offsetHeight,
          right: offsetData.offsetWidth,
        },
        {
          top: e.pageY,
          left: e.pageX,
          offsetWidth: 0,
          offsetHeight: 0,
        }
      )
      if (!collides) return
    }

    this.updateRootBounds()
    this.updateRegistry()

    const { scaledTop, scaledLeft } = this.applyScale(e.pageY, e.pageX)
    this.mouseDownData = {
      boxLeft: scaledLeft,
      boxTop: scaledTop,
      scrollTop: this.scrollContainer.scrollTop,
      scrollLeft: this.scrollContainer.scrollLeft,
      target: e.target,
    }

    e.preventDefault()

    document.addEventListener('mousemove', this.openSelectbox)
    document.addEventListener('touchmove', this.openSelectbox)
    document.addEventListener('mouseup', this.mouseUp)
    document.addEventListener('touchend', this.mouseUp)
  }

  preventEvent(target, type) {
    const preventHandler = (e) => {
      target.removeEventListener(type, preventHandler, true)
      e.preventDefault()
      e.stopPropagation()
    }
    target.addEventListener(type, preventHandler, true)
  }

  @autobind
  mouseUp(event) {
    if (this.mouseUpStarted) return

    this.mouseUpStarted = true
    this.mouseDownStarted = false

    document.removeEventListener('mousemove', this.openSelectbox)
    document.removeEventListener('touchmove', this.openSelectbox)
    document.removeEventListener('mouseup', this.mouseUp)
    document.removeEventListener('touchend', this.mouseUp)

    if (!this.mouseDownData) return

    const e = this.desktopEventCoords(event)

    const { scaledTop, scaledLeft } = this.applyScale(e.pageY, e.pageX)
    const { boxTop, boxLeft } = this.mouseDownData
    const isClick = (scaledLeft === boxLeft && scaledTop === boxTop)

    if (isClick && isNodeInRoot(e.target, this.rootNode)) {
      this.handleClick(e, scaledTop, scaledLeft)
    } else {
      for (const item of this.selectingItems.values()) {
        item.setState({ selected: true, selecting: false })
      }
      this.selectedItems = new Set([...this.selectedItems, ...this.selectingItems])
      this.selectingItems.clear()

      if (e.which === 1 && this.mouseDownData.target === e.target) {
        this.preventEvent(e.target, 'click')
      }

      this.refs.selectbox.setState({
        isBoxSelecting: false,
        boxWidth: 0,
        boxHeight: 0,
      })
      this.props.onSelectionFinish([...this.selectedItems])
    }

    this.toggleSelectionMode()
  }

  handleClick(e, top, left) {
    const isMouseUpOnClickElement =
      [...(e.target.classList || [])].indexOf(this.props.clickClassName) > -1

    if (
      this.props.allowClickWithoutSelected ||
      this.selectedItems.size ||
      isMouseUpOnClickElement ||
      this.ctrlPressed
    ) {
      this.selectItems({ top, left, offsetWidth: 0, offsetHeight: 0 }, { click: true })
      this.props.onSelectionFinish([...this.selectedItems], this.clickedItem)

      if (e.which === 1) {
        this.preventEvent(e.target, 'click')
      }
      if (e.which === 2 || e.which === 3) {
        this.preventEvent(e.target, 'contextmenu')
      }
    }
  }

  @autobind
  keyListener(e) {
    this.ctrlPressed = e.ctrlKey || e.metaKey
    if (this.ctrlPressed) {
      return
    }

    if (e.keyCode === 27) {
      this.clearSelection()
    }
  }

  /**
   * Used to return event object with desktop (non-touch) format of event
   * coordinates, regardless of whether the action is from mobile or desktop.
   */
  desktopEventCoords(e) {
    if (e.pageX === undefined || e.pageY === undefined) { // Touch-device
      if (
        e.targetTouches[0] !== undefined &&
        e.targetTouches[0].pageX !== undefined
      ) { // For touchmove
        e.pageX = e.targetTouches[0].pageX
        e.pageY = e.targetTouches[0].pageY
      } else if (
        e.changedTouches[0] !== undefined &&
        e.changedTouches[0].pageX !== undefined
      ) { // For touchstart
        e.pageX = e.changedTouches[0].pageX
        e.pageY = e.changedTouches[0].pageY
      }
    }
    return e
  }

  render() {
    return (
      <this.props.component
        ref="selectableGroup"
        style={this.props.style}
        className={
          `${this.props.className} ${this.state.selectionMode ? this.props.selectionModeClass : ''}`
        }
      >
        <Selectbox fixedPosition={this.props.fixedPosition} ref="selectbox" />
        {this.props.children}
      </this.props.component>
    )
  }
}

export default SelectableGroup
<commit_msg>Fixed select box position when window scrolled right<commit_after>import 'babel-polyfill'
import React, { Component, PropTypes } from 'react'
import autobind from 'autobind-decorator'
import isNodeInRoot from './nodeInRoot'
import getBoundsForNode from './getBoundsForNode'
import doObjectsCollide from './doObjectsCollide'
import Selectbox from './Selectbox'

class SelectableGroup extends Component {
  static propTypes = {
    scale: PropTypes.number,
    globalMouse: PropTypes.bool,
    whiteList: PropTypes.array,
    scrollSpeed: PropTypes.number,
    minimumSpeedFactor: PropTypes.number,
    allowClickWithoutSelected: PropTypes.bool,
    clickableClassName: PropTypes.string,
    style: PropTypes.object,
    selectionModeClass: PropTypes.string,
    onSelectionClear: PropTypes.func,

    /**
     * Scroll container selector
     */
    scrollContainer: PropTypes.string,

    /**
     * Event that will fire rapidly during selection (while the selector is
     * being dragged). Passes an array of keys.
     */
    duringSelection: PropTypes.func,

    /**
     * Event that will fire when items are selected. Passes an array of keys.
     */
    onSelectionFinish: PropTypes.func,

    /**
     * The component that will represent the Selectable DOM node
     */
    component: PropTypes.node,

    /**
     * Amount of forgiveness an item will offer to the selectbox before registering
     * a selection, i.e. if only 1px of the item is in the selection, it shouldn't be
     * included.
     */
    tolerance: PropTypes.number,

    /**
     * In some cases, it the bounding box may need fixed positioning, if your layout
     * is relying on fixed positioned elements, for instance.
     * @type boolean
     */
    fixedPosition: PropTypes.bool,

    /**
     * When enabled, makes all new selections add to the already selected items,
     * except for selections that contain only previously selected items--in this case
     * it unselects those items.
     */
    dontClearSelection: PropTypes.bool,
  }

  static defaultProps = {
    component: 'div',
    tolerance: 0,
    globalMouse: false,
    whiteList: [],
    scale: 1,
    scrollSpeed: 0.25,
    minimumSpeedFactor: 60,
    duringSelection: () => {},
    onSelectionFinish: () => {},
    onSelectionClear: () => {},
    dontClearSelection: true,
    allowClickWithoutSelected: true,
    selectionModeClass: 'in-selection-mode',
  }

  static childContextTypes = {
    selectable: React.PropTypes.object,
  }

  constructor(props) {
    super(props)
    this.state = { selectionMode: false }

    this.mouseDownStarted = false
    this.mouseMoveStarted = false
    this.mouseUpStarted = false
    this.mouseDownData = null

    this.registry = new Set()
    this.selectedItems = new Set()
    this.selectingItems = new Set()
    this.whiteList = this.props.whiteList.concat(
      ['.selectable-select-all', '.selectable-deselect-all']
    )
  }

  getChildContext() {
    return {
      selectable: {
        register: this.registerSelectable,
        unregister: this.unregisterSelectable,
        selectAll: this.selectAll,
        clearSelection: this.clearSelection,
        getScrolledContainer: () => this.scrollContainer,
      },
    }
  }

  componentDidMount() {
    this.rootNode = this.refs.selectableGroup
    this.scrollContainer = document.querySelector(this.props.scrollContainer) || this.rootNode
    this.initialRootBounds = this.rootNode.getBoundingClientRect()
    this.rootNode.addEventListener('mousedown', this.mouseDown)
    this.rootNode.addEventListener('touchstart', this.mouseDown)
    document.addEventListener('keydown', this.keyListener)
    document.addEventListener('keyup', this.keyListener)
  }

  componentWillUnmount() {
    this.rootNode.removeEventListener('mousedown', this.mouseDown)
    this.rootNode.removeEventListener('touchstart', this.mouseDown)
    document.removeEventListener('keydown', this.keyListener)
    document.removeEventListener('keyup', this.keyListener)
  }

  @autobind
  applyScale(top, left) {
    return {
      scaledTop: top / this.props.scale,
      scaledLeft: left / this.props.scale,
    }
  }

  @autobind
  setScollTop(e) {
    const scrollTop = this.scrollContainer.scrollTop
    this.checkScrollUp(e, scrollTop)
    this.checkScrollDown(e, scrollTop)
  }

  @autobind
  checkScrollUp(e, currentTop) {
    const { minimumSpeedFactor, scrollSpeed } = this.props
    const offset = this.scrollBounds.top - e.clientY

    if (offset > 0 || e.clientY < 0) {
      const newTop = currentTop - (Math.max(offset, minimumSpeedFactor)) * scrollSpeed
      this.scrollContainer.scrollTop = newTop
    }
  }

  @autobind
  checkScrollDown(e, currentTop) {
    const { minimumSpeedFactor, scrollSpeed } = this.props
    const offset = e.clientY - this.scrollBounds.bottom

    if (offset > 0 || e.clientY > window.innerHeight) {
      const newTop = currentTop + (Math.max(offset, minimumSpeedFactor)) * scrollSpeed
      this.scrollContainer.scrollTop = Math.min(newTop, this.maxScroll)
    }
  }

  updateRootBounds() {
    if (this.scrollBounds) {
      this.oldScrollBounds = this.scrollBounds
    }
    this.scrollBounds = this.scrollContainer.getBoundingClientRect()
    this.maxScroll = this.scrollContainer.scrollHeight - this.scrollContainer.clientHeight
  }

  @autobind
  updateRegistry() {
    const containerScroll = {
      scrollTop: this.scrollContainer.scrollTop,
      scrollLeft: this.scrollContainer.scrollLeft,
    }

    for (const selectableItem of this.registry.values()) {
      selectableItem.registerSelectable(containerScroll)
    }
  }

  @autobind
  registerSelectable(selectableItem) {
    this.registry.add(selectableItem)
  }

  @autobind
  unregisterSelectable(selectableItem) {
    this.registry.delete(selectableItem)
  }

  toggleSelectionMode() {
    const { selectedItems, state: { selectionMode } } = this

    if (selectedItems.size && !selectionMode) {
      this.setState({ selectionMode: true })
    }
    if (!selectedItems.size && selectionMode) {
      this.setState({ selectionMode: false })
    }
  }

  @autobind
  openSelectbox(event) {
    const e = this.desktopEventCoords(event)
    this.setScollTop(e)

    if (this.mouseMoveStarted) return
    this.mouseMoveStarted = true

    const scrollTop = this.scrollContainer.scrollTop
    const applyContainerScroll = (value, scroll) => value + scroll / this.props.scale
    const { scaledTop, scaledLeft } = this.applyScale(e.pageY, e.pageX)

    const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor)
    const windowTopScroll = isChrome ? window.scrollY : document.documentElement.scrollTop
    const windowLeftScroll = isChrome ? window.scrollX : document.documentElement.scrollLeft

    const top = applyContainerScroll(scaledTop - this.scrollBounds.top, scrollTop - windowTopScroll)
    let boxTop = applyContainerScroll(
      this.mouseDownData.boxTop - this.scrollBounds.top, this.mouseDownData.scrollTop - windowTopScroll
    )
    const h = boxTop - top
    boxTop = Math.min(boxTop - h, boxTop)
    const w = this.mouseDownData.boxLeft - scaledLeft
    const leftContainerRelative = this.mouseDownData.boxLeft - this.scrollBounds.left
    const boxLeft = applyContainerScroll(
      Math.min(
        leftContainerRelative - w / this.props.scale, leftContainerRelative / this.props.scale
      ), -windowLeftScroll
    )

    this.updatedSelecting()

    this.refs.selectbox.setState({
      isBoxSelecting: true,
      boxWidth: Math.abs(w),
      boxHeight: Math.abs(h),
      boxLeft,
      boxTop,
    }, () => {
      this.mouseMoveStarted = false
    })

    this.props.duringSelection([...this.selectingItems])
  }

  @autobind
  updatedSelecting() {
    const selectbox = this.refs.selectbox.getRef()
    if (!selectbox) return

    const selectboxBounds = getBoundsForNode(selectbox)
    this.selectItems(selectboxBounds)
  }

  @autobind
  selectItems(selectboxBounds, { click } = {}) {
    const { tolerance, dontClearSelection } = this.props
    selectboxBounds.top += this.scrollContainer.scrollTop
    selectboxBounds.left += this.scrollContainer.scrollLeft

    for (const item of this.registry.values()) {
      this.processItem(item, tolerance, dontClearSelection, selectboxBounds, click)
    }
  }

  processItem(item, tolerance, dontClearSelection, selectboxBounds, click) {
    if (this.inWhiteList(item.node)) {
      return null
    }

    const isCollided = doObjectsCollide(selectboxBounds, item.bounds, tolerance)
    const { selecting, selected } = item.state

    if (click && isCollided) {
      if (selected) {
        this.selectedItems.delete(item)
      } else {
        this.selectedItems.add(item)
      }
      item.setState({ selected: !selected })
      return this.clickedItem = item
    }

    if (!click && isCollided && !selecting && !selected) {
      item.setState({ selecting: true })
      return this.selectingItems.add(item)
    }

    if (!click && !isCollided && selecting) {
      if (this.selectingItems.has(item)) {
        item.setState({ selecting: false })
        this.selectingItems.delete(item)
      } else {
        if (!dontClearSelection) {
          item.setState({ selecting: false })
          this.selectedItems.delete(item)
        }
      }
    }

    return null
  }

  @autobind
  clearSelection() {
    for (const item of this.selectedItems.values()) {
      item.setState({ selected: false })
      this.selectedItems.delete(item)
    }
    this.setState({ selectionMode: false })
    this.props.onSelectionFinish([...this.selectedItems])
    this.props.onSelectionClear()
  }

  @autobind
  selectAll() {
    this.updateWhiteListNodes()
    for (const item of this.registry.values()) {
      if (!this.inWhiteList(item.node) && !item.state.selected) {
        item.setState({ selected: true })
        this.selectedItems.add(item)
      }
    }
    this.setState({ selectionMode: true })
    this.props.onSelectionFinish([...this.selectedItems])
  }

  inWhiteList(target) {
    return [...this.whiteListNodes].some(node => (
      target === node || node.contains(target)
    ))
  }

  updateWhiteListNodes() {
    this.whiteListNodes = document.querySelectorAll(this.whiteList.join(', '))
  }

  @autobind
  mouseDown(e) {
    if (this.mouseDownStarted) return
    this.mouseDownStarted = true
    this.mouseUpStarted = false
    e = this.desktopEventCoords(e)

    this.updateWhiteListNodes()
    if (this.inWhiteList(e.target)) {
      this.mouseDownStarted = false
      return
    }

    const node = this.refs.selectableGroup
    if (!this.props.globalMouse && !isNodeInRoot(e.target, node)) {
      const offsetData = getBoundsForNode(node)
      const collides = doObjectsCollide(
        {
          top: offsetData.top,
          left: offsetData.left,
          bottom: offsetData.offsetHeight,
          right: offsetData.offsetWidth,
        },
        {
          top: e.pageY,
          left: e.pageX,
          offsetWidth: 0,
          offsetHeight: 0,
        }
      )
      if (!collides) return
    }

    this.updateRootBounds()
    this.updateRegistry()

    const { scaledTop, scaledLeft } = this.applyScale(e.pageY, e.pageX)
    this.mouseDownData = {
      boxLeft: scaledLeft,
      boxTop: scaledTop,
      scrollTop: this.scrollContainer.scrollTop,
      scrollLeft: this.scrollContainer.scrollLeft,
      target: e.target,
    }

    e.preventDefault()

    document.addEventListener('mousemove', this.openSelectbox)
    document.addEventListener('touchmove', this.openSelectbox)
    document.addEventListener('mouseup', this.mouseUp)
    document.addEventListener('touchend', this.mouseUp)
  }

  preventEvent(target, type) {
    const preventHandler = (e) => {
      target.removeEventListener(type, preventHandler, true)
      e.preventDefault()
      e.stopPropagation()
    }
    target.addEventListener(type, preventHandler, true)
  }

  @autobind
  mouseUp(event) {
    if (this.mouseUpStarted) return

    this.mouseUpStarted = true
    this.mouseDownStarted = false

    document.removeEventListener('mousemove', this.openSelectbox)
    document.removeEventListener('touchmove', this.openSelectbox)
    document.removeEventListener('mouseup', this.mouseUp)
    document.removeEventListener('touchend', this.mouseUp)

    if (!this.mouseDownData) return

    const e = this.desktopEventCoords(event)

    const { scaledTop, scaledLeft } = this.applyScale(e.pageY, e.pageX)
    const { boxTop, boxLeft } = this.mouseDownData
    const isClick = (scaledLeft === boxLeft && scaledTop === boxTop)

    if (isClick && isNodeInRoot(e.target, this.rootNode)) {
      this.handleClick(e, scaledTop, scaledLeft)
    } else {
      for (const item of this.selectingItems.values()) {
        item.setState({ selected: true, selecting: false })
      }
      this.selectedItems = new Set([...this.selectedItems, ...this.selectingItems])
      this.selectingItems.clear()

      if (e.which === 1 && this.mouseDownData.target === e.target) {
        this.preventEvent(e.target, 'click')
      }

      this.refs.selectbox.setState({
        isBoxSelecting: false,
        boxWidth: 0,
        boxHeight: 0,
      })
      this.props.onSelectionFinish([...this.selectedItems])
    }

    this.toggleSelectionMode()
  }

  handleClick(e, top, left) {
    const isMouseUpOnClickElement =
      [...(e.target.classList || [])].indexOf(this.props.clickClassName) > -1

    if (
      this.props.allowClickWithoutSelected ||
      this.selectedItems.size ||
      isMouseUpOnClickElement ||
      this.ctrlPressed
    ) {
      this.selectItems({ top, left, offsetWidth: 0, offsetHeight: 0 }, { click: true })
      this.props.onSelectionFinish([...this.selectedItems], this.clickedItem)

      if (e.which === 1) {
        this.preventEvent(e.target, 'click')
      }
      if (e.which === 2 || e.which === 3) {
        this.preventEvent(e.target, 'contextmenu')
      }
    }
  }

  @autobind
  keyListener(e) {
    this.ctrlPressed = e.ctrlKey || e.metaKey
    if (this.ctrlPressed) {
      return
    }

    if (e.keyCode === 27) {
      this.clearSelection()
    }
  }

  /**
   * Used to return event object with desktop (non-touch) format of event
   * coordinates, regardless of whether the action is from mobile or desktop.
   */
  desktopEventCoords(e) {
    if (e.pageX === undefined || e.pageY === undefined) { // Touch-device
      if (
        e.targetTouches[0] !== undefined &&
        e.targetTouches[0].pageX !== undefined
      ) { // For touchmove
        e.pageX = e.targetTouches[0].pageX
        e.pageY = e.targetTouches[0].pageY
      } else if (
        e.changedTouches[0] !== undefined &&
        e.changedTouches[0].pageX !== undefined
      ) { // For touchstart
        e.pageX = e.changedTouches[0].pageX
        e.pageY = e.changedTouches[0].pageY
      }
    }
    return e
  }

  render() {
    return (
      <this.props.component
        ref="selectableGroup"
        style={this.props.style}
        className={
          `${this.props.className} ${this.state.selectionMode ? this.props.selectionModeClass : ''}`
        }
      >
        <Selectbox fixedPosition={this.props.fixedPosition} ref="selectbox" />
        {this.props.children}
      </this.props.component>
    )
  }
}

export default SelectableGroup
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2020 Mickael Jeanroy
 *
 * 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.
 */

'use strict';

const fs = require('fs');
const path = require('path');
const tmp = require('tmp');
const npmUtil = require('../dist/npm-utils');

describe('npm-utils', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = tmp.dirSync({
      unsafeCleanup: true,
    });
  });

  afterEach(() => {
    tmpDir.removeCallback();
  });

  it('should get list of releases', (done) => {
    const promise = npmUtil.releases('bower', '1.7.7');
    expect(promise).toBeDefined();

    promise
        .then((releases) => {
          expect(releases.length).toBeGreaterThan(0);
          expect(releases).toContain('1.7.7');
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });

  it('should get tarball', (done) => {
    const promise = npmUtil.downloadTarball('bower', '1.7.7', tmpDir.name);

    expect(promise).toBeDefined();

    promise
        .then((path) => {
          expect(path.length).toBeGreaterThan(0);
          expect(path).toContain('bower');
          expect(path).toContain('1.7.7');
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });

  it('should not change current working directory', (done) => {
    const cwd = process.cwd();
    const promise = npmUtil.downloadTarball('bower', '1.7.7', tmpDir.name);

    promise
        .then(() => {
          expect(process.cwd()).toBe(cwd);
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });

  it('should get tarball with relative path', (done) => {
    const newTmpDir = tmp.dirSync({
      unsafeCleanup: true,
      tmpdir: path.join(__dirname, '..'),
    });

    const relativeToCwd = path.relative(process.cwd(), newTmpDir.name);
    const expected = path.join(newTmpDir.name, 'bower-1.7.7.tgz');

    const promise = npmUtil.downloadTarball('bower', '1.7.7', relativeToCwd);

    promise
        .then((path) => {
          expect(path).toBe(expected);
          expect(fs.statSync(path).isFile()).toBe(true);
          done();
        })
        .catch((err) => {
          done.fail(err);
        })
        .finally(() => {
          newTmpDir.removeCallback();
        });
  });

  it('should get scoped package tarball', (done) => {
    const expected = path.join(tmpDir.name, 'angular-core-2.0.0.tgz');
    const promise = npmUtil.downloadTarball('@angular/core', '2.0.0', tmpDir.name);

    promise
        .then((path) => {
          expect(path).toBe(expected);
          expect(fs.statSync(path).isFile()).toBe(true);
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });
});
<commit_msg>fix(test): require src instead of dist<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2020 Mickael Jeanroy
 *
 * 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.
 */

'use strict';

const fs = require('fs');
const path = require('path');
const tmp = require('tmp');
const npmUtil = require('../src/npm-utils');

describe('npm-utils', () => {
  let tmpDir;

  beforeEach(() => {
    tmpDir = tmp.dirSync({
      unsafeCleanup: true,
    });
  });

  afterEach(() => {
    tmpDir.removeCallback();
  });

  it('should get list of releases', (done) => {
    const promise = npmUtil.releases('bower', '1.7.7');
    expect(promise).toBeDefined();

    promise
        .then((releases) => {
          expect(releases.length).toBeGreaterThan(0);
          expect(releases).toContain('1.7.7');
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });

  it('should get tarball', (done) => {
    const promise = npmUtil.downloadTarball('bower', '1.7.7', tmpDir.name);

    expect(promise).toBeDefined();

    promise
        .then((path) => {
          expect(path.length).toBeGreaterThan(0);
          expect(path).toContain('bower');
          expect(path).toContain('1.7.7');
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });

  it('should not change current working directory', (done) => {
    const cwd = process.cwd();
    const promise = npmUtil.downloadTarball('bower', '1.7.7', tmpDir.name);

    promise
        .then(() => {
          expect(process.cwd()).toBe(cwd);
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });

  it('should get tarball with relative path', (done) => {
    const newTmpDir = tmp.dirSync({
      unsafeCleanup: true,
      tmpdir: path.join(__dirname, '..'),
    });

    const relativeToCwd = path.relative(process.cwd(), newTmpDir.name);
    const expected = path.join(newTmpDir.name, 'bower-1.7.7.tgz');

    const promise = npmUtil.downloadTarball('bower', '1.7.7', relativeToCwd);

    promise
        .then((path) => {
          expect(path).toBe(expected);
          expect(fs.statSync(path).isFile()).toBe(true);
          done();
        })
        .catch((err) => {
          done.fail(err);
        })
        .finally(() => {
          newTmpDir.removeCallback();
        });
  });

  it('should get scoped package tarball', (done) => {
    const expected = path.join(tmpDir.name, 'angular-core-2.0.0.tgz');
    const promise = npmUtil.downloadTarball('@angular/core', '2.0.0', tmpDir.name);

    promise
        .then((path) => {
          expect(path).toBe(expected);
          expect(fs.statSync(path).isFile()).toBe(true);
          done();
        })
        .catch((err) => {
          done.fail(err);
        });
  });
});
<|endoftext|>


<commit_before>maria.SetView.subclass = maria.ElementView.subclass;
<commit_msg>SetView documentation<commit_after>/**

@property maria.SetView.subclass

@description

The same as maria.ElementView.

*/
maria.SetView.subclass = maria.ElementView.subclass;
<|endoftext|>


<commit_before>
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite();

var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter;

var EventEmitter2 = require('../../lib/eventemitter2').EventEmitter2;
var emitter2 = new EventEmitter2;

var EventEmitter3 = require('events').EventEmitter;
var emitter3 = new EventEmitter3;

suite

  .add('EventEmitterHeatUp', function() {

      emitter3.on('test3', function () { 1==1; });
      emitter3.emit('test3');
      emitter3.removeAllListeners('test3');

  })
  .add('EventEmitter', function() {

    emitter.on('test1', function () { 1==1; });
    emitter.emit('test1');
    emitter.removeAllListeners('test1');

  })
  .add('EventEmitter2', function() {

    emitter2.on('test2', function () { 1==1; });
    emitter2.emit('test2');
    emitter2.removeAllListeners('test2');

  })

  .add('EventEmitter2 (wild)', function() {

    emitter2.on('test2.foo', function () { 1==1; });
    emitter2.emit('test2.foo');
    emitter2.removeAllListeners('test2.foo');

  })

  .on('cycle', function(event, bench) {
    console.log(String(bench));
  })
  .on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').pluck('name'));
  })

  .run(true);
<commit_msg>fix: benchmark.js now correctly shows cycle stats. Before it showed 'undefined'<commit_after>
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite();

var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter;

var EventEmitter2 = require('../../lib/eventemitter2').EventEmitter2;
var emitter2 = new EventEmitter2;

var EventEmitter3 = require('events').EventEmitter;
var emitter3 = new EventEmitter3;

suite

  .add('EventEmitterHeatUp', function() {

      emitter3.on('test3', function () { 1==1; });
      emitter3.emit('test3');
      emitter3.removeAllListeners('test3');

  })
  .add('EventEmitter', function() {

    emitter.on('test1', function () { 1==1; });
    emitter.emit('test1');
    emitter.removeAllListeners('test1');

  })
  .add('EventEmitter2', function() {

    emitter2.on('test2', function () { 1==1; });
    emitter2.emit('test2');
    emitter2.removeAllListeners('test2');

  })

  .add('EventEmitter2 (wild)', function() {

    emitter2.on('test2.foo', function () { 1==1; });
    emitter2.emit('test2.foo');
    emitter2.removeAllListeners('test2.foo');

  })

  .on('cycle', function(event, bench) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('\nFastest is ' + this.filter('fastest').pluck('name'));
  })

  .run(true);
<|endoftext|>


<commit_before>var _ = require('underscore'),
    assert = require('assert'),
    build = require('../../lib/build'),
    fs = require('fs'),
    fu = require('../../lib/fileUtil'),
    lib = require('../lib'),
    should = require('should');

describe('stylus plugin', function() {
  describe('mixin', function() {
    it('should include special values from mixins', function(done) {
      var mixins = [
        {
          styles: {
            "pixelDensity": {
              "iphone": [ 1, 2, 3 ],
              "web": [ 1, 2 ]
            },
            "urlSizeLimit": 103,
            "copyFiles": true,
            "useNib": true,
            "includes": [
              "styles/global.styl",
              "styles/1.styl"
            ]
          }
        },
        {
          styles: {
            "pixelDensity": {
              "android": [ 1, 2 ],
              "web": [ 1, 2, 3 ]
            },
            "urlSizeLimit": 104,
            "copyFiles": false,
            "styleRoot": "foo/",
            "includes": [
              "styles/2.styl"
            ]
          }
        }
      ];

      var config = {
        styles: {
          "pixelDensity": {
            "iphone": [ 1, 2 ]
          },
          "useNib": false,
          "includes": [
            "styles/config.styl"
          ]
        }
      };

      lib.mixinExec({}, mixins, config, function(mixins, context) {
        context.config.attributes.styles.should.eql({
          "pixelDensity": {
            "iphone": [ 1, 2 ],
            "web": [ 1, 2, 3 ],
            "android": [ 1, 2 ]
          },
          "useNib": false,
          "includes": [
            "styles/global.styl",
            "styles/1.styl",
            "styles/2.styl",
            "styles/config.styl"
          ],
          "urlSizeLimit": 104,
          "copyFiles": false
        });
        done();
      });
    });
    it('should create styles config if necessary', function(done) {
      var mixin = {
        "styles": {
          "pixelDensity": {
            "iphone": [ 1, 2 ]
          },
          "useNib": true
        }
      };

      lib.mixinExec({}, [], {}, function(mixins, context) {
        mixins.load(context, mixin, function() {
          context.config.attributes.styles.should.eql({
            "pixelDensity": {
              "iphone": [ 1, 2 ]
            },
            "useNib": true
          });
          done();
        });
      });
    });
    it('should update path references', function(done) {
      var mixin = {
        "styles": {
          "includes": ['foo', 'bar'],
          "styleRoot": 'baz'
        }
      };

      lib.mixinExec({}, [], {}, function(mixins, context) {
        mixin.root = 'a/';
        mixins.load(context, mixin, function() {
          context.config.attributes.styles.should.eql({
            "includes": ['a/foo', 'a/bar']
          });
          done();
        });
      });
    });

    it('should lookup files from mixins', function(done) {
      fu.lookupPath('');

      var readFileSync = fs.readFileSync,
          statSync = fs.statSync,
          read = [];
      after(function() {
        fs.readFileSync = readFileSync;
        fs.statSync = statSync;
      });

      fs.readFileSync = function(path) {
        if (path === 'mixinRoot/mixin-import.styl') {
          read.push(path);
          return '@import "foo"\n';
        } else if (/\.styl|png$/.test(path) && !/node_modules[\\\/]stylus/.test(path)) {
          read.push(path);
          return '.test\n  background url("img.png")\n';
        } else {
          return readFileSync.apply(this, arguments);
        }
      };
      fs.statSync = function(path) {
        if (!/\.styl|png$/.test(path) || /node_modules[\\\/]stylus/.test(path)) {
          return statSync.apply(this, arguments);
        } else if (/mixinRoot/.test(path)) {
          if (/stylusRoot/.test(path)) {
            read.push(path);
            throw new Error();
          }
        }
      };

      var mixins = [{
        root: 'mixinRoot/',
        mixins: {
          'stylus': {
            'styles': [
              'file1.styl',
              'file2.styl'
            ]
          }
        },
        'styles': {
          'stylusRoot': 'stylusRoot/',
          'includes': [
            'mixin-import.styl'
          ]
        }
      }];

      var config = {
        'modules': {
          'test': {
            'mixins': [
              {name: 'stylus', overrides: {'file1.styl': 'bar1.styl'}}
            ],
            'styles': [
              'file1.styl',
              'file2.styl'
            ]
          }
        }
      };

      lib.mixinExec(config.modules.test, mixins, config, function(mixins, context) {
        context.mode = 'styles';
        context.fileConfig = {};

        build.loadResources(context, function(err, resources) {
          if (err) {
            throw err;
          }

          build.processResources(resources, context, function(err, resources) {
            if (err) {
              throw err;
            }

            context.moduleResources = resources;

            var stylus = context.plugins.get('stylus');
            stylus.module(context, function(complete) { complete(); }, function() {
              context.loadResource(context.moduleResources[0], function(err, data) {
                if (err) {
                  throw err;
                }

                read.should.eql([
                  'mixinRoot/stylusRoot/mixin-import.styl',
                  'mixinRoot/mixin-import.styl',
                  'mixinRoot/stylusRoot/foo.styl',
                  'mixinRoot/foo.styl',
                  'mixinRoot/stylusRoot/img.png',
                  'mixinRoot/img.png',
                  'bar1.styl',
                  'mixinRoot/stylusRoot/img.png',
                  'mixinRoot/img.png',
                  'mixinRoot/stylusRoot/file2.styl',
                  'mixinRoot/file2.styl',
                  'mixinRoot/stylusRoot/img.png',
                  'mixinRoot/img.png',
                  'file1.styl',
                  'img.png',
                  'file2.styl',
                  'img.png'
                ]);
                done();
              });
            });
          });
        });
      });
    });
  });
});
<commit_msg>Ensure fs mock cleanup<commit_after>var _ = require('underscore'),
    assert = require('assert'),
    build = require('../../lib/build'),
    fs = require('fs'),
    fu = require('../../lib/fileUtil'),
    lib = require('../lib'),
    should = require('should');

describe('stylus plugin', function() {
  var readFileSync = fs.readFileSync,
      statSync = fs.statSync,
      read = [];
  after(function() {
    fs.readFileSync = readFileSync;
    fs.statSync = statSync;
  });

  describe('mixin', function() {
    it('should include special values from mixins', function(done) {
      var mixins = [
        {
          styles: {
            "pixelDensity": {
              "iphone": [ 1, 2, 3 ],
              "web": [ 1, 2 ]
            },
            "urlSizeLimit": 103,
            "copyFiles": true,
            "useNib": true,
            "includes": [
              "styles/global.styl",
              "styles/1.styl"
            ]
          }
        },
        {
          styles: {
            "pixelDensity": {
              "android": [ 1, 2 ],
              "web": [ 1, 2, 3 ]
            },
            "urlSizeLimit": 104,
            "copyFiles": false,
            "styleRoot": "foo/",
            "includes": [
              "styles/2.styl"
            ]
          }
        }
      ];

      var config = {
        styles: {
          "pixelDensity": {
            "iphone": [ 1, 2 ]
          },
          "useNib": false,
          "includes": [
            "styles/config.styl"
          ]
        }
      };

      lib.mixinExec({}, mixins, config, function(mixins, context) {
        context.config.attributes.styles.should.eql({
          "pixelDensity": {
            "iphone": [ 1, 2 ],
            "web": [ 1, 2, 3 ],
            "android": [ 1, 2 ]
          },
          "useNib": false,
          "includes": [
            "styles/global.styl",
            "styles/1.styl",
            "styles/2.styl",
            "styles/config.styl"
          ],
          "urlSizeLimit": 104,
          "copyFiles": false
        });
        done();
      });
    });
    it('should create styles config if necessary', function(done) {
      var mixin = {
        "styles": {
          "pixelDensity": {
            "iphone": [ 1, 2 ]
          },
          "useNib": true
        }
      };

      lib.mixinExec({}, [], {}, function(mixins, context) {
        mixins.load(context, mixin, function() {
          context.config.attributes.styles.should.eql({
            "pixelDensity": {
              "iphone": [ 1, 2 ]
            },
            "useNib": true
          });
          done();
        });
      });
    });
    it('should update path references', function(done) {
      var mixin = {
        "styles": {
          "includes": ['foo', 'bar'],
          "styleRoot": 'baz'
        }
      };

      lib.mixinExec({}, [], {}, function(mixins, context) {
        mixin.root = 'a/';
        mixins.load(context, mixin, function() {
          context.config.attributes.styles.should.eql({
            "includes": ['a/foo', 'a/bar']
          });
          done();
        });
      });
    });

    it('should lookup files from mixins', function(done) {
      fu.lookupPath('');

      fs.readFileSync = function(path) {
        if (path === 'mixinRoot/mixin-import.styl') {
          read.push(path);
          return '@import "foo"\n';
        } else if (/\.styl|png$/.test(path) && !/node_modules[\\\/]stylus/.test(path)) {
          read.push(path);
          return '.test\n  background url("img.png")\n';
        } else {
          return readFileSync.apply(this, arguments);
        }
      };
      fs.statSync = function(path) {
        if (!/\.styl|png$/.test(path) || /node_modules[\\\/]stylus/.test(path)) {
          return statSync.apply(this, arguments);
        } else if (/mixinRoot/.test(path)) {
          if (/stylusRoot/.test(path)) {
            read.push(path);
            throw new Error();
          }
        }
      };

      var mixins = [{
        root: 'mixinRoot/',
        mixins: {
          'stylus': {
            'styles': [
              'file1.styl',
              'file2.styl'
            ]
          }
        },
        'styles': {
          'stylusRoot': 'stylusRoot/',
          'includes': [
            'mixin-import.styl'
          ]
        }
      }];

      var config = {
        'modules': {
          'test': {
            'mixins': [
              {name: 'stylus', overrides: {'file1.styl': 'bar1.styl'}}
            ],
            'styles': [
              'file1.styl',
              'file2.styl'
            ]
          }
        }
      };

      lib.mixinExec(config.modules.test, mixins, config, function(mixins, context) {
        context.mode = 'styles';
        context.fileConfig = {};

        build.loadResources(context, function(err, resources) {
          if (err) {
            throw err;
          }

          build.processResources(resources, context, function(err, resources) {
            if (err) {
              throw err;
            }

            context.moduleResources = resources;

            var stylus = context.plugins.get('stylus');
            stylus.module(context, function(complete) { complete(); }, function() {
              context.loadResource(context.moduleResources[0], function(err, data) {
                if (err) {
                  throw err;
                }

                read.should.eql([
                  'mixinRoot/stylusRoot/mixin-import.styl',
                  'mixinRoot/mixin-import.styl',
                  'mixinRoot/stylusRoot/foo.styl',
                  'mixinRoot/foo.styl',
                  'mixinRoot/stylusRoot/img.png',
                  'mixinRoot/img.png',
                  'bar1.styl',
                  'mixinRoot/stylusRoot/img.png',
                  'mixinRoot/img.png',
                  'mixinRoot/stylusRoot/file2.styl',
                  'mixinRoot/file2.styl',
                  'mixinRoot/stylusRoot/img.png',
                  'mixinRoot/img.png',
                  'file1.styl',
                  'img.png',
                  'file2.styl',
                  'img.png'
                ]);
                done();
              });
            });
          });
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>import Component from 'graflow'
import {isString, isArray, isObject, isDefined, isFunction,
  flatten, mapObject} from './utils'
import Message from './Message'

import snabbdom from 'snabbdom'
import eventlisteners from 'snabbdom/modules/eventlisteners'
import props from 'snabbdom/modules/props'
import klass from 'snabbdom/modules/class'
import h from 'snabbdom/h'

const Event = arg => {
  if(isString(arg)) return [['dom', arg, {}]]
  if(isObject(arg)) {
    return Object.entries(arg).map(([name, value]) => ['dom', name, value])
  }
  return [['dom', 'default', arg]]
}

const getHandlers = (handlers, component) => mapObject(handlers,
  (event, handler) => {
    const next = component
      ? v => component.send(Message('dom', 'event', Event(v)))
      : () => {}

    const newHandler = e => isFunction(handler)
      ? handler(e, next)
      : next(handler)

    return [event, newHandler]
  })

const toSnabbdom = vdom => {
  if (isString(vdom)) return vdom
  if (isArray(vdom)) return flatten(vdom).filter(isDefined).map(toSnabbdom)

  const {tag = 'div', attrs = {}, on = {}, content = [], component} = vdom

  const {class: klass, ...props} = attrs

  const handlers = getHandlers(on, component)

  const hook = vdom.root
    ? {hook: {update:
        (_, vnode) => { component.send(Message('dom', 'node', vnode.elm)) }
      }}
    : {}

  return h(tag,
    {props, class: klass, on: handlers, ...hook},
    toSnabbdom(content))
}

const updateProps = (oldVnode, vnode) => {
  if (vnode.elm.tagName === 'INPUT' && vnode.data.props.value) {
    vnode.elm.value = vnode.data.props.value
  }
}

const liveProps = {create: updateProps, update: updateProps}

const SnabbdomRenderer = (targetId) => {
  const patch = snabbdom.init([eventlisteners, props, klass, liveProps])

  const target = targetId
    ? document.getElementById(targetId)
    : document.body.appendChild(document.createElement('div'))

  let lastVdom = target

  return Component(vdom => {
    const snabbdomVdom = toSnabbdom(vdom)
    patch(lastVdom, snabbdomVdom)
    lastVdom = snabbdomVdom
  })
}

export default SnabbdomRenderer
<commit_msg>Add SVG support to SnabbdomRenderer<commit_after>import Component from 'graflow'
import {isString, isArray, isObject, isDefined, isFunction,
  flatten, mapObject} from './utils'
import Message from './Message'

import snabbdom from 'snabbdom'
import eventlisteners from 'snabbdom/modules/eventlisteners'
import props from 'snabbdom/modules/props'
import attributes from 'snabbdom/modules/attributes'
import klass from 'snabbdom/modules/class'
import h from 'snabbdom/h'

const Event = arg => {
  if(isString(arg)) return [['dom', arg, {}]]
  if(isObject(arg)) {
    return Object.entries(arg).map(([name, value]) => ['dom', name, value])
  }
  return [['dom', 'default', arg]]
}

const getHandlers = (handlers, component) => mapObject(handlers,
  (event, handler) => {
    const next = component
      ? v => component.send(Message('dom', 'event', Event(v)))
      : () => {}

    const newHandler = e => isFunction(handler)
      ? handler(e, next)
      : next(handler)

    return [event, newHandler]
  })

const toSnabbdom = (vdom, svg = false) => {
  if (isString(vdom)) return vdom
  if (isArray(vdom)) return flatten(vdom).filter(isDefined).map(toSnabbdom)

  const {tag = 'div', attrs = {}, on = {}, content = [], component} = vdom

  const {class: klass, ...rest} = attrs

  const handlers = getHandlers(on, component)

  const hook = vdom.root
    ? {hook: {update:
        (_, vnode) => { component.send(Message('dom', 'node', vnode.elm)) }
      }}
    : {}

  const newSvg = svg || tag === 'svg'

  console.log('toSnabbdom',
    tag,
    newSvg,
    {
      [newSvg ? 'attrs' : 'props']: rest
    },
    content
  )

  return h(tag,
    {
      [newSvg ? 'attrs' : 'props']: rest,
      class: klass,
      on: handlers, ...hook
    },
    toSnabbdom(content, newSvg))
}

const updateProps = (oldVnode, vnode) => {
  if (vnode.elm.tagName === 'INPUT' && vnode.data.props.value) {
    vnode.elm.value = vnode.data.props.value
  }
}

const liveProps = {create: updateProps, update: updateProps}

const SnabbdomRenderer = (targetId) => {
  const patch = snabbdom.init([
    eventlisteners,
    props,
    attributes,
    klass,
    liveProps
  ])

  const target = targetId
    ? document.getElementById(targetId)
    : document.body.appendChild(document.createElement('div'))

  let lastVdom = target

  return Component(vdom => {
    const snabbdomVdom = toSnabbdom(vdom)
    patch(lastVdom, snabbdomVdom)
    lastVdom = snabbdomVdom
  })
}

export default SnabbdomRenderer
<|endoftext|>


<commit_before>var net        = require('net'),
    spawn      = require('child_process').spawn,
    fs         = require('fs'),
    temp       = require('temp'),
    dgram      = require('dgram');

var writeconfig = function(text) {
  var info = temp.openSync({ suffix: '-statsdconf.js' });
  fs.writeSync(info.fd, text);
  return info.path;
};

function log() {
  //console.log.apply(console, arguments);
}

var FakeStatsDServer = function() {
  this.port = 9125;
};

FakeStatsDServer.prototype.start = function(cb) {
  this.sock = dgram.createSocket('udp4');

  var self = this;
  this.sock.on('listening', function() {
    log('Fake statsd server listening on', self.port);
    cb();
  });

  this.sock.bind(this.port);
};

FakeStatsDServer.prototype.stop = function(cb) {
  this.sock.close();
  cb();
};

FakeStatsDServer.prototype.collect = function(timeout, cb) {
  var sock = this.sock;

  var messages = [];
  function onmsg(msg) {
    log('Received %s', msg.toString());
    messages.push(msg.toString());
  }

  sock.on('message', onmsg);

  setTimeout(function() {
    sock.removeListener('message', onmsg);
    cb(messages);
  }, timeout);
};

var StatsDClient = function(port, host) {
  this.host = host || '127.0.0.1';
  this.port = port || 8125;
};

StatsDClient.prototype.send = function(data, cb) {
  var buf = new Buffer(data);
  var sock = dgram.createSocket('udp4');
  sock.send(buf, 0, buf.length, this.port, this.host, function(err, bytes) {
    if(err) {
      throw err;
    }
    sock.close();
    cb();
  });
};

var RepeaterServer = function(port, server_port) {
  this.port = port || 8125;
  this.server_port = server_port || 9125;
};

RepeaterServer.prototype.start = function(cb) {
  var config_path = writeconfig(
    "{ "
      + "repeater: [{ host: '127.0.0.1', port: " + this.server_port + "}] "
      + ", repeaterProtocol: 'udp4' "
      + ", server: './servers/udp' "
      + ", dumpMessages: true "
      + ", port: " + this.port + " "
      + ", backends: [ './backends/repeater' ] "
      + "}"
  );
  log('Wrote config file %s', config_path);
  log('Starting repeater listening on', this.port,
      'forwarding to', this.server_port);

  this.server_up = true;
  this.ok_to_die = false;
  var self = this;
  var r = spawn('node', ['stats.js', config_path]);

  r.on('exit', function(code) {
    self.server_up = false;
    if(!self.ok_to_die) {
      console.log('node server unexpectedly quit with code:', code);
      process.exit();
    }
    self.exit_callback();
  });

  r.stderr.on('data', function(data) {
    console.log('stderr: ' + data.toString().replace(/\n$/,''));
  });

  r.stdout.on('data', function (data) {
    if (data.toString().match(/server is listening/)) {
      log('Repeater server is up');
      cb();
    }
  });

  this.repeater = r;
};

RepeaterServer.prototype.stop = function(cb) {
  this.ok_to_die = true;
  if(this.server_up) {
    this.exit_callback = cb;
    this.repeater.kill();
  } else {
    cb();
  }
};

module.exports = {

  setUp: function(cb) {
    this.server = new FakeStatsDServer();
    this.repeater = new RepeaterServer();

    var repeater = this.repeater;
    this.server.start(function() {
      repeater.start(cb);
    });
  },

  tearDown: function(cb) {
    var repeater = this.repeater;
    this.server.stop(function() {
      repeater.stop(cb);
    });
  },

  fake_server_is_working: function(test) {
    test.expect(1);
    var server = this.server;
    var client = new StatsDClient(this.server.port, 'localhost');

    client.send('foobar', function() {
      server.collect(100, function(messages) {
        test.equal(messages[0], 'foobar');
        test.done();
      });
    });
  },

  repeater_works: function(test) {
    test.expect(1);
    var server = this.server;
    var client = new StatsDClient(this.repeater.port, '127.0.0.1');

    client.send('foobar', function() {
      server.collect(200, function(messages) {
        test.equal(messages[0], 'foobar');
        test.done();
      });
    });
  }

};<commit_msg>removing superfluous test of fake statsd backend<commit_after>var net        = require('net'),
    spawn      = require('child_process').spawn,
    fs         = require('fs'),
    temp       = require('temp'),
    dgram      = require('dgram');

var writeconfig = function(text) {
  var info = temp.openSync({ suffix: '-statsdconf.js' });
  fs.writeSync(info.fd, text);
  return info.path;
};

function log() {
  //console.log.apply(console, arguments);
}

var FakeStatsDServer = function() {
  this.port = 9125;
};

FakeStatsDServer.prototype.start = function(cb) {
  this.sock = dgram.createSocket('udp4');

  var self = this;
  this.sock.on('listening', function() {
    log('Fake statsd server listening on', self.port);
    cb();
  });

  this.sock.bind(this.port);
};

FakeStatsDServer.prototype.stop = function(cb) {
  this.sock.close();
  cb();
};

FakeStatsDServer.prototype.collect = function(timeout, cb) {
  var sock = this.sock;

  var messages = [];
  function onmsg(msg) {
    log('Received %s', msg.toString());
    messages.push(msg.toString());
  }

  sock.on('message', onmsg);

  setTimeout(function() {
    sock.removeListener('message', onmsg);
    cb(messages);
  }, timeout);
};

var StatsDClient = function(port, host) {
  this.host = host || '127.0.0.1';
  this.port = port || 8125;
};

StatsDClient.prototype.send = function(data, cb) {
  var buf = new Buffer(data);
  var sock = dgram.createSocket('udp4');
  sock.send(buf, 0, buf.length, this.port, this.host, function(err, bytes) {
    if(err) {
      throw err;
    }
    sock.close();
    cb();
  });
};

var RepeaterServer = function(port, server_port) {
  this.port = port || 8125;
  this.server_port = server_port || 9125;
};

RepeaterServer.prototype.start = function(cb) {
  var config_path = writeconfig(
    "{ "
      + "repeater: [{ host: '127.0.0.1', port: " + this.server_port + "}] "
      + ", repeaterProtocol: 'udp4' "
      + ", server: './servers/udp' "
      + ", dumpMessages: true "
      + ", port: " + this.port + " "
      + ", backends: [ './backends/repeater' ] "
      + "}"
  );
  log('Wrote config file %s', config_path);
  log('Starting repeater listening on', this.port,
      'forwarding to', this.server_port);

  this.server_up = true;
  this.ok_to_die = false;
  var self = this;
  var r = spawn('node', ['stats.js', config_path]);

  r.on('exit', function(code) {
    self.server_up = false;
    if(!self.ok_to_die) {
      console.log('node server unexpectedly quit with code:', code);
      process.exit();
    }
    self.exit_callback();
  });

  r.stderr.on('data', function(data) {
    console.log('stderr: ' + data.toString().replace(/\n$/,''));
  });

  r.stdout.on('data', function (data) {
    if (data.toString().match(/server is listening/)) {
      log('Repeater server is up');
      cb();
    }
  });

  this.repeater = r;
};

RepeaterServer.prototype.stop = function(cb) {
  this.ok_to_die = true;
  if(this.server_up) {
    this.exit_callback = cb;
    this.repeater.kill();
  } else {
    cb();
  }
};

module.exports = {

  setUp: function(cb) {
    this.server = new FakeStatsDServer();
    this.repeater = new RepeaterServer();

    var repeater = this.repeater;
    this.server.start(function() {
      repeater.start(cb);
    });
  },

  tearDown: function(cb) {
    var repeater = this.repeater;
    this.server.stop(function() {
      repeater.stop(cb);
    });
  },

  repeater_works: function(test) {
    test.expect(1);
    var server = this.server;
    var client = new StatsDClient(this.repeater.port, '127.0.0.1');

    client.send('foobar', function() {
      server.collect(100, function(messages) {
        test.equal(messages[0], 'foobar');
        test.done();
      });
    });
  }

};<|endoftext|>


<commit_before>import supertest from "supertest";
import app from "../../app.js";
import ProjectManager from "../../objects/ProjectManager.js";
import fs from "fs";
import sinon from "sinon";
import http from "http";
import { PassThrough } from "stream";
import { expect } from "chai";

const request = supertest(app);

describe("Routes: Cameras", () => {
    try {
        fs.accessSync(`${__dirname}/../../project-scp-files/project.scp`, fs.F_OK);

        it("GET /cameras", done => {
            request.get("/cameras")
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body).to.have.property("object");
                })
                .end(err => done(err));
        });

        it("GET /cameras/:id", done => {
            request.get("/cameras/1")
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body).to.have.property("object");
                })
                .end(err => done(err));
        });

        it("GET /cameras/:id Unknown Camera", done => {
            request.get("/cameras/4444440404004040040404")
                .expect(404)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Camera not found.");
                })
                .end(err => done(err));
        });
    } catch (e) {
        console.log(e);
    } finally {
        // Most Bad Weather Tests Should Pass Regardless of File Presence

        it("GET /cameras No Data", done => {
            ProjectManager.waitForXML(projectManager => {
                const tempData = projectManager.data;
                projectManager.data = null;
                request.get("/cameras")
                    .expect(500)
                    .expect(res => {
                        expect(res.body.message).to.equal("Unable to load cameras");
                        expect(res.body.succeeded).to.be.false;
                    })
                    .end(err => {
                        projectManager.data = tempData;
                        done(err)
                    });
            });
        });

        it("GET /cameras/:id No Data", done => {
            ProjectManager.waitForXML(projectManager => {
                const tempData = projectManager.data;
                projectManager.data = null;
                request.get("/cameras/1")
                    .expect(500)
                    .expect(res => {
                        expect(res.body.message).to.equal("Unable to load cameras");
                        expect(res.body.succeeded).to.be.false;
                    })
                    .end(err => {
                        projectManager.data = tempData;
                        done(err)
                    });
            });
        });

        it("POST /pan No Body", done => {
            request.post("/cameras/pan")
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Request has to contain: direction, speed.");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /pan No Speed", done => {
            request.post("/cameras/pan")
                .send({ direction: "five" })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Request has to contain: direction, speed.");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /pan Not Left Or Right", done => {
            request.post("/cameras/pan")
                .send({ direction: "five", speed: 12 })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Direction has to be left or right! five is not allowed!");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /pan Speed Too High", done => {
            request.post("/cameras/pan")
                .send({ direction: "left", speed: 500 })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Speed has to be between 1 and 49. 500 is not allowed!");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /pan Speed Too Low", done => {
            request.post("/cameras/pan")
                .send({ direction: "left", speed: -500 })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Speed has to be between 1 and 49. -500 is not allowed!");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /pan left", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Success");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/pan")
                .send({ direction: "left", speed: 23})
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Camera moving left at speed 23.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /pan right", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Success");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/pan")
                .send({ direction: "right", speed: 23})
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Camera moving right at speed 23.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /pan Unreachable Camera", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 404;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/pan")
                .send({ direction: "left", speed: 23})
                .expect(404)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Unable to reach camera.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /tilt up", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Success");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/tilt")
                .send({ direction: "up", speed: 23})
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Camera moving up at speed 23.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /tilt down", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Success");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/tilt")
                .send({ direction: "down", speed: 23})
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Camera moving down at speed 23.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /tilt Unreachable Camera", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 404;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/tilt")
                .send({ direction: "down", speed: 23})
                .expect(404)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Unable to reach camera.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /tilt No Body", done => {
            request.post("/cameras/tilt")
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Request has to contain: direction, speed.");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /tilt No Speed", done => {
            request.post("/cameras/tilt")
                .send({ direction: "five" })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Request has to contain: direction, speed.");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /tilt Not Up Or Down", done => {
            request.post("/cameras/tilt")
                .send({ direction: "five", speed: 12 })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Direction has to be up or down! five is not allowed!");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /tilt Speed Too High", done => {
            request.post("/cameras/tilt")
                .send({ direction: "up", speed: 500 })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Speed has to be between 1 and 49. 500 is not allowed!");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /tilt Speed Too Low", done => {
            request.post("/cameras/tilt")
                .send({ direction: "up", speed: -500 })
                .expect(400)
                .expect(res => {
                    expect(res.body.message).to.equal("Speed has to be between 1 and 49. -500 is not allowed!");
                    expect(res.body.succeeded).to.be.false;
                })
                .end(err => done(err));
        });

        it("POST /save", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("S");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/save")
                .send({ preset: 1 })
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Saved preset 1.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /save high ID", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("S");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/save")
                .send({ preset: 15 })
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Saved preset 15.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /save Response Error", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/save")
                .send({ preset: 15 })
                .expect(504)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Error while saving camera position");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /save Unreachable Camera", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 404;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/save")
                .send({ preset: 15 })
                .expect(404)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Unable to reach camera.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /recall", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("S");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/recall")
                .send({ preset: 1 })
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Recalled preset 1.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /recall high ID", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("S");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/recall")
                .send({ preset: 15 })
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Recalled preset 15.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /save Response Error", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/recall")
                .send({ preset: 15 })
                .expect(504)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Error while recalling camera position");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("POST /recall Unreachable Camera", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 404;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.post("/cameras/recall")
                .send({ preset: 15 })
                .expect(404)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Unable to reach camera.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("GET /stop", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Stopped");
            response.statusCode = 200;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.get("/cameras/stop")
                .expect(200)
                .expect(res => {
                    expect(res.body.succeeded).to.be.true;
                    expect(res.body.message).to.equal("Stopped camera");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });

        it("GET /stop Unreachable Camera", done => {
            const get = sinon.stub(http, "get");
            const response = new PassThrough();
            response.write("Failed");
            response.statusCode = 404;
            response.end();

            const req = new PassThrough();
            get.callsArgWith(1, response)
                .returns(req);

            request.get("/cameras/stop")
                .expect(404)
                .expect(res => {
                    expect(res.body.succeeded).to.be.false;
                    expect(res.body.message).to.equal("Unable to reach camera.");
                })
                .end(err => {
                    http.get.restore();
                    done(err);
                });
        });
    }
});
<commit_msg>rmeoved camera tests<commit_after><|endoftext|>


<commit_before>import _ from 'lodash';
import tape from 'tape';
import * as specs from '../specs';

_.forEach(specs, spec => spec());
<commit_msg>style(runners/travis): Stop using lodash just for iterating specs<commit_after>import * as specs from '../specs';

Object.keys(specs).forEach(specName => specs[specName]());
<|endoftext|>