<commit_before>/*global XMLHttpRequest,$,,chai,resemble, html2canvas, Image, XMLSerializer,btoa */
/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "capture" }]*/
/**
 * Does httpGet on url synchronously
 * @param {string} theUrl - url to do GET request on
 * @returns {string} GET Response Text
 */
function httpGet (theUrl) {
  var xmlHttp = new XMLHttpRequest()
  xmlHttp.open('GET', theUrl, false) // false for synchronous request
  xmlHttp.send(null)
  return xmlHttp.responseText
}
/**
 * Convert Svgs to canvas. In hopes of a more accurate rendering using html2canvas
 * @returns {Promise} Promise of all svg conversions
 */
function experimentalSvgCapture () {
  /* eslint-enable no-unused-vars */
  var items = Array.from(document.querySelectorAll('svg'))
  var promises = items.map(function (svg) {
    return new Promise(resolve => {
      var clientWidth = $(svg).width() || $(svg.parentNode).width()
      var clientHeight = $(svg).height() || $(svg.parentNode).height()
      svg.setAttribute('width', clientWidth)
      svg.setAttribute('height', clientWidth)
      var myCanvas = document.createElement('canvas')

      // Get drawing context for the Canvas
      var myCanvasContext = myCanvas.getContext('2d')
        // Load up our image.
      var source = new Image()
      var xml = new XMLSerializer().serializeToString(svg)
      var data = 'data:image/svg+xml;base64,' + btoa(xml)
      source.src = data
        // Render our SVG image to the canvas once it loads.
      /**
       *
       */
      source.onload = function () {
        myCanvas.width = clientWidth
        myCanvas.height = clientHeight
        myCanvas.className = svg.className.baseVal
        myCanvas.id = svg.id
        myCanvasContext.drawImage(source, 0, 0, clientWidth, clientHeight)
        $(svg).replaceWith(myCanvas)
        resolve()
      }
    })
  })
  return Promise.all(promises)
}
/**
 * Creates baseline imagesfor visual regression during standard Ember tests using html2Canvas and ResembleJS
 * @param {string} imageName - Name of the image you wish to save
 * @param {object} options - Options for capture
 * @param {number} [options.width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [options.height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [options.misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} [options.targetElement=ember-testing-container] - DOM element to capture
 * @param {boolean} [options.experimentalSvgs=undefined] - Set to true in order try experimental rendering of svgs using html2canvas
 * @param {object} [options.assert=undefined] - Use only if using qunit
 * @returns {Promise} ResembleJs return value
 */
function capture (imageName, options) {
  options = options || {}
  if (options.misMatchPercentageMargin == null) {
    options.misMatchPercentageMargin = 0.00
  }
  if (options.targetElement == null) {
    options.targetElement = document.getElementById('ember-testing-container')
  }
  var browser = window.ui
  var istargetbrowser = JSON.parse(httpGet('/istargetbrowser?' + $.param(browser)))
  if (istargetbrowser === false) {
    return new Promise(function (resolve) {
      resolve('Does not match target browser')
    })
  }

  $(document.getElementById('ember-testing')).css('zoom', 'initial')
  $(document.getElementById('ember-testing')).css('width', '100%')
  $(document.getElementById('ember-testing')).css('height', '100%')
  $(document.getElementById('ember-testing-container')).css('overflow', 'visible')
  $(document.getElementById('ember-testing-container')).css('position', 'initial')
  var browserDirectory
  if (browser.osversion === undefined) {
    browserDirectory = browser.os + '/' + browser.browser + '/'
  } else {
    browserDirectory = browser.os + '/' + browser.osversion + '/' + browser.browser + '/'
  }

  if (options.height && options.width) {
    $(options.targetElement).css('width', options.width + 'px')
    $(options.targetElement).css('height', options.height + 'px')
    browserDirectory += options.width + 'x' + options.height + '/'
  } else {
    // default mocha window size
    browserDirectory += options.targetElement.clientWidth + 'x' + options.targetElement.clientHeight + '/'
  }
  // resemble.outputSettings({
  //   largeImageThreshold: 0
  // })
  if (window.callPhantom !== undefined) {
    return capturePhantom(imageName, options.width, options.height,
     options.misMatchPercentageMargin, options.targetElement, options.assert, browserDirectory)
  } else {
    if (options.experimentalSvgs === true) {
      return experimentalSvgCapture().then(function () {
        return captureHtml2Canvas(imageName, options.width, options.height,
         options.misMatchPercentageMargin, options.targetElement,
         options.assert, browserDirectory)
      })
    } else {
      return captureHtml2Canvas(imageName, options.width, options.height,
       options.misMatchPercentageMargin, options.targetElement,
       options.assert, browserDirectory)
    }
  }
}
/**
 * Use phantomJS/slimerjs callback to capture Image
 * @param {string} imageName - Name of the image you wish to save
 * @param {number} [width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} targetElement - DOM element to capture
 * @param {object} [assert=undefined] - Use only if using qunit
 * @param {object} [browserDirectory=undefined] - visual acceptance image path based off window.ui (holds browser info) and size of ember-testing-container
 * @returns {Promise} ResembleJs return value
 */
function capturePhantom (imageName, width, height, misMatchPercentageMargin, targetElement, assert, browserDirectory) {
  return new Promise(function (resolve, reject) {
    if (window.callPhantom === undefined) {
      resolve('Not on PhantomJS')
    }
    var image
    if (targetElement.id !== '') {
      image = window.callPhantom({
        id: targetElement.id
      })
    } else {
      var tempId = 'tempVisualAcceptanceId'
      targetElement.id = tempId
      image = window.callPhantom({
        id: targetElement.id
      })
      targetElement.id = ''
    }
    // Get test dummy image

    image = 'data:image/png;base64,' + image
      // console.log(image)
    return utilizeImage(imageName, width, height, misMatchPercentageMargin, targetElement, assert,
     image, browserDirectory,
     resolve, reject)
  })
}
/**
 * Use html2canvas to capture Image
 * @param {string} imageName - Name of the image you wish to save
 * @param {number} [width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} targetElement - DOM element to capture
 * @param {object} [assert=undefined] - Use only if using qunit
 * @param {object} browserDirectory - visual acceptance image path based off window.ui (holds browser info) and size of ember-testing-container
 * @returns {Promise} ResembleJs return value
 */
function captureHtml2Canvas (imageName, width, height, misMatchPercentageMargin, targetElement,
 assert, browserDirectory) {
  return html2canvas(targetElement, {
    timeout: 1000
  }).then(function (canvas) {
    // Get test dummy image
    var image = canvas.toDataURL('image/png')
    return new Promise(function (resolve, reject) {
      return utilizeImage(imageName, width, height, misMatchPercentageMargin, targetElement, assert,
       image, browserDirectory, resolve, reject)
    })
  })
}
/**
 * Use html2canvas to capture Image
 * @param {string} imageName - Name of the image you wish to save
 * @param {number} [width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} targetElement - DOM element to capture
 * @param {object} [assert=undefined] - Use only if using qunit
 * @param {string} image - Base64 image produced from html2canvas or PhantomJS
 * @param {object} browserDirectory - visual acceptance image path based off window.ui (holds browser info) and size of ember-testing-container
 * @param {object} resolve - resolve from Promise
 * @param {object} reject - reject from Promise
 * @returns {Promise} ResembleJs return value
 */
function utilizeImage (imageName, width, height, misMatchPercentageMargin, targetElement, assert,
 image, browserDirectory, resolve, reject) {
  if (!document.getElementById('visual-acceptance') && $('.tabs').length === 0) {
    var visualAcceptanceContainer
    visualAcceptanceContainer = document.createElement('div')
    visualAcceptanceContainer.setAttribute('id', 'visual-acceptance')
    visualAcceptanceContainer.innerHTML = '
 Visual Acceptance tests:
'
    visualAcceptanceContainer.innerHTML += '

'
    document.body.appendChild(visualAcceptanceContainer)
  }
  var node = document.createElement('div')
  var images = []
    // Get passed image
  var res = JSON.parse(httpGet('/image?name=' + encodeURIComponent(browserDirectory + imageName) + '-passed.png'))
  if (res.error === 'File does not exist') {
    // Save image as passed if no existing passed image
    $.ajax({
      type: 'POST',
      async: false,
      url: '/passed',
      data: {
        image: image,
        name: browserDirectory + imageName + '.png'
      }
    })
    $(document.getElementById('ember-testing')).removeAttr('style')
    $(targetElement).removeAttr('style')
    node.innerHTML = '

 No new image. Saving current as baseline: ' +
     imageName + '

 Addition Information:

'
    images.push(image)
    $.ajax({
      type: 'POST',
      async: false,
      url: '/report',
      data: {
        type: 'New',
        images: images,
        name: imageName,
        browser: window.ui.browser
      }
    })
    resolve('No passed image. Saving current test as base')
  } else {
    // Passed image exists so compare to current
    res.image = 'data:image/png;base64,' + res.image
    return new Promise(function (resolve, reject) {
      resemble(res.image).compareTo(image).scaleToSameSize().onComplete(function (data) {
        var result = false
        if (parseFloat(data.misMatchPercentage) <= misMatchPercentageMargin) {
          // Passed
          $.ajax({
            type: 'POST',
            async: false,
            url: '/passed',
            data: {
              image: image,
              name: browserDirectory + imageName + '.png'
            }
          })
          result = true
          node.innerHTML = '

  New: ' + imageName +
           '

 Addition Information:

'
        } else {
          // Fail
          $.ajax({
            type: 'POST',
            async: false,
            url: '/fail',
            data: {
              image: data.getImageDataUrl(),
              name: browserDirectory + imageName + '.png'
            }
          })
          node.innerHTML = '

  Changed: ' + imageName +
           '

 Addition Information:

' +
            '

  Diff

 Current

 Baseline

'

          images.push(data.getImageDataUrl())
          images.push(image)
          images.push(res.image)
          $.ajax({
            type: 'POST',
            async: false,
            url: '/report',
            data: {
              type: 'Changed',
              images: images,
              name: imageName,
              browser: window.ui.browser
            }
          })
        }
        $(document.getElementById('ember-testing')).removeAttr('style')
        $(targetElement).removeAttr('style')
        document.getElementsByClassName('visual-acceptance-container')[0].appendChild(node)
        assert = assert === undefined ? chai.assert : assert
        assert.equal(result, true, 'Image mismatch percentage (' + data.misMatchPercentage +
         ') is above mismatch threshold(' + misMatchPercentageMargin + ').')
        data ? resolve(data) : reject(data)
      })
    }).then(function (data) {
      data ? resolve(data) : reject(data)
    })
  }
}
<commit_msg>Remove Es6 for pure phantomJS<commit_after>/*global XMLHttpRequest,$,,chai,resemble, html2canvas, Image, XMLSerializer,btoa */
/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "capture" }]*/
/**
 * Does httpGet on url synchronously
 * @param {string} theUrl - url to do GET request on
 * @returns {string} GET Response Text
 */
function httpGet (theUrl) {
  var xmlHttp = new XMLHttpRequest()
  xmlHttp.open('GET', theUrl, false) // false for synchronous request
  xmlHttp.send(null)
  return xmlHttp.responseText
}
/**
 * Convert Svgs to canvas. In hopes of a more accurate rendering using html2canvas
 * @returns {Promise} Promise of all svg conversions
 */
function experimentalSvgCapture () {
  /* eslint-enable no-unused-vars */
  var items = Array.from(document.querySelectorAll('svg'))
  var promises = items.map(function (svg) {
    return new Promise(function (resolve) {
      var clientWidth = $(svg).width() || $(svg.parentNode).width()
      var clientHeight = $(svg).height() || $(svg.parentNode).height()
      svg.setAttribute('width', clientWidth)
      svg.setAttribute('height', clientWidth)
      var myCanvas = document.createElement('canvas')

      // Get drawing context for the Canvas
      var myCanvasContext = myCanvas.getContext('2d')
        // Load up our image.
      var source = new Image()
      var xml = new XMLSerializer().serializeToString(svg)
      var data = 'data:image/svg+xml;base64,' + btoa(xml)
      source.src = data
        // Render our SVG image to the canvas once it loads.
      /**
       *
       */
      source.onload = function () {
        myCanvas.width = clientWidth
        myCanvas.height = clientHeight
        myCanvas.className = svg.className.baseVal
        myCanvas.id = svg.id
        myCanvasContext.drawImage(source, 0, 0, clientWidth, clientHeight)
        $(svg).replaceWith(myCanvas)
        resolve()
      }
    })
  })
  return Promise.all(promises)
}
/**
 * Creates baseline imagesfor visual regression during standard Ember tests using html2Canvas and ResembleJS
 * @param {string} imageName - Name of the image you wish to save
 * @param {object} options - Options for capture
 * @param {number} [options.width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [options.height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [options.misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} [options.targetElement=ember-testing-container] - DOM element to capture
 * @param {boolean} [options.experimentalSvgs=undefined] - Set to true in order try experimental rendering of svgs using html2canvas
 * @param {object} [options.assert=undefined] - Use only if using qunit
 * @returns {Promise} ResembleJs return value
 */
function capture (imageName, options) {
  options = options || {}
  if (options.misMatchPercentageMargin == null) {
    options.misMatchPercentageMargin = 0.00
  }
  if (options.targetElement == null) {
    options.targetElement = document.getElementById('ember-testing-container')
  }
  var browser = window.ui
  var istargetbrowser = JSON.parse(httpGet('/istargetbrowser?' + $.param(browser)))
  if (istargetbrowser === false) {
    return new Promise(function (resolve) {
      resolve('Does not match target browser')
    })
  }

  $(document.getElementById('ember-testing')).css('zoom', 'initial')
  $(document.getElementById('ember-testing')).css('width', '100%')
  $(document.getElementById('ember-testing')).css('height', '100%')
  $(document.getElementById('ember-testing-container')).css('overflow', 'visible')
  $(document.getElementById('ember-testing-container')).css('position', 'initial')
  var browserDirectory
  if (browser.osversion === undefined) {
    browserDirectory = browser.os + '/' + browser.browser + '/'
  } else {
    browserDirectory = browser.os + '/' + browser.osversion + '/' + browser.browser + '/'
  }

  if (options.height && options.width) {
    $(options.targetElement).css('width', options.width + 'px')
    $(options.targetElement).css('height', options.height + 'px')
    browserDirectory += options.width + 'x' + options.height + '/'
  } else {
    // default mocha window size
    browserDirectory += options.targetElement.clientWidth + 'x' + options.targetElement.clientHeight + '/'
  }
  // resemble.outputSettings({
  //   largeImageThreshold: 0
  // })
  if (window.callPhantom !== undefined) {
    return capturePhantom(imageName, options.width, options.height,
     options.misMatchPercentageMargin, options.targetElement, options.assert, browserDirectory)
  } else {
    if (options.experimentalSvgs === true) {
      return experimentalSvgCapture().then(function () {
        return captureHtml2Canvas(imageName, options.width, options.height,
         options.misMatchPercentageMargin, options.targetElement,
         options.assert, browserDirectory)
      })
    } else {
      return captureHtml2Canvas(imageName, options.width, options.height,
       options.misMatchPercentageMargin, options.targetElement,
       options.assert, browserDirectory)
    }
  }
}
/**
 * Use phantomJS/slimerjs callback to capture Image
 * @param {string} imageName - Name of the image you wish to save
 * @param {number} [width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} targetElement - DOM element to capture
 * @param {object} [assert=undefined] - Use only if using qunit
 * @param {object} [browserDirectory=undefined] - visual acceptance image path based off window.ui (holds browser info) and size of ember-testing-container
 * @returns {Promise} ResembleJs return value
 */
function capturePhantom (imageName, width, height, misMatchPercentageMargin, targetElement, assert, browserDirectory) {
  return new Promise(function (resolve, reject) {
    if (window.callPhantom === undefined) {
      resolve('Not on PhantomJS')
    }
    var image
    if (targetElement.id !== '') {
      image = window.callPhantom({
        id: targetElement.id
      })
    } else {
      var tempId = 'tempVisualAcceptanceId'
      targetElement.id = tempId
      image = window.callPhantom({
        id: targetElement.id
      })
      targetElement.id = ''
    }
    // Get test dummy image

    image = 'data:image/png;base64,' + image
      // console.log(image)
    return utilizeImage(imageName, width, height, misMatchPercentageMargin, targetElement, assert,
     image, browserDirectory,
     resolve, reject)
  })
}
/**
 * Use html2canvas to capture Image
 * @param {string} imageName - Name of the image you wish to save
 * @param {number} [width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} targetElement - DOM element to capture
 * @param {object} [assert=undefined] - Use only if using qunit
 * @param {object} browserDirectory - visual acceptance image path based off window.ui (holds browser info) and size of ember-testing-container
 * @returns {Promise} ResembleJs return value
 */
function captureHtml2Canvas (imageName, width, height, misMatchPercentageMargin, targetElement,
 assert, browserDirectory) {
  return html2canvas(targetElement, {
    timeout: 1000
  }).then(function (canvas) {
    // Get test dummy image
    var image = canvas.toDataURL('image/png')
    return new Promise(function (resolve, reject) {
      return utilizeImage(imageName, width, height, misMatchPercentageMargin, targetElement, assert,
       image, browserDirectory, resolve, reject)
    })
  })
}
/**
 * Use html2canvas to capture Image
 * @param {string} imageName - Name of the image you wish to save
 * @param {number} [width=null] - Define the width of the canvas in pixels. If null, renders with full width of the container(640px).
 * @param {number} [height=null] - Define the height of the canvas in pixels. If null, renders with full height of the window.(384px).
 * @param {float} [misMatchPercentageMargin=0.00] - The maximum percentage ResembleJs is allowed to misMatch.
 * @param {HTMLElement} targetElement - DOM element to capture
 * @param {object} [assert=undefined] - Use only if using qunit
 * @param {string} image - Base64 image produced from html2canvas or PhantomJS
 * @param {object} browserDirectory - visual acceptance image path based off window.ui (holds browser info) and size of ember-testing-container
 * @param {object} resolve - resolve from Promise
 * @param {object} reject - reject from Promise
 * @returns {Promise} ResembleJs return value
 */
function utilizeImage (imageName, width, height, misMatchPercentageMargin, targetElement, assert,
 image, browserDirectory, resolve, reject) {
  if (!document.getElementById('visual-acceptance') && $('.tabs').length === 0) {
    var visualAcceptanceContainer
    visualAcceptanceContainer = document.createElement('div')
    visualAcceptanceContainer.setAttribute('id', 'visual-acceptance')
    visualAcceptanceContainer.innerHTML = '
 Visual Acceptance tests:
'
    visualAcceptanceContainer.innerHTML += '

'
    document.body.appendChild(visualAcceptanceContainer)
  }
  var node = document.createElement('div')
  var images = []
    // Get passed image
  var res = JSON.parse(httpGet('/image?name=' + encodeURIComponent(browserDirectory + imageName) + '-passed.png'))
  if (res.error === 'File does not exist') {
    // Save image as passed if no existing passed image
    $.ajax({
      type: 'POST',
      async: false,
      url: '/passed',
      data: {
        image: image,
        name: browserDirectory + imageName + '.png'
      }
    })
    $(document.getElementById('ember-testing')).removeAttr('style')
    $(targetElement).removeAttr('style')
    node.innerHTML = '

 No new image. Saving current as baseline: ' +
     imageName + '

 Addition Information:

'
    images.push(image)
    $.ajax({
      type: 'POST',
      async: false,
      url: '/report',
      data: {
        type: 'New',
        images: images,
        name: imageName,
        browser: window.ui.browser
      }
    })
    resolve('No passed image. Saving current test as base')
  } else {
    // Passed image exists so compare to current
    res.image = 'data:image/png;base64,' + res.image
    return new Promise(function (resolve, reject) {
      resemble(res.image).compareTo(image).scaleToSameSize().onComplete(function (data) {
        var result = false
        if (parseFloat(data.misMatchPercentage) <= misMatchPercentageMargin) {
          // Passed
          $.ajax({
            type: 'POST',
            async: false,
            url: '/passed',
            data: {
              image: image,
              name: browserDirectory + imageName + '.png'
            }
          })
          result = true
          node.innerHTML = '

  New: ' + imageName +
           '

 Addition Information:

'
        } else {
          // Fail
          $.ajax({
            type: 'POST',
            async: false,
            url: '/fail',
            data: {
              image: data.getImageDataUrl(),
              name: browserDirectory + imageName + '.png'
            }
          })
          node.innerHTML = '

  Changed: ' + imageName +
           '

 Addition Information:

' +
            '

  Diff

 Current

 Baseline

'

          images.push(data.getImageDataUrl())
          images.push(image)
          images.push(res.image)
          $.ajax({
            type: 'POST',
            async: false,
            url: '/report',
            data: {
              type: 'Changed',
              images: images,
              name: imageName,
              browser: window.ui.browser
            }
          })
        }
        $(document.getElementById('ember-testing')).removeAttr('style')
        $(targetElement).removeAttr('style')
        document.getElementsByClassName('visual-acceptance-container')[0].appendChild(node)
        assert = assert === undefined ? chai.assert : assert
        assert.equal(result, true, 'Image mismatch percentage (' + data.misMatchPercentage +
         ') is above mismatch threshold(' + misMatchPercentageMargin + ').')
        data ? resolve(data) : reject(data)
      })
    }).then(function (data) {
      data ? resolve(data) : reject(data)
    })
  }
}
<|endoftext|>


<commit_before>import {
  authedUserCreateStore,
  mount,
  shallowMount,
  createStore,
  RouterLinkStub as NuxtLink,
  createLocalVue
} from 'helper'
import Vuex from 'vuex'

import AppSidebar from '~/components/sidebar/App'

const menus = [
  'Your Stream',
  'Mentions',
  'Interactions',
  'Stars',
  'Conversations',
  'Missed Conversations',
  'Conversations',
  'Newcomers',
  'Photos',
  'Trending',
  'Global'
]

function getTrimmedText(wrapper) {
  return wrapper.text().trim()
}

describe('App sidebar component', () => {
  let localVue
  beforeEach(() => {
    localVue = createLocalVue()
    localVue.use(Vuex)
  })
  test('Show all menus when logged in', () => {
    const wrapper = shallowMount(AppSidebar, {
      store: authedUserCreateStore(),
      mocks: {
        $route: {
          fullPath: ''
        }
      },
      localVue,
      stubs: {
        NuxtLink
      }
    })

    expect(wrapper.findAll('.heading').wrappers.map(getTrimmedText)).toEqual(
      expect.arrayContaining(['Beta', 'Explore', 'Other'])
    )
    const links = wrapper.findAll('a')
    expect(links.wrappers.map(getTrimmedText)).toEqual(
      expect.arrayContaining(menus)
    )
  })

  test('Hide some menus when not logged in', () => {
    const wrapper = shallowMount(AppSidebar, {
      store: createStore(),
      localVue,
      mocks: {
        $route: {
          fullPath: ''
        }
      },
      stubs: {
        NuxtLink
      }
    })

    expect(wrapper.findAll('.heading').wrappers.map(getTrimmedText)).toEqual(
      expect.arrayContaining(['Other'])
    )

    const links = wrapper.findAll('a')
    const texts = links.wrappers.map(getTrimmedText)
    expect(texts).not.toEqual(
      expect.arrayContaining([
        'Your Stream',
        'Mentions',
        'Interactions',
        'Stars',
        'Conversations',
        'Missed Conversations',
        'Conversations',
        'Newcomers',
        'Photos',
        'Trending',
        'Keyboard shortcuts'
      ])
    )
    expect(texts).toEqual(
      expect.arrayContaining(['Global', 'About', 'Beta on GitHub'])
    )
  })
  test('Show help modal when click keyboard shortcuts', async () => {
    const wrapper = mount(AppSidebar, {
      store: authedUserCreateStore(),
      localVue,
      mocks: {
        $route: {
          fullPath: ''
        }
      },
      stubs: {
        NuxtLink
      }
    })
    wrapper.setMethods({
      showHelpModal: jest.fn()
    })
    const keyboardLink = wrapper
      .findAll('a')
      .wrappers.find(wrapper =>
        /Keyboard shortcuts/.test(wrapper.text().trim())
      )
    keyboardLink.trigger('click')
    // why???
    // expect(wrapper.vm.showHelpModal).toHaveBeenCalled()
  })
})
<commit_msg>Fix tests<commit_after>import {
  authedUserCreateStore,
  mount,
  shallowMount,
  createStore,
  RouterLinkStub as NuxtLink,
  createLocalVue
} from 'helper'
import Vuex from 'vuex'

import AppSidebar from '~/components/sidebar/App'

const menus = [
  'Your Stream',
  'Mentions',
  'Interactions',
  'Stars',
  'Conversations',
  'Missed Conversations',
  'Conversations',
  'Newcomers',
  'Photos',
  'Trending',
  'Global'
]

function getTrimmedText(wrapper) {
  return wrapper.text().trim()
}

describe('App sidebar component', () => {
  let localVue
  beforeEach(() => {
    localVue = createLocalVue()
    localVue.use(Vuex)
  })
  test('Show all menus when logged in', () => {
    const wrapper = shallowMount(AppSidebar, {
      store: authedUserCreateStore(),
      mocks: {
        $route: {
          fullPath: ''
        }
      },
      localVue,
      stubs: {
        NuxtLink
      }
    })

    expect(wrapper.findAll('.heading').wrappers.map(getTrimmedText)).toEqual(
      expect.arrayContaining(['Beta', 'Explore', 'Other'])
    )
    const links = wrapper.findAll('a')
    expect(links.wrappers.map(getTrimmedText)).toEqual(
      expect.arrayContaining(menus)
    )
  })

  test('Hide some menus when not logged in', () => {
    const wrapper = shallowMount(AppSidebar, {
      store: createStore(),
      localVue,
      mocks: {
        $route: {
          fullPath: ''
        }
      },
      stubs: {
        NuxtLink
      }
    })

    expect(wrapper.findAll('.heading').wrappers.map(getTrimmedText)).toEqual(
      expect.arrayContaining(['Other'])
    )

    const links = wrapper.findAll('.list-group a')
    const texts = links.wrappers.map(getTrimmedText)
    expect(texts).not.toEqual(
      expect.arrayContaining([
        'Your Stream',
        'Mentions',
        'Interactions',
        'Stars',
        'Conversations',
        'Missed Conversations',
        'Conversations',
        'Newcomers',
        'Photos',
        'Trending',
        'Keyboard shortcuts'
      ])
    )
    expect(texts).toEqual(expect.arrayContaining(['About', 'Beta on GitHub']))
  })
  test('Show help modal when click keyboard shortcuts', async () => {
    const wrapper = mount(AppSidebar, {
      store: authedUserCreateStore(),
      localVue,
      mocks: {
        $route: {
          fullPath: ''
        }
      },
      stubs: {
        NuxtLink
      }
    })
    wrapper.setMethods({
      showHelpModal: jest.fn()
    })
    const keyboardLink = wrapper
      .findAll('a')
      .wrappers.find(wrapper =>
        /Keyboard shortcuts/.test(wrapper.text().trim())
      )
    const fn = jest.fn()
    wrapper.vm.$modal.show = fn
    keyboardLink.trigger('click')
    expect(fn).toHaveBeenCalled()
  })
})
<|endoftext|>


<commit_before>import { show, Morph, morph, Text, config } from "lively.morphic"
import { obj, arr, num, fun, promise } from "lively.lang"
import { pt, Rectangle, rect, Color } from "lively.graphics"
import { connect, signal, once } from "lively.bindings"
import { resource } from "lively.resources";

// this.world().openInWindow(new TextEditor).activate()

const editorCommands = [

  {
    name: "focus url input",
    exec: fileBrowser => { fileBrowser.ui.urlInput.focus(); return true; }
  },

  {
    name: "focus content text",
    exec: fileBrowser => { fileBrowser.ui.contentText.focus(); return true; }
  },

  {
    name: "load file",
    exec: async fileBrowser => {
      fileBrowser.location = fileBrowser.ui.urlInput.input;
      return true;
    }
  },

  {
    name: "save file",
    exec: async textEditor => {
      let f = textEditor.locationResource;
      if (!f) {
        textEditor.setStatusMessage("No file selected");
        return true;
      }

      try {
        await f.write(textEditor.ui.contentText.textString);
        textEditor.setStatusMessage(`${f.url} saved`, Color.green);
        signal(textEditor, "contentSaved");
      } catch (e) {
        textEditor.showError(`Error writing ${f.url}: ${e.stack || e}`);
      }

      return true;
    }
  },

  {
    name: "remove file",
    exec: async textEditor => {
      let f = textEditor.location
      if (!f) {
        textEditor.setStatusMessage("No file selected");
        return true;
      }

      try {
        if (await textEditor.world().confirm(`Really remove ${f.url}?`)) {
          await f.remove();
          textEditor.setStatusMessage(`${f.url} removed!`);
          textEditor.reload();
        } else {
          await textEditor.world().inform("delete file canceled");
        }

      } catch (e) {
        textEditor.showError(`Error writing ${f.url}: ${e.stack || e}`);
      }

      return true;
    }
  },

];

export default class TextEditor extends Morph {

  static openURL(url, props) {
    return this.openInWindow({location: url, ...props})
  }

  static openAsEDITOR(file, props) {
    // returns "saved" or "aborted"
    var editor = this.openURL(file, props);
    return new Promise((resolve, reject) => {
      once(editor, 'contentSaved', resolve, 'call', {
        updater: function($upd) { $upd(null, "saved"); this.sourceObj.close(); }
      });
      once(editor, 'closed', resolve, 'call', {updater: $upd => $upd(null, "aborted")});
    })
  }

  static openInWindow(props) {
    var ed = new this(props);
    ed.env.world.openInWindow(ed).activate();
    return ed;
  }

  static get properties() {
    return {
      name: {defaultValue: "text editor"},
      fill: {defaultValue: Color.white},
      border: {defaultValue: {width: 1, color: Color.black}},
      extent: {defaultValue: pt(700,600)},

      submorphs: {
        initialize() {
          this.submorphs = [
            Text.makeInputLine({name: "urlInput", historyId: "lively.morphic-text editor url"}),
            {name: "loadButton", type: "button", label: "reload"},
            {name: "saveButton", type: "button", label: "save"},
            {name: "removeButton", type: "button", label: "remove"},
            {
              ...config.codeEditor.defaultStyle,
              name: "contentText", type: "text",
              lineWrapping: false
            }
          ]
          var {urlInput, loadButton, saveButton, removeButton, contentText} = this.ui;
          connect(this, 'extent', this, 'relayout');
          connect(urlInput, 'inputAccepted', this, 'location');
          connect(loadButton, 'fire', this, 'execCommand', {converter: () => "load file"});
          connect(saveButton, 'fire', this, 'execCommand', {converter: () => "save file"});
          connect(removeButton, 'fire', this, 'execCommand', {converter: () => "remove file"});
          connect(contentText, 'doSave', this, 'execCommand', {converter: () => "save file"});
        }
      },

      ui: {
        readOnly: true, derived: true, after: ["submorphs"],
        get() {
          var [urlInput, loadButton, saveButton, removeButton, contentText] = this.submorphs;
          return {urlInput, loadButton, saveButton, removeButton, contentText};
        }
      },

      location: {
        derived: true, after: ["submorphs"],

        get() { return this.ui.urlInput.input; },

        set(val) {
          let {url, lineNumber} = this.parseFileNameInput(val),
              {contentText, urlInput} = this.ui;
          urlInput.input = val || "";
          urlInput.acceptInput();
          if (urlInput.isFocused()) contentText.focus();
          this.showFileContent(resource(url));
          if (typeof lineNumber !== "undefined") this.lineNumber = lineNumber;
        }
      },

      locationResource: {
        derived: true, after: ["location"],
        get() {
          let {url} = this.parseFileNameInput(this.location);
          return resource(url);
        },
        set(resource) { this.location = resource.url; }
      },

      lineNumber: {
        derived: true, after: ["location"],
        get() { return this.ui.contentText.cursorPosition.row; },
        set(val) {
          var row = Number(val);
          if (isNaN(row)) return;
          this.whenLoaded().then(() => {
            var ed = this.ui.contentText;
            ed.cursorPosition = {row, column: 0};
            ed.centerRow(row);
          })
        }
      }
    }
  }

  constructor(props) {
    super(props);
    this.relayout();
    this._loadPromise = null;
  }

  reload() { this.location = this.location; }

  relayout() {
    var {urlInput, loadButton, saveButton, removeButton, contentText} = this.ui;

    urlInput.width = contentText.width = this.width;
    urlInput.top = 0;
    urlInput.height = 20;
    var oneThird = this.width/3;
    loadButton.extent = saveButton.extent = removeButton.extent = pt(oneThird, 20);
    loadButton.topLeft = urlInput.bottomLeft;
    saveButton.topLeft = loadButton.topRight;
    removeButton.topLeft = saveButton.topRight;
    contentText.topLeft = loadButton.bottomLeft;
    contentText.height = this.height - loadButton.bottom;
  }

  async whenLoaded() {
    return this._loadPromise || Promise.resolve(this);
  }

  parseFileNameInput(input) {
    let url = input,
        lineNumber = undefined,
        colonIndex = input.lastIndexOf(":");

    if (colonIndex > -1 && input.slice(colonIndex+1).match(/^[0-9]+$/)) {
      lineNumber = Number(input.slice(colonIndex+1));
      url = input.slice(0, colonIndex);
    }

    return {lineNumber, url}
  }

  async showFileContent(resource) {
    var deferred = promise.deferred();
    this._loadPromise = deferred.promise;
    try {
      var content = await resource.read();
      await this.prepareEditorForFile(resource, content);
      var win = this.getWindow();
      if (win) win.title = resource.name();
      deferred.resolve(this);
    } catch (e) { this.showError(e); deferred.reject(e); }
    return this._loadPromise;
  }

  async prepareEditorForFile(resource, content = "") {
    var ed = this.ui.contentText;

    var {_editorPlugin: editorPlugin} = this;
    if (editorPlugin) ed.removePlugin(editorPlugin);
    editorPlugin = null;

    var url = (resource || {}).url,
        fileType = "plain text";

    if (content.length > 2**20/*1MB*/) {
      this.setStatusMessage(`File content very big, ${num.humanReadableByteSize(content.length)}. Styling is disabled`);

    } else if (url) {

      var [_, ext] = url.match(/\.([^\.\s]+)$/) || [];

      // FIXME
      switch (ext) {

        case 'js':
          var { JavaScriptEditorPlugin } = await System.import("lively.morphic/ide/js/editor-plugin.js");
          editorPlugin = new JavaScriptEditorPlugin(config.codeEditor.defaultTheme);
          editorPlugin.evalEnvironment = {
            get targetModule() { return url; },
            context: ed,
            get format() { return lively.modules.module(this.targetModule).format() || "global"; }
          }
          break;

        case 'json':
          var { JSONEditorPlugin } = await System.import("lively.morphic/ide/json/editor-plugin.js");
          editorPlugin = new JSONEditorPlugin(config.codeEditor.defaultTheme);
          break;

        case 'md':
          var { MarkdownEditorPlugin } = await System.import("lively.morphic/ide/md/editor-plugin.js");
          editorPlugin = new MarkdownEditorPlugin(config.codeEditor.defaultTheme);
          break;

        case 'sh':
          var { ShellEditorPlugin } = await System.import("lively.morphic/ide/shell/editor-plugin.js");
          editorPlugin = new ShellEditorPlugin(config.codeEditor.defaultTheme);
          break;

        default:
          fileType = "plain text";
      }
    }

    if (editorPlugin) ed.addPlugin(editorPlugin);
    this._editorPlugin = editorPlugin;

    ed.textString = content
    ed.gotoDocumentStart();
    ed.scroll = pt(0,0);
  }

  focus() {
    this.ui.contentText.focus();
  }

  close() {
    var win = this.getWindow();
    win ? win.close() : this.remove();
  }

  onWindowClose() {
    signal(this, "closed");
  }

  get commands() {
    return editorCommands.concat(super.commands);
  }

  get keybindings() {
    return [
      {keys: {mac: "Meta-S", win: "Ctrl-S"}, command: "save file"},
      {keys: "Alt-Up", command: "focus url input"},
      {keys: "Alt-Down", command: "focus content text"}
    ].concat(super.keybindings);
  }
}
<commit_msg>fix assigning text plugins in text editor<commit_after>import { show, Morph, morph, Text, config } from "lively.morphic"
import { obj, arr, num, fun, promise } from "lively.lang"
import { pt, Rectangle, rect, Color } from "lively.graphics"
import { connect, signal, once } from "lively.bindings"
import { resource } from "lively.resources";

// this.world().openInWindow(new TextEditor).activate()

const editorCommands = [

  {
    name: "focus url input",
    exec: fileBrowser => { fileBrowser.ui.urlInput.focus(); return true; }
  },

  {
    name: "focus content text",
    exec: fileBrowser => { fileBrowser.ui.contentText.focus(); return true; }
  },

  {
    name: "load file",
    exec: async fileBrowser => {
      fileBrowser.location = fileBrowser.ui.urlInput.input;
      return true;
    }
  },

  {
    name: "save file",
    exec: async textEditor => {
      let f = textEditor.locationResource;
      if (!f) {
        textEditor.setStatusMessage("No file selected");
        return true;
      }

      try {
        await f.write(textEditor.ui.contentText.textString);
        textEditor.setStatusMessage(`${f.url} saved`, Color.green);
        signal(textEditor, "contentSaved");
      } catch (e) {
        textEditor.showError(`Error writing ${f.url}: ${e.stack || e}`);
      }

      return true;
    }
  },

  {
    name: "remove file",
    exec: async textEditor => {
      let f = textEditor.location
      if (!f) {
        textEditor.setStatusMessage("No file selected");
        return true;
      }

      try {
        if (await textEditor.world().confirm(`Really remove ${f.url}?`)) {
          await f.remove();
          textEditor.setStatusMessage(`${f.url} removed!`);
          textEditor.reload();
        } else {
          await textEditor.world().inform("delete file canceled");
        }

      } catch (e) {
        textEditor.showError(`Error writing ${f.url}: ${e.stack || e}`);
      }

      return true;
    }
  },

];

export default class TextEditor extends Morph {

  static openURL(url, props) {
    return this.openInWindow({location: url, ...props})
  }

  static openAsEDITOR(file, props) {
    // returns "saved" or "aborted"
    var editor = this.openURL(file, props);
    return new Promise((resolve, reject) => {
      once(editor, 'contentSaved', resolve, 'call', {
        updater: function($upd) { $upd(null, "saved"); this.sourceObj.close(); }
      });
      once(editor, 'closed', resolve, 'call', {updater: $upd => $upd(null, "aborted")});
    })
  }

  static openInWindow(props) {
    var ed = new this(props);
    ed.env.world.openInWindow(ed).activate();
    return ed;
  }

  static get properties() {
    return {
      name: {defaultValue: "text editor"},
      fill: {defaultValue: Color.white},
      border: {defaultValue: {width: 1, color: Color.black}},
      extent: {defaultValue: pt(700,600)},

      submorphs: {
        initialize() {
          this.submorphs = [
            Text.makeInputLine({name: "urlInput", historyId: "lively.morphic-text editor url"}),
            {name: "loadButton", type: "button", label: "reload"},
            {name: "saveButton", type: "button", label: "save"},
            {name: "removeButton", type: "button", label: "remove"},
            {
              ...config.codeEditor.defaultStyle,
              name: "contentText", type: "text",
              lineWrapping: false
            }
          ]
          var {urlInput, loadButton, saveButton, removeButton, contentText} = this.ui;
          connect(this, 'extent', this, 'relayout');
          connect(urlInput, 'inputAccepted', this, 'location');
          connect(loadButton, 'fire', this, 'execCommand', {converter: () => "load file"});
          connect(saveButton, 'fire', this, 'execCommand', {converter: () => "save file"});
          connect(removeButton, 'fire', this, 'execCommand', {converter: () => "remove file"});
          connect(contentText, 'doSave', this, 'execCommand', {converter: () => "save file"});
        }
      },

      ui: {
        readOnly: true, derived: true, after: ["submorphs"],
        get() {
          var [urlInput, loadButton, saveButton, removeButton, contentText] = this.submorphs;
          return {urlInput, loadButton, saveButton, removeButton, contentText};
        }
      },

      location: {
        derived: true, after: ["submorphs"],

        get() { return this.ui.urlInput.input; },

        set(val) {
          let {url, lineNumber} = this.parseFileNameInput(val),
              {contentText, urlInput} = this.ui;
          urlInput.input = val || "";
          urlInput.acceptInput();
          if (urlInput.isFocused()) contentText.focus();
          this.showFileContent(resource(url));
          if (typeof lineNumber !== "undefined") this.lineNumber = lineNumber;
        }
      },

      locationResource: {
        derived: true, after: ["location"],
        get() {
          let {url} = this.parseFileNameInput(this.location);
          return resource(url);
        },
        set(resource) { this.location = resource.url; }
      },

      lineNumber: {
        derived: true, after: ["location"],
        get() { return this.ui.contentText.cursorPosition.row; },
        set(val) {
          var row = Number(val);
          if (isNaN(row)) return;
          this.whenLoaded().then(() => {
            var ed = this.ui.contentText;
            ed.cursorPosition = {row, column: 0};
            ed.centerRow(row);
          })
        }
      }
    }
  }

  constructor(props) {
    super(props);
    this.relayout();
    this._loadPromise = null;
  }

  reload() { this.location = this.location; }

  relayout() {
    var {urlInput, loadButton, saveButton, removeButton, contentText} = this.ui;

    urlInput.width = contentText.width = this.width;
    urlInput.top = 0;
    urlInput.height = 20;
    var oneThird = this.width/3;
    loadButton.extent = saveButton.extent = removeButton.extent = pt(oneThird, 20);
    loadButton.topLeft = urlInput.bottomLeft;
    saveButton.topLeft = loadButton.topRight;
    removeButton.topLeft = saveButton.topRight;
    contentText.topLeft = loadButton.bottomLeft;
    contentText.height = this.height - loadButton.bottom;
  }

  async whenLoaded() {
    return this._loadPromise || Promise.resolve(this);
  }

  parseFileNameInput(input) {
    let url = input,
        lineNumber = undefined,
        colonIndex = input.lastIndexOf(":");

    if (colonIndex > -1 && input.slice(colonIndex+1).match(/^[0-9]+$/)) {
      lineNumber = Number(input.slice(colonIndex+1));
      url = input.slice(0, colonIndex);
    }

    return {lineNumber, url}
  }

  async showFileContent(resource) {
    var deferred = promise.deferred();
    this._loadPromise = deferred.promise;
    try {
      var content = await resource.read();
      await this.prepareEditorForFile(resource, content);
      var win = this.getWindow();
      if (win) win.title = resource.name();
      deferred.resolve(this);
    } catch (e) { this.showError(e); deferred.reject(e); }
    return this._loadPromise;
  }

  async prepareEditorForFile(resource, content = "") {
    var ed = this.ui.contentText, editorPlugin;

    ed.plugins = [];

    var url = (resource || {}).url,
        fileType = "plain text";

    if (content.length > 2**20/*1MB*/) {
      this.setStatusMessage(`File content very big, ${num.humanReadableByteSize(content.length)}. Styling is disabled`);

    } else if (url) {

      var [_, ext] = url.match(/\.([^\.\s]+)$/) || [];

      // FIXME
      switch (ext) {

        case 'js':
          var { JavaScriptEditorPlugin } = await System.import("lively.morphic/ide/js/editor-plugin.js");
          editorPlugin = new JavaScriptEditorPlugin(config.codeEditor.defaultTheme);
          editorPlugin.evalEnvironment = {
            get targetModule() { return url; },
            context: ed,
            get format() { return lively.modules.module(this.targetModule).format() || "global"; }
          }
          break;

        case 'json':
          var { JSONEditorPlugin } = await System.import("lively.morphic/ide/json/editor-plugin.js");
          editorPlugin = new JSONEditorPlugin(config.codeEditor.defaultTheme);
          break;

        case 'md':
          var { MarkdownEditorPlugin } = await System.import("lively.morphic/ide/md/editor-plugin.js");
          editorPlugin = new MarkdownEditorPlugin(config.codeEditor.defaultTheme);
          break;

        case 'sh':
          var { ShellEditorPlugin } = await System.import("lively.morphic/ide/shell/editor-plugin.js");
          editorPlugin = new ShellEditorPlugin(config.codeEditor.defaultTheme);
          break;

        default:
          fileType = "plain text";
      }
    }

    if (editorPlugin) ed.addPlugin(editorPlugin);

    ed.textString = content;
    ed.gotoDocumentStart();
    ed.scroll = pt(0,0);
  }

  focus() {
    this.ui.contentText.focus();
  }

  close() {
    var win = this.getWindow();
    win ? win.close() : this.remove();
  }

  onWindowClose() {
    signal(this, "closed");
  }

  get commands() {
    return editorCommands.concat(super.commands);
  }

  get keybindings() {
    return [
      {keys: {mac: "Meta-S", win: "Ctrl-S"}, command: "save file"},
      {keys: "Alt-Up", command: "focus url input"},
      {keys: "Alt-Down", command: "focus content text"}
    ].concat(super.keybindings);
  }
}
<|endoftext|>


<commit_before>module.exports = function( grunt ) {

    // Create a new task.
    grunt.registerTask( 'compass', 'This triggers the `compass compile` command.', function() {

        var exec = require('child_process').exec;
        grunt.log.write( '`compass compile` was inititated.' );

        function puts( error, stdout, stderr ) {

            grunt.log.write( '\n\nCOMPASS output:\n' );
            grunt.log.write( stdout );
            console.error( stderr );

            if ( error !== null ) {
                grunt.log.write( 'error: ' + error );
            }
        }

        exec( "compass compile", puts );
    });
};<commit_msg>use `grunt.log.error()` instead of `console.error()`<commit_after>module.exports = function( grunt ) {

    // Create a new task.
    grunt.registerTask( 'compass', 'This triggers the `compass compile` command.', function() {

        var exec = require('child_process').exec;
        grunt.log.write( '`compass compile` was inititated.' );

        function puts( error, stdout, stderr ) {

            grunt.log.write( '\n\nCOMPASS output:\n' );
            grunt.log.write( stdout );
            grunt.log.error( stderr );

            if ( error !== null ) {
                grunt.log.error( error );
            }
        }

        exec( "compass compile", puts );
    });
};<|endoftext|>


<commit_before>'use strict';

const express = require('express');
const puppeteer = require('puppeteer');
const {URL} = require('url');
const log4js = require('log4js');
log4js.configure({
  appenders: {
    out: {type: 'stdout'},
    app: {type: 'file', filename: 'webbkoll-backend.log'},
  },
  categories: {
    default: {
      appenders: ['out', 'app'],
      level: 'info',
    },
  },
});

const logger = log4js.getLogger();

const PORT = process.env.PORT || 8100;
const app = express();

app.get('/', async (request, response) => {
  const url = request.query.fetch_url;

  try {
    const parsedUrl = new URL(request.query.fetch_url);
    if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
      return response.status(500).type('application/json').send(JSON.stringify({
        'success': false,
        'reason': 'Failed to fetch this URL: invalid URL',
      }));
    }
  } catch (err) {
    return response.status(500).type('application/json').send(JSON.stringify({
      'success': false,
      'reason': 'Failed to fetch this URL: invalid URL',
    }));
  }

  logger.info('Trying ' + url);

  const timeout = request.query.timeout || 15000;
  const browser = await puppeteer.launch({headless: true});
  const viewport = {
    width: 1920,
    height: 1080,
  };

  try {
    const context = await browser.createIncognitoBrowserContext();
    const page = await context.newPage();

    await page.setViewport(viewport);
    await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 webbkoll.dataskydd.net');

    let requests = [];
    let responses = [];
    let responseHeaders = {};

    page.on('response', (response) => {
      responses.push({
        'url': response.url(),
        'status': response.status(),
        'headers': response.headers(),
      });
    });

    page.on('request', (request) => {
      let tmpHeaders = request.headers();
      let headersArr = [];

      // A little ugly, but the Webbkoll frontend expects headers in this format
      Object.keys(tmpHeaders).forEach(function(key, index) {
        headersArr.push({'name': key, 'value': tmpHeaders[key]});
      });

      requests.push({'url': request.url(), 'headers': headersArr});
    });

    await page._client.send('Network.enable');
    await page.goto(url, {
      waitUntil: ['load', 'domcontentloaded', 'networkidle0'],
      timeout: timeout,
    });

    let content = await page.content();
    // Necessary to get *ALL* cookies
    let cookies = await page._client.send('Network.getAllCookies');
    let title = await page.title();
    let finalUrl = await page.url();
    let hashIndex = finalUrl.indexOf('#');
    if (hashIndex > 0) {
      finalUrl = finalUrl.substr(0, finalUrl.indexOf('#'));
    }

    // We're only interested in the response headers for the final URL
    responses.forEach(function(response) {
      if (response.url == finalUrl) {
        responseHeaders = response.headers;
        responseHeaders['status'] = response.status;
      }
    });

    let webbkollStatus = 200;
    let results = {};
    if (responseHeaders['status'] >= 200 && responseHeaders['status'] <= 299) {
      logger.info('Successfully checked ' + url);
      results = {
        'success': true,
        'input_url': url,
        'final_url': finalUrl,
        'requests': requests,
        'response_headers': responseHeaders,
        'cookies': cookies.cookies,
        'content': content,
      };
    } else {
      logger.warn('Failed checking ' + url + ': ' + responseHeaders.status);
      results = {
        'success': false,
        'reason': 'Failed to fetch this URL: ' + responseHeaders.status + ' (' + title + ')',
      };
      webbkollStatus = 500;
    }

    response.status(webbkollStatus).type('application/json').send(JSON.stringify(results));
    await context.close();
  } catch (err) {
    logger.warn('Failed checking ' + url + ': ' + err.toString());
    response.status(500).type('application/json').send(JSON.stringify({
      'success': false,
      'reason': 'Failed to fetch this URL: ' + err.toString(),
    }));
  }
  await browser.close();
  logger.info('Finished with ' + url);
});

app.get('/status', async (request, response) => {
  response.status(200).send('OK!');
});

app.listen(PORT, function() {
  logger.info(`Webkoll backend listening on port ${PORT}`);
});
<commit_msg>Get response headers in a more sane way<commit_after>'use strict';

const express = require('express');
const puppeteer = require('puppeteer');
const {URL} = require('url');
const log4js = require('log4js');
log4js.configure({
  appenders: {
    out: {type: 'stdout'},
    app: {type: 'file', filename: 'webbkoll-backend.log'},
  },
  categories: {
    default: {
      appenders: ['out', 'app'],
      level: 'info',
    },
  },
});

const logger = log4js.getLogger();

const PORT = process.env.PORT || 8100;
const app = express();

app.get('/', async (request, response) => {
  const url = request.query.fetch_url;

  try {
    const parsedUrl = new URL(request.query.fetch_url);
    if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
      return response.status(500).type('application/json').send(JSON.stringify({
        'success': false,
        'reason': 'Failed to fetch this URL: invalid URL',
      }));
    }
  } catch (err) {
    return response.status(500).type('application/json').send(JSON.stringify({
      'success': false,
      'reason': 'Failed to fetch this URL: invalid URL',
    }));
  }

  logger.info('Trying ' + url);

  const timeout = request.query.timeout || 15000;
  const browser = await puppeteer.launch({headless: true});
  const viewport = {
    width: 1920,
    height: 1080,
  };

  try {
    const context = await browser.createIncognitoBrowserContext();
    const page = await context.newPage();

    await page.setViewport(viewport);
    await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 webbkoll.dataskydd.net');

    let requests = [];
    let responses = [];
    let responseHeaders = {};

    page.on('response', (response) => {
      responses.push({
        'url': response.url(),
        'status': response.status(),
        'headers': response.headers(),
      });
    });

    page.on('request', (request) => {
      let tmpHeaders = request.headers();
      let headersArr = [];

      // A little ugly, but the Webbkoll frontend expects headers in this format
      Object.keys(tmpHeaders).forEach(function(key, index) {
        headersArr.push({'name': key, 'value': tmpHeaders[key]});
      });

      requests.push({'url': request.url(), 'headers': headersArr});
    });

    await page._client.send('Network.enable');
    const pageResponse = await page.goto(url, {
      waitUntil: ['load', 'domcontentloaded', 'networkidle0'],
      timeout: timeout,
    });

    let content = await page.content();
    // Necessary to get *ALL* cookies
    let cookies = await page._client.send('Network.getAllCookies');
    let title = await page.title();
    let finalUrl = await page.url();

    responseHeaders = pageResponse.headers();
    responseHeaders.status = pageResponse.status();

    let webbkollStatus = 200;
    let results = {};
    if (responseHeaders.status >= 200 && responseHeaders.status <= 299) {
      logger.info('Successfully checked ' + url);
      results = {
        'success': true,
        'input_url': url,
        'final_url': finalUrl,
        'requests': requests,
        'response_headers': responseHeaders,
        'cookies': cookies.cookies,
        'content': content,
      };
    } else {
      logger.warn('Failed checking ' + url + ': ' + responseHeaders.status);
      results = {
        'success': false,
        'reason': 'Failed to fetch this URL: ' + responseHeaders.status + ' (' + title + ')',
      };
      webbkollStatus = 500;
    }

    response.status(webbkollStatus).type('application/json').send(JSON.stringify(results));
    await context.close();
  } catch (err) {
    logger.warn('Failed checking ' + url + ': ' + err.toString());
    response.status(500).type('application/json').send(JSON.stringify({
      'success': false,
      'reason': 'Failed to fetch this URL: ' + err.toString(),
    }));
  }
  await browser.close();
  logger.info('Finished with ' + url);
});

app.get('/status', async (request, response) => {
  response.status(200).send('OK!');
});

app.listen(PORT, function() {
  logger.info(`Webkoll backend listening on port ${PORT}`);
});
<|endoftext|>


<commit_before>import * as AsyncWorker from "../util/AsyncWorker.js";

/**
 * @return {Promise}
 *   Return a promise which will be resolved as specified value after specified
 * milliseconds.
 */
function delayedValue( milliseconds, value ) {
  return new Promise( ( resolve /*, reject*/ ) => {
    setTimeout( () => resolve( value ), milliseconds );
  } );
}

/**
 * The implementation of a neural network web worker.
 *
 */
class AsyncWorker_Body_tester extends AsyncWorker.Body {

  /** */
  constructor() {
    super(); // register callback from Worker_Proxy.
  }

  /** @override */
  async* disposeResources() {
    yield *super.disposeResources();
  }

  /**
   */
  async* initWorker( workerId ) {
    this.workerId = workerId;

    // Load libraries dynamically in global scope.
    {
      // [ globalThis.Pool,
      //   globalThis.Recyclable,
      //   globalThis.ValueMax,
      // ] = await Promise.all( [
      //   import( "../util/Pool.js" ),
      //   import( "../util/Recyclable.js" ),
      //   import( "../util/ValueMax.js" ),
      // ] );
    }

    return { value: true };
  }

  /**
   * @param {number} intervalMilliseconds
   *   How long to generate the next value.
   *
   * @param {number} valueBegin
   *   The first value in the sequence.
   *
   * @param {number} valueCountTotal
   *   There will be so many value be generated.
   *
   * @param {number} valueCountPerBoost
   *   Every so many value, generate so many values without delay intervalMilliseconds.
   */
  async* number_sequence(
    intervalMilliseconds,
    [ valueBegin, valueCountTotal, valueCountPerBoost ]
  ) {

    let bBoost = ( Math.random() < 0.5 );

    let countInBlock = 0;
    let value = valueBegin;
    for ( let i = 0; i < ( valueCountTotal - 1 ); ++i ) {

      if ( bBoost ) {
        yield { value: value }; // No delay.

      } else {
        yield delayedValue( intervalMilliseconds, { value: value } );
      }

      // Counting how many number has been generated in the boost (non-boost) block.
      ++countInBlock;
      if ( countInBlock == valueCountPerBoost ) {
        bBoost = !bBoost; // Toogle between boost and non-boost.
        countInBlock = 0;
      }

      ++value;
    }

    // Generate the final value by return;
    return { value: value };
  }

}

// In main document context (Not in worker context). Do nothing. (Should not happen)
if ( globalThis.document )
  return;

// In worker context.
AsyncWorker_Body_tester.Singleton = new AsyncWorker_Body_tester(); // Create worker body.

<commit_msg>AsyncWorker_Body_tester.Singleton<commit_after>import * as AsyncWorker from "../util/AsyncWorker.js";

/**
 * @return {Promise}
 *   Return a promise which will be resolved as specified value after specified
 * milliseconds.
 */
function delayedValue( milliseconds, value ) {
  return new Promise( ( resolve /*, reject*/ ) => {
    setTimeout( () => resolve( value ), milliseconds );
  } );
}

/**
 * The implementation of a neural network web worker.
 *
 */
class AsyncWorker_Body_tester extends AsyncWorker.Body {

  /** */
  constructor() {
    super(); // register callback from Worker_Proxy.
  }

  /** @override */
  async* disposeResources() {
    yield *super.disposeResources();
  }

  /**
   */
  async* initWorker( workerId ) {
    this.workerId = workerId;

    // Load libraries dynamically in global scope.
    {
      // [ globalThis.Pool,
      //   globalThis.Recyclable,
      //   globalThis.ValueMax,
      // ] = await Promise.all( [
      //   import( "../util/Pool.js" ),
      //   import( "../util/Recyclable.js" ),
      //   import( "../util/ValueMax.js" ),
      // ] );
    }

    return { value: true };
  }

  /**
   * @param {number} intervalMilliseconds
   *   How long to generate the next value.
   *
   * @param {number} valueBegin
   *   The first value in the sequence.
   *
   * @param {number} valueCountTotal
   *   There will be so many value be generated.
   *
   * @param {number} valueCountPerBoost
   *   Every so many value, generate so many values without delay intervalMilliseconds.
   */
  async* number_sequence(
    intervalMilliseconds,
    [ valueBegin, valueCountTotal, valueCountPerBoost ]
  ) {

    let bBoost = ( Math.random() < 0.5 );

    let countInBlock = 0;
    let value = valueBegin;
    for ( let i = 0; i < ( valueCountTotal - 1 ); ++i ) {

      if ( bBoost ) {
        yield { value: value }; // No delay.

      } else {
        yield delayedValue( intervalMilliseconds, { value: value } );
      }

      // Counting how many number has been generated in the boost (non-boost) block.
      ++countInBlock;
      if ( countInBlock == valueCountPerBoost ) {
        bBoost = !bBoost; // Toogle between boost and non-boost.
        countInBlock = 0;
      }

      ++value;
    }

    // Generate the final value by return;
    return { value: value };
  }

}

AsyncWorker_Body_tester.Singleton = new AsyncWorker_Body_tester(); // Create worker body.
<|endoftext|>


<commit_before>(function(win, undefined){
	win.det = {
		breakpointed : [],
		closedicon : "+",
		openicon : "-",
		support : (function() {
			var fakeBody,
				doc = document,
				de = doc.documentElement,
				bod = doc.body || (function() {
					fakeBody = true;
					return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
				}()),
				det = doc.createElement('details'),
				div = doc.createElement('div'),
				txt = doc.createTextNode('W'),
				ret;

			div.style.visibility = 'hidden';
			det.style.display = 'block';
			det.style.fontSize = '7px';
			det.style.lineHeight = '7px';

			div.appendChild(txt);
			det.appendChild(div);
			bod.appendChild(det);

			ret = div.offsetHeight === 0;

			bod.removeChild(det);
			fakeBody && bod.parentNode.removeChild(bod);

			return ret;
		})(),
		collapseDetails : function() {
			var windowWidth = window.innerWidth;

			for( var i = 0; i < det.breakpointed.length; i++ ) {
				var bkpt = det.breakpointed[i],
					open = win.matchMedia( bkpt.breakpoint ).matches;

				bkpt.el[ ( open ? "set" : "remove" ) + "Attribute" ]( "open", "open" );

				if( !det.support ) {
					var summary = bkpt.el.getElementsByTagName("summary")[0];

					det.polyfillToggle( summary, open );
				}
				// Update the aria attributes:
				win.det.ariaStates( bkpt.el, open );
			}
		},
		polyfillToggle : function( el, open ) {
			var siblings = function( oEl ) {
					var sibs = [],
					el = oEl.parentNode.firstChild;

				while( el = el.nextSibling ) {
					if( el.nodeType === 1 && el !== oEl ) {
						sibs.push( el );
					}
				}
				return sibs;
			},
			content = siblings( el );

			el.parentNode[ ( !open ? "set" : "remove" ) + "Attribute" ]( "open", "open" );
			el.firstChild.innerHTML = !open ? det.closedicon : det.openicon;

			for( var i = 0; i < content.length; i++ ) {
				content[i].style.display = open ? "" : "none";
			}
		},
		ariaStates : function( el, open ) {
			// Get first-level contents of the `details` element:
			var contents = el.children;

			// Set the "aria-expanded" true/false value on `details`:
			el.setAttribute( "aria-expanded", open );

			// Set the "aria-hidden" false/true value on all `details` contents except the summary:
			for( var j = 0; j < contents.length; j++ ) {
				contents[j].nodeName !== "SUMMARY" && contents[j].setAttribute( "aria-hidden", !open );
			}
		},
		init : function( ) {
			var details = document.getElementsByTagName("details");

			// Loop through each of the `details` elements.
			for( var i = 0; i < details.length; i++ ) {
				var detail = details[i],
					summary = detail.getElementsByTagName("summary")[0],
					breakpoint = detail.getAttribute( "data-media" ),
					open = detail.getAttribute("open") !== null;

				if( !summary ) {
					continue;
				}

				det.ariaStates( detail, open );

				// If there are breakpoints set, add the elements/details to a `breakpointed` array:
				if( breakpoint !== null ) {
					det.breakpointed.push( { "el" : detail, "breakpoint" : breakpoint } );
				}

				// Make sure we can navigate to the summary via the tab key:
				summary.setAttribute( "tabindex", "0" );

				if( !det.support ) {
					// Create the little toggle arrow element:
					var tog = document.createElement("div");

					tog.style.cssFloat = "left";
					tog.style.display = "inline";
					tog.style.paddingRight = ".5em";
					tog.style.fontSize = ".9em";
					tog.style.lineHeight = "1.2em";

					tog.setAttribute("class", "details-toggle");

					// Add the toggle to the start of the `summary`
					summary.insertBefore( tog, summary.firstChild );

					// Set the initial indicator arrow dealie.
					tog.innerHTML = !open ? det.openicon : det.closedicon;

					det.polyfillToggle( summary, open );
				}

				var clicked = function( e ) {
					var el = ( win.event ) ? event.srcElement : this,
						detail,
						open;

					while( el.nodeName && el.nodeName !== "SUMMARY" ) {
						el = el.parentNode;
					}

					detail = el.parentNode,
					open = detail.getAttribute("open") !== null;

					// Show/hide the `details` content in browsers that don’t support it natively.
					if( !det.support ) {
						det.polyfillToggle( el, open );
						det.ariaStates( detail, open );
					} else {
						// Update the aria attributes:
						det.ariaStates( detail, !open );
					}

					for( var k = 0; k < det.breakpointed.length; k++ ) {
						var bk = det.breakpointed[ k ];

						if( bk.el == el.parentNode ) {
							det.breakpointed.splice( det.breakpointed.indexOf( bk ), 1 );
						}
					}
				};

				if( win.addEventListener ) {
					summary.addEventListener("click", clicked, false );
				} else {
					summary.attachEvent( "onclick", clicked );
				}

				if( !det.support && win.addEventListener ) {
					// Trigger a click on the `summary` element when focused and enter or the spacebar are pressed:
					summary.addEventListener("keydown", function(e) {
						if( e.keyCode == 32 || e.keyCode == 13 ) {
							var ev = document.createEvent("HTMLEvents");

							ev.initEvent("click", true, true);
							this.dispatchEvent( ev, det.collapseDetails );
						}
					});
				}
			}
			// If we have breakpointed `details` in that array, trigger them now and on window resize.
			if( det.breakpointed.length && win.matchMedia && Array.indexOf ) {
				det.collapseDetails();

				if( !win.addEventListener ) {
					win.addEventListener = function( evt, cb ){
						return win.attachEvent( "on" + evt, cb );
					};
				}

				win.addEventListener( "resize", det.collapseDetails );
			}
		}
	}

	det.init();

})(this);
<commit_msg>Check the prototype. This was causing the data-media stuff to fail.<commit_after>(function(win, undefined){
	win.det = {
		breakpointed : [],
		closedicon : "+",
		openicon : "-",
		support : (function() {
			var fakeBody,
				doc = document,
				de = doc.documentElement,
				bod = doc.body || (function() {
					fakeBody = true;
					return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
				}()),
				det = doc.createElement('details'),
				div = doc.createElement('div'),
				txt = doc.createTextNode('W'),
				ret;

			div.style.visibility = 'hidden';
			det.style.display = 'block';
			det.style.fontSize = '7px';
			det.style.lineHeight = '7px';

			div.appendChild(txt);
			det.appendChild(div);
			bod.appendChild(det);

			ret = div.offsetHeight === 0;

			bod.removeChild(det);
			fakeBody && bod.parentNode.removeChild(bod);

			return ret;
		})(),
		collapseDetails : function() {
			var windowWidth = window.innerWidth;

			for( var i = 0; i < det.breakpointed.length; i++ ) {
				var bkpt = det.breakpointed[i],
					open = win.matchMedia( bkpt.breakpoint ).matches;

				bkpt.el[ ( open ? "set" : "remove" ) + "Attribute" ]( "open", "open" );

				if( !det.support ) {
					var summary = bkpt.el.getElementsByTagName("summary")[0];

					det.polyfillToggle( summary, open );
				}
				// Update the aria attributes:
				win.det.ariaStates( bkpt.el, open );
			}
		},
		polyfillToggle : function( el, open ) {
			var siblings = function( oEl ) {
					var sibs = [],
					el = oEl.parentNode.firstChild;

				while( el = el.nextSibling ) {
					if( el.nodeType === 1 && el !== oEl ) {
						sibs.push( el );
					}
				}
				return sibs;
			},
			content = siblings( el );

			el.parentNode[ ( !open ? "set" : "remove" ) + "Attribute" ]( "open", "open" );
			el.firstChild.innerHTML = !open ? det.closedicon : det.openicon;

			for( var i = 0; i < content.length; i++ ) {
				content[i].style.display = open ? "" : "none";
			}
		},
		ariaStates : function( el, open ) {
			// Get first-level contents of the `details` element:
			var contents = el.children;

			// Set the "aria-expanded" true/false value on `details`:
			el.setAttribute( "aria-expanded", open );

			// Set the "aria-hidden" false/true value on all `details` contents except the summary:
			for( var j = 0; j < contents.length; j++ ) {
				contents[j].nodeName !== "SUMMARY" && contents[j].setAttribute( "aria-hidden", !open );
			}
		},
		init : function( ) {
			var details = document.getElementsByTagName("details");

			// Loop through each of the `details` elements.
			for( var i = 0; i < details.length; i++ ) {
				var detail = details[i],
					summary = detail.getElementsByTagName("summary")[0],
					breakpoint = detail.getAttribute( "data-media" ),
					open = detail.getAttribute("open") !== null;

				if( !summary ) {
					continue;
				}

				det.ariaStates( detail, open );

				// If there are breakpoints set, add the elements/details to a `breakpointed` array:
				if( breakpoint !== null ) {
					det.breakpointed.push( { "el" : detail, "breakpoint" : breakpoint } );
				}

				// Make sure we can navigate to the summary via the tab key:
				summary.setAttribute( "tabindex", "0" );

				if( !det.support ) {
					// Create the little toggle arrow element:
					var tog = document.createElement("div");

					tog.style.cssFloat = "left";
					tog.style.display = "inline";
					tog.style.paddingRight = ".5em";
					tog.style.fontSize = ".9em";
					tog.style.lineHeight = "1.2em";

					tog.setAttribute("class", "details-toggle");

					// Add the toggle to the start of the `summary`
					summary.insertBefore( tog, summary.firstChild );

					// Set the initial indicator arrow dealie.
					tog.innerHTML = !open ? det.openicon : det.closedicon;

					det.polyfillToggle( summary, open );
				}

				var clicked = function( e ) {
					var el = ( win.event ) ? event.srcElement : this,
						detail,
						open;

					while( el.nodeName && el.nodeName !== "SUMMARY" ) {
						el = el.parentNode;
					}

					detail = el.parentNode,
					open = detail.getAttribute("open") !== null;

					// Show/hide the `details` content in browsers that don’t support it natively.
					if( !det.support ) {
						det.polyfillToggle( el, open );
						det.ariaStates( detail, open );
					} else {
						// Update the aria attributes:
						det.ariaStates( detail, !open );
					}

					for( var k = 0; k < det.breakpointed.length; k++ ) {
						var bk = det.breakpointed[ k ];

						if( bk.el == el.parentNode ) {
							det.breakpointed.splice( det.breakpointed.indexOf( bk ), 1 );
						}
					}
				};

				if( win.addEventListener ) {
					summary.addEventListener("click", clicked, false );
				} else {
					summary.attachEvent( "onclick", clicked );
				}

				if( !det.support && win.addEventListener ) {
					// Trigger a click on the `summary` element when focused and enter or the spacebar are pressed:
					summary.addEventListener("keydown", function(e) {
						if( e.keyCode == 32 || e.keyCode == 13 ) {
							var ev = document.createEvent("HTMLEvents");

							ev.initEvent("click", true, true);
							this.dispatchEvent( ev, det.collapseDetails );
						}
					});
				}
			}
			// If we have breakpointed `details` in that array, trigger them now and on window resize.
			if( det.breakpointed.length && win.matchMedia && Array.prototype.indexOf ) {
				det.collapseDetails();

				if( !win.addEventListener ) {
					win.addEventListener = function( evt, cb ){
						return win.attachEvent( "on" + evt, cb );
					};
				}

				win.addEventListener( "resize", det.collapseDetails );
			}
		}
	}

	det.init();

})(this);
<|endoftext|>


<commit_before>/*
module.exports = function (grunt) {
	grunt.registerTask('prod', [
		'compileAssets',
		'concat',
		'uglify',
		'cssmin',
		'sails-linker:prodJs',
		'sails-linker:prodStyles',
		'sails-linker:devTpl',
		'sails-linker:prodJsJade',
		'sails-linker:prodStylesJade',
		'sails-linker:devTplJade'
	]);
};
*/
module.exports = function (grunt) {
	grunt.registerTask('prod', [
		'compileAssets',
		'concat',
		'uglify',
		'cssmin',
		'sails-linker:prodJs',
		'sails-linker:prodStyles',
		'sails-linker:devTpl',
		'sails-linker:prodJsJade',
		'sails-linker:prodStylesJade',
		'sails-linker:devTplJade'
	]);
};<commit_msg>Installing bower to openshift<commit_after>/*
module.exports = function (grunt) {
	grunt.registerTask('prod', [
		'compileAssets',
		'concat',
		'uglify',
		'cssmin',
		'sails-linker:prodJs',
		'sails-linker:prodStyles',
		'sails-linker:devTpl',
		'sails-linker:prodJsJade',
		'sails-linker:prodStylesJade',
		'sails-linker:devTplJade'
	]);
};
*/
module.exports = function (grunt) {
	grunt.registerTask('prod', [
		'compileAssets'
	]);
};<|endoftext|>


<commit_before>var dns = require('dns'),
    cachedRecords = {},
    nextGarbageCollection = 0,
    currentCollectionTimeout = null;
delete cachedRecords.a; //don't let V8 try to optimize

function scheduleGarbageCollection(time) {
    if (nextGarbageCollection === 0 || time < nextGarbageCollection) {
        if (currentCollectionTimeout !== null) {
            clearTimeout(currentCollectionTimeout);
        }
        nextGarbageCollection = time;
        currentCollectionTimeout = setTimeout(collectGarbage, time - Date.now());
        //don't let garbage collection prevent node from killing the app
        currentCollectionTimeout.unref();
    }
}

function collectGarbage() {
    //we can't reliably create a timeout in < 10 ms so just clean those now
    var now = Date.now() + 10,
        nextCollection = Infinity,
        name;
    for (name in cachedRecords) {
        if (!cachedRecords.hasOwnProperty(name)) {
            continue;
        }
        if (cachedRecords[name].expire <= now) {
            delete cachedRecords[name];
            continue;
        }
        nextCollection = Math.min(cachedRecords[name].expire, nextCollection);
    }
    nextGarbageCollection = 0;
    currentCollectionTimeout = null;
    if (isFinite(nextCollection)) {
        scheduleGarbageCollection(nextCollection);
    }
}

module.exports.setServers = function(servers) {
    dns.setServers(servers);
};

//via: http://stackoverflow.com/questions/9716468/is-there-any-function-like-isnumeric-in-javascript-to-validate-numbers
//except we should allow infinity so there's no isFinite check
function isNumber(n) {
    return !isNaN(parseFloat(n));
}

/**
 * Lower priority means more preferred
 * Higher weight means more preferred
 *
 * First sort by priorities then sort by weights
 */
function compareTargets(a, b) {
    //if the one of them has a non-number priority then bail
    if (isNaN(a.priority) && !isNaN(b.priority)) {
        return 1;
    }
    if (isNaN(b.priority) || a.priority < b.priority) {
        //a is more preferred
        return -1;
    }
    if (b.priority < a.priority) {
        //b is more preferred
        return 1;
    }
    //at this point a.priority is equal to
    if (isNaN(a.weight)) {
        //if b.weight ALSO isn't a number then treat them equal
        return !isNaN(b.weight) ? 1 : 0;
    }
    if (isNaN(b.weight) || a.weight > b.weight) {
        //a is more preferred
        return -1;
    }
    if (b.weight > a.weight) {
        //b is more preferred
        return 1;
    }
    return 0;
}

function sortPriorityWeight(targets) {
    targets.sort(compareTargets);
}

function randomizeWeights(targets) {
    var totalWeights = 0,
        lastPriority = Infinity,
        i = 0,
        tmp = null;
    for (; i < targets.length; i++) {
        if (targets[i].priority !== lastPriority) {
            lastPriority = targets[i].priority;
            continue;
        }
        //todo: better handle weights that are not numbers
        if (isNaN(targets[i - 1].weight)) {
            //just flip the weight that is a number to before the non-number weight
            if (!isNaN(targets[i].weight)) {
                tmp = targets[i - 1];
                targets[i - 1] = targets[i];
                targets[i] = tmp;
            }
            continue;
        }
        //leave the previous numeric weight before the non-number
        if (isNaN(targets[i].weight)) {
            continue;
        }
        totalWeights = targets[i - 1].weight + targets[i].weight;
        //flip them randomly based on weight
        if ((Math.random() * totalWeights) > targets[i - 1].weight) {
            tmp = targets[i - 1];
            targets[i - 1] = targets[i];
            targets[i] = tmp;
        }
    }
}

function SRVTarget(result) {
    this.name = result.name || '';
    this.port = result.port || 0;
    this.priority = parseInt(result.priority, 10);
    this.weight = parseInt(result.weight, 10);
    this.address = '';
    this.addressFamily = 0;
}
SRVTarget.prototype.resolve = function(cb) {
    if (typeof cb !== 'function') {
        throw new TypeError('callback must be a function');
    }
    if (this.address !== '' && this.addressFamily === 4) {
        cb(null, this.address);
        return;
    }
    dns.resolve4(this.name, function(err, addresses) {
        if (err) {
            cb(err, null);
            return;
        }
        this.address = addresses[0];
        this.addressFamily = 4;
        cb(null, this.address);
    }.bind(this));
};
SRVTarget.prototype.resolve4 = SRVTarget.prototype.resolve;
SRVTarget.prototype.resolve6 = function(cb) {
    if (typeof cb !== 'function') {
        throw new TypeError('callback must be a function');
    }
    if (this.address !== '' && this.addressFamily === 6) {
        cb(null, this.address);
        return;
    }
    dns.resolve6(this.name, function(err, addresses) {
        if (err) {
            cb(err, null);
            return;
        }
        this.address = addresses[0];
        this.addressFamily = 6;
        cb(null, this.address);
    }.bind(this));
};
SRVTarget.prototype.lookup = function(cb) {
    if (typeof cb !== 'function') {
        throw new TypeError('callback must be a function');
    }
    if (this.address !== '') {
        cb(null, this.address);
        return;
    }
    dns.lookup(this.name, function(err, address) {
        if (err) {
            cb(err, null);
            return;
        }
        this.address = address;
        //todo: detect the addressFamily
        cb(null, address)
    }.bind(this));
};

function wrapTargets(targets) {
    for (var i = 0; i < targets.length; i++) {
        targets[i] = new SRVTarget(targets[i]);
    }
    return targets;
}

function getTargets(options) {
    var hostname = options.hostname,
        callback = options.callback,
        cache = options.cache || 0,
        onlyFirst = options.single || false,
        sort = options.sort || sortPriorityWeight;
    if (typeof callback !== 'function') {
        if (typeof cache === 'function') {
            callback = cache;
            cache = 0;
        } else {
            throw new TypeError('callback must be a function');
        }
    }
    if (cache > 0) {
        if (!isNumber(cache)) {
            throw new TypeError('cache must be a number');
        }
        if (cachedRecords.hasOwnProperty(hostname) && cachedRecords[hostname].expire > Date.now()) {
            callback(null, (onlyFirst ? (cachedRecords[hostname].targets[0] || null) : cachedRecords[hostname].targets.slice()));
            return;
        }
    }
    dns.resolveSrv(hostname, function(err, targets) {
        if (err || !Array.isArray(targets)) {
            callback(err, null);
            return;
        }

        wrapTargets(targets);
        sort(targets);

        if (cache > 0 && targets.length) {
            cachedRecords[hostname] = {
                targets: targets,
                expire: Date.now() + cache
            };
            scheduleGarbageCollection(cachedRecords[hostname].expire);
            targets = targets.slice();
        }
        callback(null, (onlyFirst ? (targets[0] || null) : targets));
    });
}

function getRandomTargets(options) {
    var callback = options.callback,
        onlyFirst = options.single || false;
    if (typeof callback !== 'function') {
        throw new TypeError('callback must be a function');
    }
    options.callback = function(err, targets) {
        if (err) {
            callback(err, null);
            return;
        }
        randomizeWeights(targets);
        callback(null, (onlyFirst ? (targets[0] || null) : targets));
    };
    // we need to always get back all the targets for randomizeWeights to work
    options.single = false;
    //if you passed another sort it won't work...
    if (options.hasOwnProperty('sort')) {
        throw new TypeError('random methods do not support a custom sort function');
    }
    return getTargets(options);
}

exports.getTargets = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getTargets({
        hostname: hostname,
        cache: cache,
        callback: callback
    });
};

exports.getTarget = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getTargets({
        hostname: hostname,
        cache: cache,
        callback: callback,
        single: true
    });
};

exports.getRandomTargets = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getRandomTargets({
        hostname: hostname,
        cache: cache,
        callback: callback
    });
};

exports.getRandomTarget = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getRandomTargets({
        hostname: hostname,
        cache: cache,
        callback: callback,
        single: true
    });
};

//mostly only available for tests
exports.sortTargets = function(targets) {
    targets.sort(compareTargets);
    return targets;
};

<commit_msg>Detect addressFamily in lookup<commit_after>var dns = require('dns'),
    net = require('net'),
    cachedRecords = {},
    nextGarbageCollection = 0,
    currentCollectionTimeout = null;
delete cachedRecords.a; //don't let V8 try to optimize

function scheduleGarbageCollection(time) {
    if (nextGarbageCollection === 0 || time < nextGarbageCollection) {
        if (currentCollectionTimeout !== null) {
            clearTimeout(currentCollectionTimeout);
        }
        nextGarbageCollection = time;
        currentCollectionTimeout = setTimeout(collectGarbage, time - Date.now());
        //don't let garbage collection prevent node from killing the app
        currentCollectionTimeout.unref();
    }
}

function collectGarbage() {
    //we can't reliably create a timeout in < 10 ms so just clean those now
    var now = Date.now() + 10,
        nextCollection = Infinity,
        name;
    for (name in cachedRecords) {
        if (!cachedRecords.hasOwnProperty(name)) {
            continue;
        }
        if (cachedRecords[name].expire <= now) {
            delete cachedRecords[name];
            continue;
        }
        nextCollection = Math.min(cachedRecords[name].expire, nextCollection);
    }
    nextGarbageCollection = 0;
    currentCollectionTimeout = null;
    if (isFinite(nextCollection)) {
        scheduleGarbageCollection(nextCollection);
    }
}

module.exports.setServers = function(servers) {
    dns.setServers(servers);
};

//via: http://stackoverflow.com/questions/9716468/is-there-any-function-like-isnumeric-in-javascript-to-validate-numbers
//except we should allow infinity so there's no isFinite check
function isNumber(n) {
    return !isNaN(parseFloat(n));
}

/**
 * Lower priority means more preferred
 * Higher weight means more preferred
 *
 * First sort by priorities then sort by weights
 */
function compareTargets(a, b) {
    //if the one of them has a non-number priority then bail
    if (isNaN(a.priority) && !isNaN(b.priority)) {
        return 1;
    }
    if (isNaN(b.priority) || a.priority < b.priority) {
        //a is more preferred
        return -1;
    }
    if (b.priority < a.priority) {
        //b is more preferred
        return 1;
    }
    //at this point a.priority is equal to
    if (isNaN(a.weight)) {
        //if b.weight ALSO isn't a number then treat them equal
        return !isNaN(b.weight) ? 1 : 0;
    }
    if (isNaN(b.weight) || a.weight > b.weight) {
        //a is more preferred
        return -1;
    }
    if (b.weight > a.weight) {
        //b is more preferred
        return 1;
    }
    return 0;
}

function sortPriorityWeight(targets) {
    targets.sort(compareTargets);
}

function randomizeWeights(targets) {
    var totalWeights = 0,
        lastPriority = Infinity,
        i = 0,
        tmp = null;
    for (; i < targets.length; i++) {
        if (targets[i].priority !== lastPriority) {
            lastPriority = targets[i].priority;
            continue;
        }
        //todo: better handle weights that are not numbers
        if (isNaN(targets[i - 1].weight)) {
            //just flip the weight that is a number to before the non-number weight
            if (!isNaN(targets[i].weight)) {
                tmp = targets[i - 1];
                targets[i - 1] = targets[i];
                targets[i] = tmp;
            }
            continue;
        }
        //leave the previous numeric weight before the non-number
        if (isNaN(targets[i].weight)) {
            continue;
        }
        totalWeights = targets[i - 1].weight + targets[i].weight;
        //flip them randomly based on weight
        if ((Math.random() * totalWeights) > targets[i - 1].weight) {
            tmp = targets[i - 1];
            targets[i - 1] = targets[i];
            targets[i] = tmp;
        }
    }
}

function SRVTarget(result) {
    this.name = result.name || '';
    this.port = result.port || 0;
    this.priority = parseInt(result.priority, 10);
    this.weight = parseInt(result.weight, 10);
    this.address = '';
    this.addressFamily = 0;
}
SRVTarget.prototype.resolve = function(cb) {
    if (typeof cb !== 'function') {
        throw new TypeError('callback must be a function');
    }
    if (this.address !== '' && this.addressFamily === 4) {
        cb(null, this.address);
        return;
    }
    dns.resolve4(this.name, function(err, addresses) {
        if (err) {
            cb(err, null);
            return;
        }
        this.address = addresses[0];
        this.addressFamily = 4;
        cb(null, this.address);
    }.bind(this));
};
SRVTarget.prototype.resolve4 = SRVTarget.prototype.resolve;
SRVTarget.prototype.resolve6 = function(cb) {
    if (typeof cb !== 'function') {
        throw new TypeError('callback must be a function');
    }
    if (this.address !== '' && this.addressFamily === 6) {
        cb(null, this.address);
        return;
    }
    dns.resolve6(this.name, function(err, addresses) {
        if (err) {
            cb(err, null);
            return;
        }
        this.address = addresses[0];
        this.addressFamily = 6;
        cb(null, this.address);
    }.bind(this));
};
SRVTarget.prototype.lookup = function(cb) {
    if (typeof cb !== 'function') {
        throw new TypeError('callback must be a function');
    }
    if (this.address !== '') {
        cb(null, this.address);
        return;
    }
    dns.lookup(this.name, function(err, address) {
        if (err) {
            cb(err, null);
            return;
        }
        this.address = address;
        this.addressFamily = net.isIP(address);
        cb(null, address);
    }.bind(this));
};

function wrapTargets(targets) {
    for (var i = 0; i < targets.length; i++) {
        targets[i] = new SRVTarget(targets[i]);
    }
    return targets;
}

function getTargets(options) {
    var hostname = options.hostname,
        callback = options.callback,
        cache = options.cache || 0,
        onlyFirst = options.single || false,
        sort = options.sort || sortPriorityWeight;
    if (typeof callback !== 'function') {
        if (typeof cache === 'function') {
            callback = cache;
            cache = 0;
        } else {
            throw new TypeError('callback must be a function');
        }
    }
    if (cache > 0) {
        if (!isNumber(cache)) {
            throw new TypeError('cache must be a number');
        }
        if (cachedRecords.hasOwnProperty(hostname) && cachedRecords[hostname].expire > Date.now()) {
            callback(null, (onlyFirst ? (cachedRecords[hostname].targets[0] || null) : cachedRecords[hostname].targets.slice()));
            return;
        }
    }
    dns.resolveSrv(hostname, function(err, targets) {
        if (err || !Array.isArray(targets)) {
            callback(err, null);
            return;
        }

        wrapTargets(targets);
        sort(targets);

        if (cache > 0 && targets.length) {
            cachedRecords[hostname] = {
                targets: targets,
                expire: Date.now() + cache
            };
            scheduleGarbageCollection(cachedRecords[hostname].expire);
            targets = targets.slice();
        }
        callback(null, (onlyFirst ? (targets[0] || null) : targets));
    });
}

function getRandomTargets(options) {
    var callback = options.callback,
        onlyFirst = options.single || false;
    if (typeof callback !== 'function') {
        throw new TypeError('callback must be a function');
    }
    options.callback = function(err, targets) {
        if (err) {
            callback(err, null);
            return;
        }
        randomizeWeights(targets);
        callback(null, (onlyFirst ? (targets[0] || null) : targets));
    };
    // we need to always get back all the targets for randomizeWeights to work
    options.single = false;
    //if you passed another sort it won't work...
    if (options.hasOwnProperty('sort')) {
        throw new TypeError('random methods do not support a custom sort function');
    }
    return getTargets(options);
}

exports.getTargets = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getTargets({
        hostname: hostname,
        cache: cache,
        callback: callback
    });
};

exports.getTarget = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getTargets({
        hostname: hostname,
        cache: cache,
        callback: callback,
        single: true
    });
};

exports.getRandomTargets = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getRandomTargets({
        hostname: hostname,
        cache: cache,
        callback: callback
    });
};

exports.getRandomTarget = function(hostname, cache, callback) {
    if (typeof cache === 'function') {
        callback = cache;
        cache = 0;
    }
    return getRandomTargets({
        hostname: hostname,
        cache: cache,
        callback: callback,
        single: true
    });
};

//mostly only available for tests
exports.sortTargets = function(targets) {
    targets.sort(compareTargets);
    return targets;
};

<|endoftext|>