<commit_before>import { expect } from 'chai'
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import { screen, fireEvent, waitFor, cleanup } from '@testing-library/react'
import PdfPreview from '../../../../../frontend/js/features/pdf-preview/components/pdf-preview'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import nock from 'nock'
import path from 'path'
import fs from 'fs'
import { cloneDeep } from 'lodash'

const examplePDF = path.join(__dirname, '../fixtures/test-example.pdf')
const corruptPDF = path.join(__dirname, '../fixtures/test-example-corrupt.pdf')

const outputFiles = [
  {
    path: 'output.pdf',
    build: '123',
    url: '/build/output.pdf',
    type: 'pdf',
  },
  {
    path: 'output.bbl',
    build: '123',
    url: '/build/output.bbl',
    type: 'bbl',
  },
  {
    path: 'output.bib',
    build: '123',
    url: '/build/output.bib',
    type: 'bib',
  },
  {
    path: 'example.txt',
    build: '123',
    url: '/build/example.txt',
    type: 'txt',
  },
  {
    path: 'output.log',
    build: '123',
    url: '/build/output.log',
    type: 'log',
  },
  {
    path: 'output.blg',
    build: '123',
    url: '/build/output.blg',
    type: 'blg',
  },
]

const mockCompile = () =>
  fetchMock.post('express:/project/:projectId/compile', {
    body: {
      status: 'success',
      clsiServerId: 'foo',
      compileGroup: 'priority',
      pdfDownloadDomain: 'https://clsi.test-overleaf.com',
      outputFiles: cloneDeep(outputFiles),
    },
  })

const mockCompileError = status =>
  fetchMock.post('express:/project/:projectId/compile', {
    body: {
      status,
      clsiServerId: 'foo',
      compileGroup: 'priority',
    },
  })

const mockValidationProblems = validationProblems =>
  fetchMock.post('express:/project/:projectId/compile', {
    body: {
      status: 'validation-problems',
      validationProblems,
      clsiServerId: 'foo',
      compileGroup: 'priority',
    },
  })

const mockClearCache = () =>
  fetchMock.delete('express:/project/:projectId/output', 204)

const mockValidPdf = () => {
  nock('https://clsi.test-overleaf.com')
    .get(/^\/build\/output\.pdf/)
    .replyWithFile(200, examplePDF)
}

const defaultFileResponses = {
  '/build/output.pdf': () => fs.createReadStream(examplePDF),
  '/build/output.blg': 'This is BibTeX, Version 4.0', // FIXME
  '/build/output.log': `
The LaTeX compiler output
  * With a lot of details

Wrapped in an HTML <pre> element with
      preformatted text which is to be presented exactly
            as written in the HTML file

                                              (whitespace included™)

The text is typically rendered using a non-proportional ("monospace") font.

LaTeX Font Info:    External font \`cmex10' loaded for size
(Font)              <7> on input line 18.
LaTeX Font Info:    External font \`cmex10' loaded for size
(Font)              <5> on input line 18.
! Undefined control sequence.
<recently read> \\Zlpha

 main.tex, line 23

`,
}

const mockBuildFile = (responses = defaultFileResponses) => {
  fetchMock.get('begin:https://clsi.test-overleaf.com/', _url => {
    const url = new URL(_url, 'https://clsi.test-overleaf.com')

    if (url.pathname in responses) {
      return responses[url.pathname]
    }

    return 404
  })

  fetchMock.get('express:/build/:file', (_url, options, request) => {
    const url = new URL(_url, 'https://example.com')

    if (url.pathname in responses) {
      return responses[url.pathname]
    }

    return 404
  })
}

const storeAndFireEvent = (key, value) => {
  localStorage.setItem(key, value)
  fireEvent(window, new StorageEvent('storage', { key }))
}

const scope = {
  settings: {
    syntaxValidation: false,
  },
  editor: {
    sharejs_doc: {
      doc_id: 'test-doc',
      getSnapshot: () => 'some doc content',
    },
  },
}

describe('<PdfPreview/>', function () {
  let clock

  beforeEach(function () {
    window.showNewPdfPreview = true
    clock = sinon.useFakeTimers({
      shouldAdvanceTime: true,
      now: Date.now(),
    })
    nock.cleanAll()
  })

  afterEach(function () {
    window.showNewPdfPreview = undefined
    clock.runAll()
    clock.restore()
    fetchMock.reset()
    localStorage.clear()
    sinon.restore()
  })

  it('renders the PDF preview', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })
  })

  it('runs a compile when the Recompile button is pressed', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    mockValidPdf()

    // press the Recompile button => compile
    const button = screen.getByRole('button', { name: 'Recompile' })
    button.click()
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    expect(fetchMock.calls()).to.have.length(6)
  })

  it('runs a compile on doc change if autocompile is enabled', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // switch on auto compile
    storeAndFireEvent('autocompile_enabled:project123', true)

    mockValidPdf()

    // fire a doc:changed event => compile
    fireEvent(window, new CustomEvent('doc:changed'))
    clock.tick(2000) // AUTO_COMPILE_DEBOUNCE

    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    expect(fetchMock.calls()).to.have.length(6)
  })

  it('does not run a compile on doc change if autocompile is disabled', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // make sure auto compile is switched off
    storeAndFireEvent('autocompile_enabled:project123', false)

    // fire a doc:changed event => no compile
    fireEvent(window, new CustomEvent('doc:changed'))
    clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
    screen.getByRole('button', { name: 'Recompile' })

    expect(fetchMock.calls()).to.have.length(3)
  })

  it('does not run a compile on doc change if autocompile is blocked by syntax check', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, {
      scope: {
        ...scope,
        'settings.syntaxValidation': true, // enable linting in the editor
        hasLintingError: true, // mock a linting error
      },
    })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // switch on auto compile and syntax checking
    storeAndFireEvent('autocompile_enabled:project123', true)
    storeAndFireEvent('stop_on_validation_error:project123', true)

    // fire a doc:changed event => no compile
    fireEvent(window, new CustomEvent('doc:changed'))
    clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
    screen.getByRole('button', { name: 'Recompile' })
    await screen.findByText('Code check failed')

    expect(fetchMock.calls()).to.have.length(3)
  })

  it('displays an error message if there was a compile error', async function () {
    const compileErrorStatuses = {
      'clear-cache':
        'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
      'clsi-maintenance':
        'The compile servers are down for maintenance, and will be back shortly.',
      'compile-in-progress':
        'A previous compile is still running. Please wait a minute and try compiling again.',
      exited: 'Server Error',
      failure: 'No PDF',
      generic: 'Server Error',
      'project-too-large': 'Project too large',
      'rate-limited': 'Compile rate limit hit',
      terminated: 'Compilation cancelled',
      timedout: 'Timed out',
      'too-recently-compiled':
        'This project was compiled very recently, so this compile has been skipped.',
      unavailable:
        'Sorry, the compile server for your project was temporarily unavailable. Please try again in a few moments.',
      foo:
        'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
    }

    for (const [status, message] of Object.entries(compileErrorStatuses)) {
      cleanup()
      fetchMock.restore()
      mockCompileError(status)

      renderWithEditorContext(<PdfPreview />, { scope })

      // wait for "compile on load" to finish
      await screen.findByRole('button', { name: 'Compiling…' })
      await screen.findByRole('button', { name: 'Recompile' })

      screen.getByText(message)
    }
  })

  it('displays expandable raw logs', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    // pretend that the content is large enough to trigger a "collapse"
    // (in jsdom these values are always zero)
    sinon.stub(HTMLElement.prototype, 'scrollHeight').value(500)
    sinon.stub(HTMLElement.prototype, 'scrollWidth').value(500)

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    const logsButton = screen.getByRole('button', { name: 'View logs' })
    logsButton.click()

    await screen.findByRole('button', { name: 'View PDF' })

    // expand the log
    const [expandButton] = screen.getAllByRole('button', { name: 'Expand' })
    expandButton.click()

    // collapse the log
    const [collapseButton] = screen.getAllByRole('button', { name: 'Collapse' })
    collapseButton.click()
  })

  it('displays error messages if there were validation problems', async function () {
    const validationProblems = {
      sizeCheck: {
        resources: [
          { path: 'foo/bar', kbSize: 76221 },
          { path: 'bar/baz', kbSize: 2342 },
        ],
      },
      mainFile: true,
      conflictedPaths: [
        {
          path: 'foo/bar',
        },
        {
          path: 'foo/baz',
        },
      ],
    }

    mockValidationProblems(validationProblems)

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    screen.getByText('Project too large')
    screen.getByText('Unknown main document')
    screen.getByText('Conflicting Paths Found')

    expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
    expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be
      .false // TODO: actual path
  })

  it('sends a clear cache request when the button is pressed', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    const logsButton = screen.getByRole('button', {
      name: 'View logs',
    })
    logsButton.click()

    const clearCacheButton = await screen.findByRole('button', {
      name: 'Clear cached files',
    })
    expect(clearCacheButton.hasAttribute('disabled')).to.be.false

    mockClearCache()

    // click the button
    clearCacheButton.click()
    expect(clearCacheButton.hasAttribute('disabled')).to.be.true
    await waitFor(() => {
      expect(clearCacheButton.hasAttribute('disabled')).to.be.false
    })

    expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
    expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be.true // TODO: actual path
  })

  it('handle "recompile from scratch"', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // show the logs UI
    const logsButton = screen.getByRole('button', {
      name: 'View logs',
    })
    logsButton.click()

    const clearCacheButton = await screen.findByRole('button', {
      name: 'Clear cached files',
    })
    expect(clearCacheButton.hasAttribute('disabled')).to.be.false

    mockValidPdf()
    mockClearCache()

    const recompileFromScratch = screen.getByRole('menuitem', {
      name: 'Recompile from scratch',
      hidden: true,
    })
    recompileFromScratch.click()

    expect(clearCacheButton.hasAttribute('disabled')).to.be.true

    // wait for compile to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
    expect(fetchMock.called('express:/project/:projectId/output')).to.be.true
    expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be.true // TODO: actual path
  })

  it('shows an error for an invalid URL', async function () {
    mockCompile()
    mockBuildFile()

    nock('https://clsi.test-overleaf.com')
      .get(/^\/build\/output.pdf/)
      .replyWithError({
        message: 'something awful happened',
        code: 'AWFUL_ERROR',
      })

    const { rerender } = renderWithEditorContext(<PdfPreview />, { scope })

    await screen.findByText('Something went wrong while rendering this PDF.')
    expect(screen.queryByLabelText('Page 1')).to.not.exist

    expect(nock.isDone()).to.be.true

    mockValidPdf()

    rerender(<PdfPreview />)

    await screen.findByLabelText('Page 1')
    expect(screen.queryByText('Something went wrong while rendering this PDF.'))
      .to.not.exist
  })

  it('shows an error for a corrupt PDF', async function () {
    mockCompile()
    mockBuildFile()

    nock('https://clsi.test-overleaf.com')
      .get(/^\/build\/output.pdf/)
      .replyWithFile(200, corruptPDF)

    const { rerender } = renderWithEditorContext(<PdfPreview />, { scope })

    await screen.findByText('Something went wrong while rendering this PDF.')
    expect(screen.queryByLabelText('Page 1')).to.not.exist

    expect(nock.isDone()).to.be.true

    mockValidPdf()

    rerender(<PdfPreview />)

    await screen.findByLabelText('Page 1')
    expect(screen.queryByText('Something went wrong while rendering this PDF.'))
      .to.not.exist
  })
})
<commit_msg>Update pdf-preview.test.js (#5656)<commit_after>import { expect } from 'chai'
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import { screen, fireEvent, waitFor, cleanup } from '@testing-library/react'
import PdfPreview from '../../../../../frontend/js/features/pdf-preview/components/pdf-preview'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import nock from 'nock'
import path from 'path'
import fs from 'fs'
import { cloneDeep } from 'lodash'

const examplePDF = path.join(__dirname, '../fixtures/test-example.pdf')
const corruptPDF = path.join(__dirname, '../fixtures/test-example-corrupt.pdf')

const outputFiles = [
  {
    path: 'output.pdf',
    build: '123',
    url: '/build/output.pdf',
    type: 'pdf',
  },
  {
    path: 'output.bbl',
    build: '123',
    url: '/build/output.bbl',
    type: 'bbl',
  },
  {
    path: 'output.bib',
    build: '123',
    url: '/build/output.bib',
    type: 'bib',
  },
  {
    path: 'example.txt',
    build: '123',
    url: '/build/example.txt',
    type: 'txt',
  },
  {
    path: 'output.log',
    build: '123',
    url: '/build/output.log',
    type: 'log',
  },
  {
    path: 'output.blg',
    build: '123',
    url: '/build/output.blg',
    type: 'blg',
  },
]

const mockCompile = () =>
  fetchMock.post('express:/project/:projectId/compile', {
    body: {
      status: 'success',
      clsiServerId: 'foo',
      compileGroup: 'priority',
      pdfDownloadDomain: 'https://clsi.test-overleaf.com',
      outputFiles: cloneDeep(outputFiles),
    },
  })

const mockCompileError = status =>
  fetchMock.post('express:/project/:projectId/compile', {
    body: {
      status,
      clsiServerId: 'foo',
      compileGroup: 'priority',
    },
  })

const mockValidationProblems = validationProblems =>
  fetchMock.post('express:/project/:projectId/compile', {
    body: {
      status: 'validation-problems',
      validationProblems,
      clsiServerId: 'foo',
      compileGroup: 'priority',
    },
  })

const mockClearCache = () =>
  fetchMock.delete('express:/project/:projectId/output', 204)

const mockValidPdf = () => {
  nock('https://clsi.test-overleaf.com')
    .get(/^\/build\/output\.pdf/)
    .replyWithFile(200, examplePDF)
}

const defaultFileResponses = {
  '/build/output.pdf': () => fs.createReadStream(examplePDF),
  '/build/output.blg': 'This is BibTeX, Version 4.0', // FIXME
  '/build/output.log': `
The LaTeX compiler output
  * With a lot of details

Wrapped in an HTML <pre> element with
      preformatted text which is to be presented exactly
            as written in the HTML file

                                              (whitespace included™)

The text is typically rendered using a non-proportional ("monospace") font.

LaTeX Font Info:    External font \`cmex10' loaded for size
(Font)              <7> on input line 18.
LaTeX Font Info:    External font \`cmex10' loaded for size
(Font)              <5> on input line 18.
! Undefined control sequence.
<recently read> \\Zlpha

 main.tex, line 23

`,
}

const mockBuildFile = (responses = defaultFileResponses) => {
  fetchMock.get('begin:https://clsi.test-overleaf.com/', _url => {
    const url = new URL(_url, 'https://clsi.test-overleaf.com')

    if (url.pathname in responses) {
      return responses[url.pathname]
    }

    return 404
  })

  fetchMock.get('express:/build/:file', (_url, options, request) => {
    const url = new URL(_url, 'https://example.com')

    if (url.pathname in responses) {
      return responses[url.pathname]
    }

    return 404
  })
}

const storeAndFireEvent = (key, value) => {
  localStorage.setItem(key, value)
  fireEvent(window, new StorageEvent('storage', { key }))
}

const scope = {
  settings: {
    syntaxValidation: false,
  },
  editor: {
    sharejs_doc: {
      doc_id: 'test-doc',
      getSnapshot: () => 'some doc content',
    },
  },
}

describe('<PdfPreview/>', function () {
  let clock

  beforeEach(function () {
    window.showNewPdfPreview = true
    clock = sinon.useFakeTimers({
      shouldAdvanceTime: true,
      now: Date.now(),
    })
    nock.cleanAll()
  })

  afterEach(function () {
    window.showNewPdfPreview = undefined
    clock.runAll()
    clock.restore()
    fetchMock.reset()
    localStorage.clear()
    sinon.restore()
  })

  it('renders the PDF preview', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })
  })

  it('runs a compile when the Recompile button is pressed', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    mockValidPdf()

    // press the Recompile button => compile
    const button = screen.getByRole('button', { name: 'Recompile' })
    button.click()
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    expect(fetchMock.calls()).to.have.length(6)
  })

  it('runs a compile on doc change if autocompile is enabled', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // switch on auto compile
    storeAndFireEvent('autocompile_enabled:project123', true)

    mockValidPdf()

    // fire a doc:changed event => compile
    fireEvent(window, new CustomEvent('doc:changed'))
    clock.tick(2000) // AUTO_COMPILE_DEBOUNCE

    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    expect(fetchMock.calls()).to.have.length(6)
  })

  it('does not run a compile on doc change if autocompile is disabled', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // make sure auto compile is switched off
    storeAndFireEvent('autocompile_enabled:project123', false)

    // fire a doc:changed event => no compile
    fireEvent(window, new CustomEvent('doc:changed'))
    clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
    screen.getByRole('button', { name: 'Recompile' })

    expect(fetchMock.calls()).to.have.length(3)
  })

  it('does not run a compile on doc change if autocompile is blocked by syntax check', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, {
      scope: {
        ...scope,
        'settings.syntaxValidation': true, // enable linting in the editor
        hasLintingError: true, // mock a linting error
      },
    })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // switch on auto compile and syntax checking
    storeAndFireEvent('autocompile_enabled:project123', true)
    storeAndFireEvent('stop_on_validation_error:project123', true)

    // fire a doc:changed event => no compile
    fireEvent(window, new CustomEvent('doc:changed'))
    clock.tick(2000) // AUTO_COMPILE_DEBOUNCE
    screen.getByRole('button', { name: 'Recompile' })
    await screen.findByText('Code check failed')

    expect(fetchMock.calls()).to.have.length(3)
  })

  describe('displays error messages', function () {
    const compileErrorStatuses = {
      'clear-cache':
        'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
      'clsi-maintenance':
        'The compile servers are down for maintenance, and will be back shortly.',
      'compile-in-progress':
        'A previous compile is still running. Please wait a minute and try compiling again.',
      exited: 'Server Error',
      failure: 'No PDF',
      generic: 'Server Error',
      'project-too-large': 'Project too large',
      'rate-limited': 'Compile rate limit hit',
      terminated: 'Compilation cancelled',
      timedout: 'Timed out',
      'too-recently-compiled':
        'This project was compiled very recently, so this compile has been skipped.',
      unavailable:
        'Sorry, the compile server for your project was temporarily unavailable. Please try again in a few moments.',
      foo:
        'Sorry, something went wrong and your project could not be compiled. Please try again in a few moments.',
    }

    for (const [status, message] of Object.entries(compileErrorStatuses)) {
      it(`displays error message for '${status}' status`, async function () {
        cleanup()
        fetchMock.restore()
        mockCompileError(status)

        renderWithEditorContext(<PdfPreview />, { scope })

        // wait for "compile on load" to finish
        await screen.findByRole('button', { name: 'Compiling…' })
        await screen.findByRole('button', { name: 'Recompile' })

        screen.getByText(message)
      })
    }
  })

  it('displays expandable raw logs', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    // pretend that the content is large enough to trigger a "collapse"
    // (in jsdom these values are always zero)
    sinon.stub(HTMLElement.prototype, 'scrollHeight').value(500)
    sinon.stub(HTMLElement.prototype, 'scrollWidth').value(500)

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    const logsButton = screen.getByRole('button', { name: 'View logs' })
    logsButton.click()

    await screen.findByRole('button', { name: 'View PDF' })

    // expand the log
    const [expandButton] = screen.getAllByRole('button', { name: 'Expand' })
    expandButton.click()

    // collapse the log
    const [collapseButton] = screen.getAllByRole('button', { name: 'Collapse' })
    collapseButton.click()
  })

  it('displays error messages if there were validation problems', async function () {
    const validationProblems = {
      sizeCheck: {
        resources: [
          { path: 'foo/bar', kbSize: 76221 },
          { path: 'bar/baz', kbSize: 2342 },
        ],
      },
      mainFile: true,
      conflictedPaths: [
        {
          path: 'foo/bar',
        },
        {
          path: 'foo/baz',
        },
      ],
    }

    mockValidationProblems(validationProblems)

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    screen.getByText('Project too large')
    screen.getByText('Unknown main document')
    screen.getByText('Conflicting Paths Found')

    expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
    expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be
      .false // TODO: actual path
  })

  it('sends a clear cache request when the button is pressed', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    const logsButton = screen.getByRole('button', {
      name: 'View logs',
    })
    logsButton.click()

    const clearCacheButton = await screen.findByRole('button', {
      name: 'Clear cached files',
    })
    expect(clearCacheButton.hasAttribute('disabled')).to.be.false

    mockClearCache()

    // click the button
    clearCacheButton.click()
    expect(clearCacheButton.hasAttribute('disabled')).to.be.true
    await waitFor(() => {
      expect(clearCacheButton.hasAttribute('disabled')).to.be.false
    })

    expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
    expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be.true // TODO: actual path
  })

  it('handle "recompile from scratch"', async function () {
    mockCompile()
    mockBuildFile()
    mockValidPdf()

    renderWithEditorContext(<PdfPreview />, { scope })

    // wait for "compile on load" to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    // show the logs UI
    const logsButton = screen.getByRole('button', {
      name: 'View logs',
    })
    logsButton.click()

    const clearCacheButton = await screen.findByRole('button', {
      name: 'Clear cached files',
    })
    expect(clearCacheButton.hasAttribute('disabled')).to.be.false

    mockValidPdf()
    mockClearCache()

    const recompileFromScratch = screen.getByRole('menuitem', {
      name: 'Recompile from scratch',
      hidden: true,
    })
    recompileFromScratch.click()

    expect(clearCacheButton.hasAttribute('disabled')).to.be.true

    // wait for compile to finish
    await screen.findByRole('button', { name: 'Compiling…' })
    await screen.findByRole('button', { name: 'Recompile' })

    expect(fetchMock.called('express:/project/:projectId/compile')).to.be.true // TODO: auto_compile query param
    expect(fetchMock.called('express:/project/:projectId/output')).to.be.true
    expect(fetchMock.called('begin:https://clsi.test-overleaf.com/')).to.be.true // TODO: actual path
  })

  it('shows an error for an invalid URL', async function () {
    mockCompile()
    mockBuildFile()

    nock('https://clsi.test-overleaf.com')
      .get(/^\/build\/output.pdf/)
      .replyWithError({
        message: 'something awful happened',
        code: 'AWFUL_ERROR',
      })

    const { rerender } = renderWithEditorContext(<PdfPreview />, { scope })

    await screen.findByText('Something went wrong while rendering this PDF.')
    expect(screen.queryByLabelText('Page 1')).to.not.exist

    expect(nock.isDone()).to.be.true

    mockValidPdf()

    rerender(<PdfPreview />)

    await screen.findByLabelText('Page 1')
    expect(screen.queryByText('Something went wrong while rendering this PDF.'))
      .to.not.exist
  })

  it('shows an error for a corrupt PDF', async function () {
    mockCompile()
    mockBuildFile()

    nock('https://clsi.test-overleaf.com')
      .get(/^\/build\/output.pdf/)
      .replyWithFile(200, corruptPDF)

    const { rerender } = renderWithEditorContext(<PdfPreview />, { scope })

    await screen.findByText('Something went wrong while rendering this PDF.')
    expect(screen.queryByLabelText('Page 1')).to.not.exist

    expect(nock.isDone()).to.be.true

    mockValidPdf()

    rerender(<PdfPreview />)

    await screen.findByLabelText('Page 1')
    expect(screen.queryByText('Something went wrong while rendering this PDF.'))
      .to.not.exist
  })
})
<|endoftext|>


<commit_before>var path = require('path');
var webpack = require('webpack');
var merge = require('webpack-merge');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var autoprefixer = require('autoprefixer');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var entryPath = path.join(__dirname, 'src/static/index.js');
var outputPath = path.join(__dirname, 'dist');

console.log('WEBPACK GO!');

// determine build env
var TARGET_ENV = process.env.npm_lifecycle_event === 'build' ? 'production' : 'development';
var outputFilename = TARGET_ENV === 'production' ? '[name]-[hash].js' : '[name].js'

// common webpack config
var commonConfig = {

	output: {
		path: outputPath,
		filename: `/static/js/${outputFilename}`,
		// publicPath: '/'
	},

	resolve: {
		extensions: ['', '.js', '.elm']
	},

	module: {
		noParse: /\.elm$/,
		loaders: [
			{
				test: /\.(eot|ttf|woff|woff2|svg)$/,
				loader: 'file-loader'
			}
		]
	},

	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/static/index.html',
			inject: 'body',
			filename: 'index.html'
		})
	],

	postcss: [ autoprefixer({ browsers: ['last 2 versions'] }) ],
}

// additional webpack settings for local env (when invoked by 'npm start')
if (TARGET_ENV === 'development') {
	console.log('Serving locally...');

	module.exports = merge(commonConfig, {

		entry: [
			'webpack-dev-server/client?http://localhost:8081',
			entryPath
		],

		devServer: {
			// serve index.html in place of 404 responses
			historyApiFallback: true,
			contentBase: './src',
		},

		module: {
			loaders: [
				{
					test: /\.elm$/,
					exclude: [/elm-stuff/, /node_modules/],
					loader: 'elm-hot!elm-webpack?verbose=true&warn=true&debug=true'
				},
				{
					test: /\.(css|scss)$/,
					loaders: [
						'style-loader',
						'css-loader',
						'postcss-loader',
						'sass-loader'
					]
				},
				{
					test: /\.(woff|woff2|ttf|eot|svg)(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/,
					loader: 'url-loader?limit=100000'
					//loader: 'file?name=static/fonts/[name].[ext]'
				}
			]
		}

	});
}

// additional webpack settings for prod env (when invoked via 'npm run build')
if (TARGET_ENV === 'production') {
	console.log('Building for prod...');

	module.exports = merge(commonConfig, {

		entry: entryPath,

		module: {
			loaders: [
				{
					test: /\.elm$/,
					exclude: [/elm-stuff/, /node_modules/],
					loader: 'elm-webpack'
				},
				{
					test: /\.(css|scss)$/,
					loader: ExtractTextPlugin.extract('style-loader', [
						'css-loader',
						'postcss-loader',
						'sass-loader'
					])
				}
			]
		},

		plugins: [
			new CopyWebpackPlugin([
				{
					from: 'src/static/img/',
					to: 'static/img/'
				},
				{
					from: 'src/favicon.ico'
				},
			]),

			new webpack.optimize.OccurenceOrderPlugin(),

			// extract CSS into a separate file
			new ExtractTextPlugin('static/css/[name]-[hash].css', { allChunks: true }),

			// minify & mangle JS/CSS
			new webpack.optimize.UglifyJsPlugin({
					minimize: true,
					compressor: { warnings: false }
					// mangle: true
			})
		]
	});
}
<commit_msg>Build works<commit_after>var path = require('path');
var webpack = require('webpack');
var merge = require('webpack-merge');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var autoprefixer = require('autoprefixer');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var entryPath = path.join(__dirname, 'src/static/index.js');
var outputPath = path.join(__dirname, 'dist');

console.log('WEBPACK GO!');

// determine build env
var TARGET_ENV = process.env.npm_lifecycle_event === 'build' ? 'production' : 'development';
var outputFilename = TARGET_ENV === 'production' ? '[name]-[hash].js' : '[name].js'

// common webpack config
var commonConfig = {

	output: {
		path: outputPath,
		filename: `/static/js/${outputFilename}`,
		// publicPath: '/'
	},

	resolve: {
		extensions: ['', '.js', '.elm']
	},

	module: {
		noParse: /\.elm$/,
		loaders: [
			{
				test: /\.(eot|ttf|woff|woff2|svg)$/,
				loader: 'file-loader'
			}
		]
	},

	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/static/index.html',
			inject: 'body',
			filename: 'index.html'
		})
	],

	postcss: [ autoprefixer({ browsers: ['last 2 versions'] }) ],
}

// additional webpack settings for local env (when invoked by 'npm start')
if (TARGET_ENV === 'development') {
	console.log('Serving locally...');

	module.exports = merge(commonConfig, {

		entry: [
			'webpack-dev-server/client?http://localhost:8081',
			entryPath
		],

		devServer: {
			// serve index.html in place of 404 responses
			historyApiFallback: true,
			contentBase: './src',
		},

		module: {
			loaders: [
				{
					test: /\.elm$/,
					exclude: [/elm-stuff/, /node_modules/],
					loader: 'elm-hot!elm-webpack?verbose=true&warn=true&debug=true'
				},
				{
					test: /\.(css|scss)$/,
					loaders: [
						'style-loader',
						'css-loader',
						'postcss-loader',
						'sass-loader'
					]
				},
				{
					test: /\.(woff|woff2|ttf|eot|svg)(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/,
					loader: 'url-loader?limit=100000'
				}
			]
		}

	});
}

// additional webpack settings for prod env (when invoked via 'npm run build')
if (TARGET_ENV === 'production') {
	console.log('Building for prod...');

	module.exports = merge(commonConfig, {

		entry: entryPath,

		module: {
			loaders: [
				{
					test: /\.elm$/,
					exclude: [/elm-stuff/, /node_modules/],
					loader: 'elm-webpack'
				},
				{
					test: /\.(css|scss)$/,
					loader: ExtractTextPlugin.extract('style-loader', [
						'css-loader',
						'postcss-loader',
						'sass-loader'
					])
				},
				{
					test: /\.(woff|woff2|ttf|eot|svg)(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/,
					loader: 'url?limit=10000&name=[name].[ext]?[hash]'
				}
			]
		},

		plugins: [
			new CopyWebpackPlugin([
				{
					from: 'src/static/img/',
					to: 'static/img/'
				},
				{
					from: 'src/favicon.png'
				},
			]),

			new webpack.optimize.OccurenceOrderPlugin(),

			// extract CSS into a separate file
			new ExtractTextPlugin('static/css/[name]-[hash].css', { allChunks: true }),

			// minify & mangle JS/CSS
			new webpack.optimize.UglifyJsPlugin({
					minimize: true,
					compressor: { warnings: false }
					// mangle: true
			})
		]
	});
}
<|endoftext|>


<commit_before>/*jslint unparam: true, node: true, plusplus: true, nomen: true, indent: 2, todo: true */
'use strict';

var passport      = require('passport');
var Utils         = require('../lib/utils');
var UserModel     = require('../models/user');

/**
 * Stores the auth data return from OAuth providers in local db. User data is
 * matched by email addresses and updated on every login action.
 * @api private
 */
function upsertUser(userData, cb) {
  UserModel.findOneAndUpdate({
    userIdProvider: userData.userIdProvider,
    type: userData.type
  }, userData, {
    // create user if (email) not exists
    upsert: true
  }, function (err, user) {

    if (err) { return cb(err); }

    UserModel.count({}, function (err, userCount) {

      if (err) { return cb(err); }

      if (userCount === 1) {
        // grant admin permissions to the first user in db
        UserModel.findByIdAndUpdate(user._id, { role: 'admin' }, function (err, user) {
          cb(null, user);
        });
      } else {
        cb(null, user);
      }
    });
  });
}

var AuthController = {

  /**
   * Redirects to github.com for authentication. After authorization, Github
   * will redirect the user back to this application at /users/auth/github/callback
   */
  authWithGithub: function (req, res, next) {
    passport.authenticate('github', {})(req, res);
  },

  /**
   * Redirects to google.com for authentication. After authorization, Google
   * will redirect the user back to this application at /users/auth/google/callback
   */
  authWithGoogle: function (req, res, next) {
    passport.authenticate('google', {
      scope: [
        'https://www.googleapis.com/auth/userinfo.profile',
        'https://www.googleapis.com/auth/userinfo.email'
      ],
      hostedDomain: process.env.GOOGLE_HOSTED_DOMAIN
    })(req, res);
  },

  /**
   * Callback action for Google OAuth requests.
   */
  authWithGoogleCb: function (req, res, next) {
    passport.authenticate('google', { failureRedirect: '/' })(req, res, function () {

      var user, json = req.session.passport.user._json;

      user = {
        userIdProvider: json.id,
        email: json.email,
        displayname: req.session.passport.user.displayName,
        name: json.name,
        avatarurl: json.picture,
        type: 'google',
        // grant editor role to internal users
        role: (json.email.indexOf(process.env.GOOGLE_HOSTED_DOMAIN) > 0) ? 'editor' : 'user'
      };

      upsertUser(user, function (err, user) {
        if (err) { return next(err); }

        // store user object in session
        req.session.user = user;

        res.redirect('/gadgets');
      });
    });
  },

  /**
   * Callback action for Github OAuth requests.
   */
  authWithGithubCb: function (req, res, next) {
    passport.authenticate('github', { failureRedirect: '/' })(req, res, function () {

      var user, json = req.session.passport.user._json;

      user = {
        userIdProvider: req.session.passport.user.id, // github user id
        email: json.email || '', // email might not be present on github oauth
        displayname: req.session.passport.user.username,
        name: json.name || '',
        avatarurl: json.avatar_url,
        type: 'github'
      };

      upsertUser(user, function (err, user) {
        if (err) { return next(err); }

        // store user object in session
        req.session.user = user;

        res.redirect('/gadgets');
      });

    });
  },

  /**
   * Checks if a request is authenticated
   */
  isAuth: function (req, res, next) {
    if (!req.isAuthenticated()) {
      return next(Utils.getError(401));
    }
    next();
  },

  /**
   * Checks if the current user has admin permissions
   */
  isAdmin: function (req, res, next) {
    if (!req.isAuthenticated()) {
      return next(Utils.getError(401));
    }
    if (!req.session.user || req.session.user.role.toLowerCase() !== 'admin') {
      return next(Utils.getError(403));
    }
    next();
  },

  /**
   * Destroys the session object and logs out the current user
   */
  logout: function (req, res, next) {
    req.logout();
    req.session.destroy();
    res.redirect('/');
  }

};

module.exports = AuthController;<commit_msg>Fix role update on login<commit_after>/*jslint unparam: true, node: true, plusplus: true, nomen: true, indent: 2, todo: true */
'use strict';

var passport      = require('passport');
var Utils         = require('../lib/utils');
var UserModel     = require('../models/user');

/**
 * Stores the auth data return from OAuth providers in local db. User data is
 * matched by email addresses and updated on every login action.
 * @api private
 */
function upsertUser(userData, cb) {

  // don't update users role
  delete userData.role;

  UserModel.findOneAndUpdate({
    userIdProvider: userData.userIdProvider,
    type: userData.type
  }, userData, {
    // create user if (email) not exists
    upsert: true
  }, function (err, user) {

    if (err) { return cb(err); }

    UserModel.count({}, function (err, userCount) {

      if (err) { return cb(err); }

      if (userCount === 1) {
        // grant admin permissions to the first user in db
        UserModel.findByIdAndUpdate(user._id, { role: 'admin' }, function (err, user) {
          cb(null, user);
        });
      } else {
        cb(null, user);
      }
    });
  });
}

var AuthController = {

  /**
   * Redirects to github.com for authentication. After authorization, Github
   * will redirect the user back to this application at /users/auth/github/callback
   */
  authWithGithub: function (req, res, next) {
    passport.authenticate('github', {})(req, res);
  },

  /**
   * Redirects to google.com for authentication. After authorization, Google
   * will redirect the user back to this application at /users/auth/google/callback
   */
  authWithGoogle: function (req, res, next) {
    passport.authenticate('google', {
      scope: [
        'https://www.googleapis.com/auth/userinfo.profile',
        'https://www.googleapis.com/auth/userinfo.email'
      ],
      hostedDomain: process.env.GOOGLE_HOSTED_DOMAIN
    })(req, res);
  },

  /**
   * Callback action for Google OAuth requests.
   */
  authWithGoogleCb: function (req, res, next) {
    passport.authenticate('google', { failureRedirect: '/' })(req, res, function () {

      var user, json = req.session.passport.user._json;

      user = {
        userIdProvider: json.id,
        email: json.email,
        displayname: req.session.passport.user.displayName,
        name: json.name,
        avatarurl: json.picture,
        type: 'google',
        // grant editor role to internal users
        role: (json.email.indexOf(process.env.GOOGLE_HOSTED_DOMAIN) > 0) ? 'editor' : 'user'
      };

      upsertUser(user, function (err, user) {
        if (err) { return next(err); }

        // store user object in session
        req.session.user = user;

        res.redirect('/gadgets');
      });
    });
  },

  /**
   * Callback action for Github OAuth requests.
   */
  authWithGithubCb: function (req, res, next) {
    passport.authenticate('github', { failureRedirect: '/' })(req, res, function () {

      var user, json = req.session.passport.user._json;

      user = {
        userIdProvider: req.session.passport.user.id, // github user id
        email: json.email || '', // email might not be present on github oauth
        displayname: req.session.passport.user.username,
        name: json.name || '',
        avatarurl: json.avatar_url,
        type: 'github'
      };

      upsertUser(user, function (err, user) {
        if (err) { return next(err); }

        // store user object in session
        req.session.user = user;

        res.redirect('/gadgets');
      });

    });
  },

  /**
   * Checks if a request is authenticated
   */
  isAuth: function (req, res, next) {
    if (!req.isAuthenticated()) {
      return next(Utils.getError(401));
    }
    next();
  },

  /**
   * Checks if the current user has admin permissions
   */
  isAdmin: function (req, res, next) {
    if (!req.isAuthenticated()) {
      return next(Utils.getError(401));
    }
    if (!req.session.user || req.session.user.role.toLowerCase() !== 'admin') {
      return next(Utils.getError(403));
    }
    next();
  },

  /**
   * Destroys the session object and logs out the current user
   */
  logout: function (req, res, next) {
    req.logout();
    req.session.destroy();
    res.redirect('/');
  }

};

module.exports = AuthController;<|endoftext|>


<commit_before>var path = require('path')
var webpack = require('webpack')

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const OfflinePlugin = require('offline-plugin');

const SRC = path.resolve(__dirname, "src");
const ASSETS = path.resolve(__dirname, "src/assets");
const PAGES = path.resolve(__dirname, "src/pages");
const COMPONENTS = path.resolve(__dirname, "src/components");
const IONICONS = path.resolve(__dirname, "node_modules/vue-ionicons/dist");

const ENV = process.env.NODE_ENV || 'development';
const CSS_MAPS = ENV !== 'production';

module.exports = {
	context: SRC,
  entry: './main.js',
  output: {
		path: path.resolve(__dirname, "build"),
		publicPath: '/explore-github/',
		filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
            // the "scss" and "sass" values for the lang attribute to the right configs here.
            // other preprocessors should work out of the box, no loader config like this necessary.
            'scss': 'vue-style-loader!css-loader!sass-loader',
            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
			{
				test: /\.(scss|sass|css)$/,
				exclude: [
					COMPONENTS, PAGES
				],
				use: ExtractTextPlugin.extract({
					fallback: 'style-loader',
					use: [
						{
							loader: 'css-loader',
							options: { sourceMap: CSS_MAPS, importLoaders: 1, minimize: true }
						},
						{
							loader: `postcss-loader`,
							options: {
								sourceMap: CSS_MAPS,
								plugins: () => {
									autoprefixer({ browsers: ['last 2 versions'] });
								}
							}
						},
						{
							loader: 'sass-loader',
							options: { sourceMap: CSS_MAPS }
						}
					]
				})
			},
			{
				test: /\.json$/,
				use: 'json-loader'
			},
			{
				test: /\.(xml|html|txt|md)$/,
				use: 'raw-loader'
			},
			{
				test: /\.(svg|woff2?|ttf|eot|jpe?g|png|gif)(\?.*)?$/i,
				use: ENV === 'production' ? 'file-loader' : 'url-loader'
			}
    ]
  },
  resolve: {
    extensions: ['.js', '.json', '.vue'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': SRC,
			pages: PAGES,
      components: COMPONENTS,
      assets: ASSETS,
      'icons': IONICONS
    }
  },
  plugins: ([
		new webpack.NoEmitOnErrorsPlugin(),
		new ExtractTextPlugin({
			filename: 'style.css',
			allChunks: true,
			disable: ENV !== 'production'
		}),
		new webpack.DefinePlugin({
			'process.env.NODE_ENV': JSON.stringify(ENV)
		}),
		new HtmlWebpackPlugin({
			template: './index.ejs',
			minify: { collapseWhitespace: true }
		}),
		new CopyWebpackPlugin([
			{ from: './manifest.json', to: './' },
			{ from: './favicon.ico', to: './' }
		])
	]).concat(ENV === 'production' ? [
		new webpack.optimize.UglifyJsPlugin({
			output: {
				comments: false
			},
			compress: {
				unsafe_comps: true,
				properties: true,
				keep_fargs: false,
				pure_getters: true,
				collapse_vars: true,
				unsafe: true,
				warnings: false,
				screw_ie8: true,
				sequences: true,
				dead_code: true,
				drop_debugger: true,
				comparisons: true,
				conditionals: true,
				evaluate: true,
				booleans: true,
				loops: true,
				unused: true,
				hoist_funs: true,
				if_return: true,
				join_vars: true,
				cascade: true,
				drop_console: true
			}
		}),

		new OfflinePlugin({
			relativePaths: false,
			AppCache: false,
			excludes: ['_redirects'],
			ServiceWorker: {
				events: true
			},
			cacheMaps: [
				{
					match: /.*/,
					to: '/',
					requestTypes: ['navigate']
				}
			],
			publicPath: '/explore-github/'
		})
  ] : []),

  devServer: {
		port: process.env.PORT || 8089,
		host: 'localhost',
		publicPath: '/explore-github/',
		contentBase: './src',
		historyApiFallback: true,
		open: true,
		openPage: 'explore-github/',
		proxy: {
			'/explore-github': {
				target: "http://localhost:8089",
				bypass: (req) => {
					let view = req.url.replace('/explore-github', '');
					return view;
				}
			}
		}
  },

  performance: {
    hints: false
  },

	devtool: ENV === 'production' ? 'source-map' : 'cheap-module-eval-source-map',

}

<commit_msg>namedChunk<commit_after>var path = require('path')
var webpack = require('webpack')

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const OfflinePlugin = require('offline-plugin');

const SRC = path.resolve(__dirname, "src");
const ASSETS = path.resolve(__dirname, "src/assets");
const PAGES = path.resolve(__dirname, "src/pages");
const COMPONENTS = path.resolve(__dirname, "src/components");
const IONICONS = path.resolve(__dirname, "node_modules/vue-ionicons/dist");

const ENV = process.env.NODE_ENV || 'development';
const CSS_MAPS = ENV !== 'production';

module.exports = {
	context: SRC,
  entry: './main.js',
  output: {
		path: path.resolve(__dirname, "build"),
		publicPath: '/explore-github/',
    filename: '[name].bundle.js',
    chunkFilename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
            // the "scss" and "sass" values for the lang attribute to the right configs here.
            // other preprocessors should work out of the box, no loader config like this necessary.
            'scss': 'vue-style-loader!css-loader!sass-loader',
            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
			{
				test: /\.(scss|sass|css)$/,
				exclude: [
					COMPONENTS, PAGES
				],
				use: ExtractTextPlugin.extract({
					fallback: 'style-loader',
					use: [
						{
							loader: 'css-loader',
							options: { sourceMap: CSS_MAPS, importLoaders: 1, minimize: true }
						},
						{
							loader: `postcss-loader`,
							options: {
								sourceMap: CSS_MAPS,
								plugins: () => {
									autoprefixer({ browsers: ['last 2 versions'] });
								}
							}
						},
						{
							loader: 'sass-loader',
							options: { sourceMap: CSS_MAPS }
						}
					]
				})
			},
			{
				test: /\.json$/,
				use: 'json-loader'
			},
			{
				test: /\.(xml|html|txt|md)$/,
				use: 'raw-loader'
			},
			{
				test: /\.(svg|woff2?|ttf|eot|jpe?g|png|gif)(\?.*)?$/i,
				use: ENV === 'production' ? 'file-loader' : 'url-loader'
			}
    ]
  },
  resolve: {
    extensions: ['.js', '.json', '.vue'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': SRC,
			pages: PAGES,
      components: COMPONENTS,
      assets: ASSETS,
      'icons': IONICONS
    }
  },
  plugins: ([
		new webpack.NoEmitOnErrorsPlugin(),
		new ExtractTextPlugin({
			filename: 'style.css',
			allChunks: true,
			disable: ENV !== 'production'
		}),
		new webpack.DefinePlugin({
			'process.env.NODE_ENV': JSON.stringify(ENV)
		}),
		new HtmlWebpackPlugin({
			template: './index.ejs',
			minify: { collapseWhitespace: true }
		}),
		new CopyWebpackPlugin([
			{ from: './manifest.json', to: './' },
			{ from: './favicon.ico', to: './' }
		])
	]).concat(ENV === 'production' ? [
		new webpack.optimize.UglifyJsPlugin({
			output: {
				comments: false
			},
			compress: {
				unsafe_comps: true,
				properties: true,
				keep_fargs: false,
				pure_getters: true,
				collapse_vars: true,
				unsafe: true,
				warnings: false,
				screw_ie8: true,
				sequences: true,
				dead_code: true,
				drop_debugger: true,
				comparisons: true,
				conditionals: true,
				evaluate: true,
				booleans: true,
				loops: true,
				unused: true,
				hoist_funs: true,
				if_return: true,
				join_vars: true,
				cascade: true,
				drop_console: true
			}
		}),

		new OfflinePlugin({
			relativePaths: false,
			AppCache: false,
			excludes: ['_redirects'],
			ServiceWorker: {
				events: true
			},
			cacheMaps: [
				{
					match: /.*/,
					to: '/',
					requestTypes: ['navigate']
				}
			],
			publicPath: '/explore-github/'
		})
  ] : []),

  devServer: {
		port: process.env.PORT || 8089,
		host: 'localhost',
		publicPath: '/explore-github/',
		contentBase: './src',
		historyApiFallback: true,
		open: true,
		openPage: 'explore-github/',
		proxy: {
			'/explore-github': {
				target: "http://localhost:8089",
				bypass: (req) => {
					let view = req.url.replace('/explore-github', '');
					return view;
				}
			}
		}
  },

  performance: {
    hints: false
  },

	devtool: ENV === 'production' ? 'source-map' : 'cheap-module-eval-source-map',

}

<|endoftext|>


<commit_before>const toolbox = require("./node_modules/devtools-launchpad/index");

const getConfig = require("./bin/getConfig");
const { isDevelopment, isFirefoxPanel } = require("devtools-config");
const { NormalModuleReplacementPlugin } = require("webpack");
const path = require("path");
const projectPath = path.join(__dirname, "src");
var Visualizer = require("webpack-visualizer-plugin");

/*
 * builds a path that's relative to the project path
 * returns an array so that we can prepend
 * hot-module-reloading in local development
 */
function getEntry(filename) {
  return [path.join(projectPath, filename)];
}

const webpackConfig = {
  entry: {
    debugger: getEntry("main.js"),
    "parser-worker": getEntry("workers/parser/worker.js"),
    "pretty-print-worker": getEntry("workers/pretty-print/worker.js"),
    "search-worker": getEntry("workers/search/worker.js")
  },

  output: {
    path: path.join(__dirname, "assets/build"),
    filename: "[name].js",
    publicPath: "/assets/build"
  }
};

function buildConfig(envConfig) {
  const extra = {};
  if (isDevelopment()) {
    webpackConfig.plugins = [];

    webpackConfig.module = webpackConfig.module || {};
    webpackConfig.module.rules = webpackConfig.module.rules || [];
  } else {
    webpackConfig.plugins = [];
    webpackConfig.output.libraryTarget = "umd";

    if (process.env.vis) {
      const viz = new Visualizer({
        filename: "webpack-stats.html"
      });
      webpackConfig.plugins = [viz];
    }

    const mappings = [
      [/\.\/mocha/, "./mochitest"],
      [/\.\.\/utils\/mocha/, "../utils/mochitest"],
      [/\.\/utils\/mocha/, "./utils/mochitest"],
      [/\.\/percy-stub/, "./percy-webpack"]
    ];

    extra.excludeMap = {
      "./source-editor": "devtools/client/sourceeditor/editor",
      "./test-flag": "devtools/shared/flags",
      react: "devtools/client/shared/vendor/react",
      redux: "devtools/client/shared/vendor/redux",
      "react-dom": "devtools/client/shared/vendor/react-dom",
      lodash: "devtools/client/shared/vendor/lodash",
      immutable: "devtools/client/shared/vendor/immutable",
      "react-redux": "devtools/client/shared/vendor/react-redux",

      "wasmparser/dist/WasmParser": "devtools/client/shared/vendor/WasmParser",
      "wasmparser/dist/WasmDis": "devtools/client/shared/vendor/WasmDis",
      "../assets/panel/debugger.properties": "devtools/shared/flags",
      "devtools-connection": "devtools/shared/flags",
      "chrome-remote-interface": "devtools/shared/flags",
      "devtools-launchpad": "devtools/shared/flags"
    };

    mappings.forEach(([regex, res]) => {
      webpackConfig.plugins.push(new NormalModuleReplacementPlugin(regex, res));
    });
  }

  // TODO: It would be nice to stop bundling `devtools-source-map` entirely for
  // the Firefox panel, but at the moment we still use `isOriginalId` from a
  // required copy of the module, instead of using the one from the toolbox.
  if (!isFirefoxPanel()) {
    // When used as a Firefox panel, the toolbox supplies its own source map
    // service and worker, so we only need to build this when running in a tab.
    webpackConfig.entry["source-map-worker"] = getEntry(
      "../node_modules/devtools-source-map/src/worker.js"
    );
  }

  return toolbox.toolboxConfig(webpackConfig, envConfig, extra);
}

const envConfig = getConfig();

module.exports = buildConfig(envConfig);
<commit_msg>Document webpack.config redirecting unused files to flags module (#5621)<commit_after>const toolbox = require("./node_modules/devtools-launchpad/index");

const getConfig = require("./bin/getConfig");
const { isDevelopment, isFirefoxPanel } = require("devtools-config");
const { NormalModuleReplacementPlugin } = require("webpack");
const path = require("path");
const projectPath = path.join(__dirname, "src");
var Visualizer = require("webpack-visualizer-plugin");

/*
 * builds a path that's relative to the project path
 * returns an array so that we can prepend
 * hot-module-reloading in local development
 */
function getEntry(filename) {
  return [path.join(projectPath, filename)];
}

const webpackConfig = {
  entry: {
    debugger: getEntry("main.js"),
    "parser-worker": getEntry("workers/parser/worker.js"),
    "pretty-print-worker": getEntry("workers/pretty-print/worker.js"),
    "search-worker": getEntry("workers/search/worker.js")
  },

  output: {
    path: path.join(__dirname, "assets/build"),
    filename: "[name].js",
    publicPath: "/assets/build"
  }
};

function buildConfig(envConfig) {
  const extra = {};
  if (isDevelopment()) {
    webpackConfig.plugins = [];

    webpackConfig.module = webpackConfig.module || {};
    webpackConfig.module.rules = webpackConfig.module.rules || [];
  } else {
    webpackConfig.plugins = [];
    webpackConfig.output.libraryTarget = "umd";

    if (process.env.vis) {
      const viz = new Visualizer({
        filename: "webpack-stats.html"
      });
      webpackConfig.plugins = [viz];
    }

    const mappings = [
      [/\.\/mocha/, "./mochitest"],
      [/\.\.\/utils\/mocha/, "../utils/mochitest"],
      [/\.\/utils\/mocha/, "./utils/mochitest"],
      [/\.\/percy-stub/, "./percy-webpack"]
    ];

    extra.excludeMap = {
      "./source-editor": "devtools/client/sourceeditor/editor",
      "./test-flag": "devtools/shared/flags",
      react: "devtools/client/shared/vendor/react",
      redux: "devtools/client/shared/vendor/redux",
      "react-dom": "devtools/client/shared/vendor/react-dom",
      lodash: "devtools/client/shared/vendor/lodash",
      immutable: "devtools/client/shared/vendor/immutable",
      "react-redux": "devtools/client/shared/vendor/react-redux",

      "wasmparser/dist/WasmParser": "devtools/client/shared/vendor/WasmParser",
      "wasmparser/dist/WasmDis": "devtools/client/shared/vendor/WasmDis",

      // The excluded files below should not be required while the Debugger runs
      // in Firefox. Here, "devtools/shared/flags" is used as a dummy module.
      "../assets/panel/debugger.properties": "devtools/shared/flags",
      "devtools-connection": "devtools/shared/flags",
      "chrome-remote-interface": "devtools/shared/flags",
      "devtools-launchpad": "devtools/shared/flags"
    };

    mappings.forEach(([regex, res]) => {
      webpackConfig.plugins.push(new NormalModuleReplacementPlugin(regex, res));
    });
  }

  // TODO: It would be nice to stop bundling `devtools-source-map` entirely for
  // the Firefox panel, but at the moment we still use `isOriginalId` from a
  // required copy of the module, instead of using the one from the toolbox.
  if (!isFirefoxPanel()) {
    // When used as a Firefox panel, the toolbox supplies its own source map
    // service and worker, so we only need to build this when running in a tab.
    webpackConfig.entry["source-map-worker"] = getEntry(
      "../node_modules/devtools-source-map/src/worker.js"
    );
  }

  return toolbox.toolboxConfig(webpackConfig, envConfig, extra);
}

const envConfig = getConfig();

module.exports = buildConfig(envConfig);
<|endoftext|>


<commit_before>var path = require("path");

module.exports = {
  entry: './src/index.jsx',
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx']
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    publicPath: '/',
    filename: 'bundle.js',
  },
  devServer: {
		port: process.env.PORT || 5678,
		host: 'localhost',
		publicPath: '/',
		contentBase: './public',
		historyApiFallback: true,
		open: true,
	},
};
<commit_msg>Fix indentation in webpack config<commit_after>var path = require("path");

module.exports = {
  entry: './src/index.jsx',
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader'
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx']
  },
  output: {
    path: path.resolve(__dirname, 'build'),
    publicPath: '/',
    filename: 'bundle.js',
  },
  devServer: {
    port: process.env.PORT || 5678,
    host: 'localhost',
    publicPath: '/',
    contentBase: './public',
    historyApiFallback: true,
    open: true,
  },
};
<|endoftext|>


<commit_before>const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: {
        app: path.resolve(__dirname, 'app/Resources/assets/js/app.js'),
        vendor: [
            'jquery',
            'moment',
            'highlightjs',
            'bootstrap',
        ]
    },
    output: {
        path: path.resolve(__dirname, 'web/builds'),
        filename: '[name].js',
        publicPath: '/builds/'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                use: 'babel-loader'
            },
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: ['css-loader', 'sass-loader']
                })
            },
            {
                test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
                use: "url-loader"
            },
            {
                test: /jquery/,
                use: [
                    { loader: 'expose-loader', options: '$' },
                    { loader: 'expose-loader', options: 'jQuery' }
                ]
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            hljs: path.resolve(__dirname, 'app/Resources/assets/js/highlight.pack.js')
        }),
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
        new ExtractTextPlugin('[name].css'),
        new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }),
        new webpack.optimize.UglifyJsPlugin({
            beautify: false,
            compress: {
                screw_ie8: true,
                warnings: false
            },
            mangle: {
                screw_ie8: true,
                keep_fnames: true
            },
            comments: false
        }),
    ],
    resolve: {
        alias: {
            fonts: path.resolve(__dirname, 'web/fonts'),
            jquery: path.resolve(__dirname, 'app/Resources/assets/js/jquery-2.1.4.min.js'),
            moment: path.resolve(__dirname, 'app/Resources/assets/js/moment.min.js'),
            highlightjs: path.resolve(__dirname, 'app/Resources/assets/js/highlight.pack.js'),
            bootstrap: path.resolve(__dirname, 'app/Resources/assets/js/bootstrap-3.3.4.min.js')
        }
    },
    devtool: 'eval-cheap-module-source-map',
    devServer: {
        hot: true,
        contentBase: './web/'
    }
};
<commit_msg>Add minification support for styles<commit_after>const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: {
        app: path.resolve(__dirname, 'app/Resources/assets/js/app.js'),
        vendor: [
            'jquery',
            'moment',
            'highlightjs',
            'bootstrap',
        ]
    },
    output: {
        path: path.resolve(__dirname, 'web/builds'),
        filename: '[name].js',
        publicPath: '/builds/'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules)/,
                use: 'babel-loader'
            },
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: [
                        {
                            loader: 'css-loader',
                            options: { // CSS Nano configuration
                                minimize: {
                                    discardComments: {
                                        removeAll: true
                                    },
                                    core: true,
                                    minifyFontValues: true
                                }
                            }
                        },
                        'sass-loader'
                    ]
                })
            },
            {
                test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
                use: "url-loader"
            },
            {
                test: /jquery/,
                use: [
                    { loader: 'expose-loader', options: '$' },
                    { loader: 'expose-loader', options: 'jQuery' }
                ]
            }
        ]
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery",
            hljs: path.resolve(__dirname, 'app/Resources/assets/js/highlight.pack.js')
        }),
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
        new ExtractTextPlugin('[name].css'),
        new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }),
        new webpack.optimize.UglifyJsPlugin({
            beautify: false,
            compress: {
                screw_ie8: true,
                warnings: false
            },
            mangle: {
                screw_ie8: true,
                keep_fnames: true
            },
            comments: false
        }),
    ],
    resolve: {
        alias: {
            fonts: path.resolve(__dirname, 'web/fonts'),
            jquery: path.resolve(__dirname, 'app/Resources/assets/js/jquery-2.1.4.min.js'),
            moment: path.resolve(__dirname, 'app/Resources/assets/js/moment.min.js'),
            highlightjs: path.resolve(__dirname, 'app/Resources/assets/js/highlight.pack.js'),
            bootstrap: path.resolve(__dirname, 'app/Resources/assets/js/bootstrap-3.3.4.min.js')
        }
    },
    devtool: 'eval-cheap-module-source-map',
    devServer: {
        hot: true,
        contentBase: './web/'
    }
};
<|endoftext|>


<commit_before>var mongoose = require('mongoose');
var passport = require('passport');
var _ = require('underscore');
var User = require('../models/User');
var NewsItem = require('../models/NewsItem');
var Vote = require('../models/Vote');

exports.index = function(req, res, next) {
  NewsItem
  .find({})
  .sort('-created')
  .limit(30)
  .populate('poster')
  .exec(function(err, newsItems) {

    if(err) return next(err);

    addVotesToNewsItems(newsItems, req.user, function (err, newsItems) {

      if(err) return next(err);

      res.render('news/index', {
        title: 'Recent News',
        items: newsItems
      });

    });

  });
};

exports.userNews = function(req, res) {
  User
  .find({'username': req.params.id})
  .exec(function(err, users) {
    NewsItem
    .find({'poster': users[0].id})
    .sort('-created')
    .limit(30)
    .populate('poster')
    .exec(function(err, newsItems) {

      if(err) return next(err);

      addVotesToNewsItems(newsItems, req.user, function (err, newsItems) {

        if(err) return next(err);

        res.render('news/index', {
          title: 'News shared by ' + users[0].username,
          items: newsItems,
          filteredUser: users[0].username
        });

      });

    });
  });
}

function addVotesToNewsItems(newsItems, user, callback) {

  Vote
  .find({ id: { $in: newsItems.map(function (item) { return item.id; }) } })
  .exec(function (err, votes) {

    if(err) return callback(err);

    newsItems = newsItems.map(function (item) {
      item = typeof item.toObject === 'function' ? item.toObject() : item;

      item.votes = votes.reduce(function (prev, curr, i) {

        // count this item as voted for if the logged in user has a vote tallied
        if(user && user.id && curr.voter.toString() === user.id.toString()) {
          item.votedFor = true;
        }

        return prev + curr.amount;
      }, 0);

      return item;
    });

    callback(null, newsItems);
  });
}

/**
 * GET /news/submit
 * Submit news.
 */

exports.submitNews = function(req, res) {
  //if (!req.user) return res.redirect('/login');
  res.render('news/submit', {
    title: 'Submit News'
  });
};

/**
 * POST /news/submit
 * Submit news item.
 * @param {string} title
 * @param {string} url
 */

exports.postNews = function(req, res, next) {
  req.assert('title', 'Title cannot be blank.').notEmpty();
  req.assert('url', 'URL cannot be blank.').notEmpty();

  var errors = req.validationErrors();

  if (errors) {
    req.flash('errors', errors);
    return res.redirect('/news/submit');
  }

  console.log("Posting for user id "+req.user.id);

  var newsItem = new NewsItem({
    title: req.body.title,
    url: req.body.url,
    poster: req.user.id
  });

  newsItem.save(function(err) {
    if (err) {
      if (err.code === 11000) {
        req.flash('errors', { msg: 'That URL already exists as a news item.' });
      }
      return res.redirect('/news/submit');
    }

    req.flash('success', { msg: 'News item submitted. Thanks!' });
    res.redirect('/news');
  });

};

/**
 * PUT /news/:item
 * Vote up a news item.
 * @param {number} amount Which direction and amount to vote up a news item (limited to +1 for now)
 */

exports.vote = function (req, res, next) {

  req.assert('amount', 'Items can only be upvoted.').equals('1');

  var errors = req.validationErrors();

  if (errors) {
    res.flash('errors', errors);
    return res.redirect(req.get('referrer') || '/');
  }

  var vote = new Vote({
    item: req.params.id,
    voter: req.user.id,
    amount: req.body.amount
  });

  vote.save(function (err) {
    if (err) {
      if (err.code === 11000) {
        req.flash('errors', { msg: 'You can only upvote an item once.' });
      }
      return res.redirect(req.get('referrer') || '/');
    }

    req.flash('success', { msg: 'News item upvoted. Awesome!' });
    res.redirect(req.get('referrer') || '/');
  });

};
<commit_msg>filter votes, no double counting<commit_after>var mongoose = require('mongoose');
var passport = require('passport');
var _ = require('underscore');
var User = require('../models/User');
var NewsItem = require('../models/NewsItem');
var Vote = require('../models/Vote');

exports.index = function(req, res, next) {
  NewsItem
  .find({})
  .sort('-created')
  .limit(30)
  .populate('poster')
  .exec(function(err, newsItems) {

    if(err) return next(err);

    addVotesToNewsItems(newsItems, req.user, function (err, newsItems) {

      if(err) return next(err);

      res.render('news/index', {
        title: 'Recent News',
        items: newsItems
      });

    });

  });
};

exports.userNews = function(req, res) {
  User
  .find({'username': req.params.id})
  .exec(function(err, users) {
    NewsItem
    .find({'poster': users[0].id})
    .sort('-created')
    .limit(30)
    .populate('poster')
    .exec(function(err, newsItems) {

      if(err) return next(err);

      addVotesToNewsItems(newsItems, req.user, function (err, newsItems) {

        if(err) return next(err);

        res.render('news/index', {
          title: 'News shared by ' + users[0].username,
          items: newsItems,
          filteredUser: users[0].username
        });

      });

    });
  });
}

function addVotesToNewsItems(newsItems, user, callback) {

  Vote
  .find({ item: { $in: newsItems.map(function (item) { return item.id; }) } })
  .exec(function (err, votes) {

    if(err) return callback(err);

    newsItems = newsItems.map(function (item) {
      item = typeof item.toObject === 'function' ? item.toObject() : item;

      item.votes = votes
        .filter(function (vote) {
          return vote.item.toString() === item._id.toString();
        }).reduce(function (prev, curr, i) {

          // count this item as voted for if the logged in user has a vote tallied
          if(user && user.id && curr.voter.toString() === user.id.toString()) {
            item.votedFor = true;
          }

          return prev + curr.amount;
        }, 0);

      return item;
    });

    callback(null, newsItems);
  });
}

/**
 * GET /news/submit
 * Submit news.
 */

exports.submitNews = function(req, res) {
  //if (!req.user) return res.redirect('/login');
  res.render('news/submit', {
    title: 'Submit News'
  });
};

/**
 * POST /news/submit
 * Submit news item.
 * @param {string} title
 * @param {string} url
 */

exports.postNews = function(req, res, next) {
  req.assert('title', 'Title cannot be blank.').notEmpty();
  req.assert('url', 'URL cannot be blank.').notEmpty();

  var errors = req.validationErrors();

  if (errors) {
    req.flash('errors', errors);
    return res.redirect('/news/submit');
  }

  console.log("Posting for user id "+req.user.id);

  var newsItem = new NewsItem({
    title: req.body.title,
    url: req.body.url,
    poster: req.user.id
  });

  newsItem.save(function(err) {
    if (err) {
      if (err.code === 11000) {
        req.flash('errors', { msg: 'That URL already exists as a news item.' });
      }
      return res.redirect('/news/submit');
    }

    req.flash('success', { msg: 'News item submitted. Thanks!' });
    res.redirect('/news');
  });

};

/**
 * PUT /news/:item
 * Vote up a news item.
 * @param {number} amount Which direction and amount to vote up a news item (limited to +1 for now)
 */

exports.vote = function (req, res, next) {

  req.assert('amount', 'Items can only be upvoted.').equals('1');

  var errors = req.validationErrors();

  if (errors) {
    res.flash('errors', errors);
    return res.redirect(req.get('referrer') || '/');
  }

  var vote = new Vote({
    item: req.params.id,
    voter: req.user.id,
    amount: req.body.amount
  });

  vote.save(function (err) {
    if (err) {
      if (err.code === 11000) {
        req.flash('errors', { msg: 'You can only upvote an item once.' });
      }
      return res.redirect(req.get('referrer') || '/');
    }

    req.flash('success', { msg: 'News item upvoted. Awesome!' });
    res.redirect(req.get('referrer') || '/');
  });

};
<|endoftext|>


<commit_before>const Moment = require('moment')
const kue = require('kue')
const Pill = require('./../models/Pill')

let queue = kue.createQueue()

/**
 * GET /pill
 */
exports.pillsGet = function (req, res) {
  let renderData = {
    title: 'Pills',
    moment: Moment,
    pills: null
  }
  Pill.find({ userId: req.user.id }, function (err, pills) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.render('pills', renderData)
    } else {
      renderData.pills = pills
      res.render('pills', renderData)
    }
  })
}

/**
 * GET /pill/:id
*/
exports.pillGet = function (req, res) {
  let renderData = {
    title: 'Pills',
    moment: Moment,
    pill: null
  }
  let pillId = req.params.id
  Pill.findById(pillId, function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else if (!pill) {
      req.flash('error', { msg: 'Pill doesn\'t exist.' })
      res.redirect('/pill')
    } else if (pill.userId.toString() !== req.user.id.toString()) {
      req.flash('error', { msg: 'You are not authorized to view this page.' })
      res.redirect('/pill')
    } else {
      renderData.pill = pill
      res.render('pill', renderData)
    }
  })
}

/**
 * PUT /pill/:id
*/
exports.pillPut = function (req, res) {
  let pillId = req.params.id

  let errors = []
  if (!req.body.title || req.body.title.length < 3) { errors.push({ msg: 'Pill title must be at least 3 characters long.' }) }
  if (errors && errors.length > 0) {
    req.flash('error', errors)
    return res.redirect('/pills')
  }

  Pill.findById(pillId, function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else if (!pill) {
      req.flash('error', { msg: 'Pill doesn\'t exist.' })
      res.redirect('/pill')
    } else if (pill.userId.toString() !== req.user.id.toString()) {
      req.flash('error', { msg: 'You are not authorized to edit this pill.' })
      res.redirect('/pill')
    } else {
      pill.title = req.body.title
      pill.description = req.body.description
      if (req.body.icon && req.body.icon.length > 0) {
        pill.icon = req.body.icon
      }
      if (req.body.methods && req.body.methods.length > 0) {
        pill.methods = req.body.methods
      }

      pill.save(function (err) {
        if (err) {
          req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
          res.redirect('/pill')
        } else {
          req.flash('success', { msg: 'Pill has been updated.' })
          res.redirect('/pill')
        }
      })
    }
  })
}

/**
 * DELETE /pill/:id
*/
exports.pillDelete = function (req, res) {
  let pillId = req.params.id
  Pill.findById(pillId, function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else if (!pill) {
      req.flash('error', { msg: 'Pill doesn\'t exist.' })
      res.redirect('/pill')
    } else if (pill.userId.toString() !== req.user.id.toString()) {
      req.flash('error', { msg: 'You are not authorized to delete this pill.' })
      res.redirect('/pill')
    } else {
      pill.remove(function (err) {
        if (err) {
          req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
          res.redirect('/pill')
        } else {
          req.flash('success', { msg: 'Pill has been deleted.' })
          res.redirect('/pill')
        }
      })
    }
  })
}

/**
 * POST /pill
*/
exports.pillPost = function (req, res) {
//  const momentFormat = 'D MMMM, YYYY h:m A'
//  let startDateString = req.body.startDate + ' ' + req.body.startTime
//  let nextDateString = req.body.nextDate + ' ' + req.body.nextTime
//  let startMoment = new Moment(startDateString, momentFormat)
//  let nextMoment = new Moment(nextDateString, momentFormat)
  let startDateString = req.body.startDateUTC
  let nextDateString = req.body.nextDateUTC
  let startMoment = Moment.utc(startDateString)
  let nextMoment = Moment.utc(nextDateString)

  let errors = []
  if (!req.body.title || req.body.title.length < 3) { errors.push({ msg: 'Pill title must be at least 3 characters long.' }) }
  if (!req.body.methods || req.body.methods.length < 1) { errors.push({ msg: 'Should choose at least 1 reminding method.' }) }
  if (!startMoment.isValid()) { errors.push({ msg: 'Start Date/Time is invalid.' }) }
  if (!nextMoment.isValid()) { errors.push({ msg: 'Next Date/Time is invalid.' }) }
  if (nextMoment.isSameOrBefore(startMoment)) { errors.push({ msg: 'Next Date/Time must be after the Start Date/Time.' }) }
  if (errors && errors.length > 0) {
    req.flash('error', errors)
    return res.redirect('/pills')
  }

  let pill = new Pill()
  pill.userId = req.user.id
  pill.title = req.body.title
  pill.description = req.body.description
  pill.icon = req.body.icon
  pill.methods = req.body.methods
  pill.rule.startDate = startMoment.toDate()
  pill.rule.step = nextMoment - startMoment

  pill.save(function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else {
      req.flash('success', { msg: 'New pill added successfully.' })
      let remindRemindData = {
        userId: pill.userId,
        pillId: pill.id
      }
      queue.create('remind-remind', remindRemindData).delay(pill.rule.currentDate).attempts(5).backoff(true).save(function (err) {
        if (err) {
          req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
        }
        res.redirect('/pill')
      })
    }
  })
}
<commit_msg>change /pills redirects to /pill<commit_after>const Moment = require('moment')
const kue = require('kue')
const Pill = require('./../models/Pill')

let queue = kue.createQueue()

/**
 * GET /pill
 */
exports.pillsGet = function (req, res) {
  let renderData = {
    title: 'Pills',
    moment: Moment,
    pills: null
  }
  Pill.find({ userId: req.user.id }, function (err, pills) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.render('pills', renderData)
    } else {
      renderData.pills = pills
      res.render('pills', renderData)
    }
  })
}

/**
 * GET /pill/:id
*/
exports.pillGet = function (req, res) {
  let renderData = {
    title: 'Pills',
    moment: Moment,
    pill: null
  }
  let pillId = req.params.id
  Pill.findById(pillId, function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else if (!pill) {
      req.flash('error', { msg: 'Pill doesn\'t exist.' })
      res.redirect('/pill')
    } else if (pill.userId.toString() !== req.user.id.toString()) {
      req.flash('error', { msg: 'You are not authorized to view this page.' })
      res.redirect('/pill')
    } else {
      renderData.pill = pill
      res.render('pill', renderData)
    }
  })
}

/**
 * PUT /pill/:id
*/
exports.pillPut = function (req, res) {
  let pillId = req.params.id

  let errors = []
  if (!req.body.title || req.body.title.length < 3) { errors.push({ msg: 'Pill title must be at least 3 characters long.' }) }
  if (errors && errors.length > 0) {
    req.flash('error', errors)
    return res.redirect('/pill')
  }

  Pill.findById(pillId, function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else if (!pill) {
      req.flash('error', { msg: 'Pill doesn\'t exist.' })
      res.redirect('/pill')
    } else if (pill.userId.toString() !== req.user.id.toString()) {
      req.flash('error', { msg: 'You are not authorized to edit this pill.' })
      res.redirect('/pill')
    } else {
      pill.title = req.body.title
      pill.description = req.body.description
      if (req.body.icon && req.body.icon.length > 0) {
        pill.icon = req.body.icon
      }
      if (req.body.methods && req.body.methods.length > 0) {
        pill.methods = req.body.methods
      }

      pill.save(function (err) {
        if (err) {
          req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
          res.redirect('/pill')
        } else {
          req.flash('success', { msg: 'Pill has been updated.' })
          res.redirect('/pill')
        }
      })
    }
  })
}

/**
 * DELETE /pill/:id
*/
exports.pillDelete = function (req, res) {
  let pillId = req.params.id
  Pill.findById(pillId, function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else if (!pill) {
      req.flash('error', { msg: 'Pill doesn\'t exist.' })
      res.redirect('/pill')
    } else if (pill.userId.toString() !== req.user.id.toString()) {
      req.flash('error', { msg: 'You are not authorized to delete this pill.' })
      res.redirect('/pill')
    } else {
      pill.remove(function (err) {
        if (err) {
          req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
          res.redirect('/pill')
        } else {
          req.flash('success', { msg: 'Pill has been deleted.' })
          res.redirect('/pill')
        }
      })
    }
  })
}

/**
 * POST /pill
*/
exports.pillPost = function (req, res) {
//  const momentFormat = 'D MMMM, YYYY h:m A'
//  let startDateString = req.body.startDate + ' ' + req.body.startTime
//  let nextDateString = req.body.nextDate + ' ' + req.body.nextTime
//  let startMoment = new Moment(startDateString, momentFormat)
//  let nextMoment = new Moment(nextDateString, momentFormat)
  let startDateString = req.body.startDateUTC
  let nextDateString = req.body.nextDateUTC
  let startMoment = Moment.utc(startDateString)
  let nextMoment = Moment.utc(nextDateString)

  let errors = []
  if (!req.body.title || req.body.title.length < 3) { errors.push({ msg: 'Pill title must be at least 3 characters long.' }) }
  if (!req.body.methods || req.body.methods.length < 1) { errors.push({ msg: 'Should choose at least 1 reminding method.' }) }
  if (!startMoment.isValid()) { errors.push({ msg: 'Start Date/Time is invalid.' }) }
  if (!nextMoment.isValid()) { errors.push({ msg: 'Next Date/Time is invalid.' }) }
  if (nextMoment.isSameOrBefore(startMoment)) { errors.push({ msg: 'Next Date/Time must be after the Start Date/Time.' }) }
  if (errors && errors.length > 0) {
    req.flash('error', errors)
    return res.redirect('/pill')
  }

  let pill = new Pill()
  pill.userId = req.user.id
  pill.title = req.body.title
  pill.description = req.body.description
  pill.icon = req.body.icon
  pill.methods = req.body.methods
  pill.rule.startDate = startMoment.toDate()
  pill.rule.step = nextMoment - startMoment

  pill.save(function (err, pill) {
    if (err) {
      req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
      res.redirect('/pill')
    } else {
      req.flash('success', { msg: 'New pill added successfully.' })
      let remindRemindData = {
        userId: pill.userId,
        pillId: pill.id
      }
      queue.create('remind-remind', remindRemindData).delay(pill.rule.currentDate).attempts(5).backoff(true).save(function (err) {
        if (err) {
          req.flash('error', { msg: 'An error occured, contact system admin for more info.' })
        }
        res.redirect('/pill')
      })
    }
  })
}
<|endoftext|>


<commit_before>require('dotenv').config()
const autoprefixer = require('autoprefixer')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const path = require('path')
const webpack = require('webpack')

//=========================================================
//  ENVIRONMENT VARS
//---------------------------------------------------------
const NODE_ENV = process.env.NODE_ENV
const FIREBASE_API_KEY = process.env.FIREBASE_API_KEY
const FIREBASE_AUTH_DOMAIN = process.env.FIREBASE_AUTH_DOMAIN
const FIREBASE_DATA_URL = process.env.FIREBASE_DATA_URL
const FIREBASE_STORAGE_BUCKET = process.env.FIREBASE_STORAGE_BUCKET

const ENV_DEVELOPMENT = NODE_ENV === 'development'
const ENV_PRODUCTION = NODE_ENV === 'production'
const ENV_TEST = NODE_ENV === 'test'

const HOST = process.env.HOST || 'localhost'
const PORT = process.env.PORT || 3000

//=========================================================
//  LOADERS
//---------------------------------------------------------
const loaders = {
  js: {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
  },
  scss: {
    test: /\.scss$/,
    loader: 'style!css!resolve-url!postcss-loader!sass',
  },
  scssProd: {
    test: /\.scss$/,
    loader: ExtractTextPlugin.extract('css!resolve-url!postcss-loader!sass')
  },
  img: {
    test: /\.(png|jpg|gif|svg)$/,
    loader: 'url-loader?limit=8192',
  }, // inline base64 URLs for <=8k images, direct URLs for the rest

}

//=========================================================
//  CONFIG
//---------------------------------------------------------
const config = {}
module.exports = config

config.resolve = {
  extensions: ['', '.ts', '.js'],
  modulesDirectories: ['node_modules', 'web_modules'],
  root: path.resolve('.')
}

config.plugins = [
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
    'process.env.FIREBASE_API_KEY': JSON.stringify(FIREBASE_API_KEY),
    'process.env.FIREBASE_AUTH_DOMAIN': JSON.stringify(FIREBASE_AUTH_DOMAIN),
    'process.env.FIREBASE_DATA_URL': JSON.stringify(FIREBASE_DATA_URL),
    'process.env.FIREBASE_STORAGE_BUCKET': JSON.stringify(FIREBASE_STORAGE_BUCKET),
  })
]

config.postcss = [
  autoprefixer({ browsers: ['last 3 versions', 'Firefox ESR'] })
]

config.sassLoader = {
  outputStyle: 'compressed',
  precision: 10,
  sourceComments: false
}

//=====================================
//  DEVELOPMENT or PRODUCTION
//-------------------------------------
if (ENV_DEVELOPMENT || ENV_PRODUCTION) {
  config.devtool = 'source-map'

  config.entry = {
    main: [
      './src/main'
    ],
    vendor: [
      'babel-polyfill',
      'classnames',
      'firebase',
      'history',
      'react',
      'react-dom',
      'react-redux',
      'react-router',
      'redux',
      'redux-thunk'
    ]
  }

  config.output = {
    filename: '[name].js',
    path: path.resolve('./public'),
    publicPath: '/'
  }

  config.plugins.push(
    new webpack.optimize.CommonsChunkPlugin('vendor', '[name].js'),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      hash: true,
      inject: 'body',
      template: './src/index.html'
    })
  )
}

config.context = path.join(__dirname)

//=====================================
//  DEVELOPMENT
//-------------------------------------
if (ENV_DEVELOPMENT) {
  config.entry.main.unshift(
    `webpack-dev-server/client?http://${HOST}:${PORT}`,
    'webpack/hot/dev-server'
  )

  config.module = {
    loaders: [
      loaders.img,
      loaders.scss,
      {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: {
        plugins: [
          [
            'react-transform',
            {transforms: [ {transform: 'react-transform-hmr', imports: ['react'], locals: ['module']} ]}
          ]
        ]
      }}
    ]
  }

  config.plugins.push(
    new webpack.HotModuleReplacementPlugin()
  )

  config.devServer = {
    contentBase: './src',
    historyApiFallback: true,
    host: HOST,
    hot: true,
    port: PORT,
    publicPath: config.output.publicPath,
    stats: {
      cached: true,
      cachedAssets: true,
      chunks: true,
      chunkModules: false,
      colors: true,
      hash: false,
      reasons: true,
      timings: true,
      version: false
    }
  }
}

//=====================================
//  PRODUCTION
//-------------------------------------
if (ENV_PRODUCTION) {
  config.module = {
    loaders: [
      loaders.js,
      loaders.img,
      loaders.scssProd,
    ]
  }

  config.plugins.push(
    new ExtractTextPlugin('styles.css'),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      compress: {
        dead_code: true, // eslint-disable-line camelcase
        screw_ie8: true, // eslint-disable-line camelcase
        unused: true,
        warnings: false
      }
    }),
    new CopyWebpackPlugin([{
      from: 'src/img',
      to: '/img',
    }])
  )
}

//=====================================
//  TEST
//-------------------------------------
if (ENV_TEST) {
  config.devtool = 'inline-source-map'

  config.module = {
    loaders: [
      loaders.js,
      loaders.scss
    ]
  }
}
<commit_msg>Add proper target directory for image transfer<commit_after>require('dotenv').config()
const autoprefixer = require('autoprefixer')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const path = require('path')
const webpack = require('webpack')

//=========================================================
//  ENVIRONMENT VARS
//---------------------------------------------------------
const NODE_ENV = process.env.NODE_ENV
const FIREBASE_API_KEY = process.env.FIREBASE_API_KEY
const FIREBASE_AUTH_DOMAIN = process.env.FIREBASE_AUTH_DOMAIN
const FIREBASE_DATA_URL = process.env.FIREBASE_DATA_URL
const FIREBASE_STORAGE_BUCKET = process.env.FIREBASE_STORAGE_BUCKET

const ENV_DEVELOPMENT = NODE_ENV === 'development'
const ENV_PRODUCTION = NODE_ENV === 'production'
const ENV_TEST = NODE_ENV === 'test'

const HOST = process.env.HOST || 'localhost'
const PORT = process.env.PORT || 3000

//=========================================================
//  LOADERS
//---------------------------------------------------------
const loaders = {
  js: {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
  },
  scss: {
    test: /\.scss$/,
    loader: 'style!css!resolve-url!postcss-loader!sass',
  },
  scssProd: {
    test: /\.scss$/,
    loader: ExtractTextPlugin.extract('css!resolve-url!postcss-loader!sass')
  },
  img: {
    test: /\.(png|jpg|gif|svg)$/,
    loader: 'url-loader?limit=8192',
  }, // inline base64 URLs for <=8k images, direct URLs for the rest

}

//=========================================================
//  CONFIG
//---------------------------------------------------------
const config = {}
module.exports = config

config.resolve = {
  extensions: ['', '.ts', '.js'],
  modulesDirectories: ['node_modules', 'web_modules'],
  root: path.resolve('.')
}

config.plugins = [
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
    'process.env.FIREBASE_API_KEY': JSON.stringify(FIREBASE_API_KEY),
    'process.env.FIREBASE_AUTH_DOMAIN': JSON.stringify(FIREBASE_AUTH_DOMAIN),
    'process.env.FIREBASE_DATA_URL': JSON.stringify(FIREBASE_DATA_URL),
    'process.env.FIREBASE_STORAGE_BUCKET': JSON.stringify(FIREBASE_STORAGE_BUCKET),
  })
]

config.postcss = [
  autoprefixer({ browsers: ['last 3 versions', 'Firefox ESR'] })
]

config.sassLoader = {
  outputStyle: 'compressed',
  precision: 10,
  sourceComments: false
}

//=====================================
//  DEVELOPMENT or PRODUCTION
//-------------------------------------
if (ENV_DEVELOPMENT || ENV_PRODUCTION) {
  config.devtool = 'source-map'

  config.entry = {
    main: [
      './src/main'
    ],
    vendor: [
      'babel-polyfill',
      'classnames',
      'firebase',
      'history',
      'react',
      'react-dom',
      'react-redux',
      'react-router',
      'redux',
      'redux-thunk'
    ]
  }

  config.output = {
    filename: '[name].js',
    path: path.resolve('./public'),
    publicPath: '/'
  }

  config.plugins.push(
    new webpack.optimize.CommonsChunkPlugin('vendor', '[name].js'),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      hash: true,
      inject: 'body',
      template: './src/index.html'
    })
  )
}

config.context = path.join(__dirname)

//=====================================
//  DEVELOPMENT
//-------------------------------------
if (ENV_DEVELOPMENT) {
  config.entry.main.unshift(
    `webpack-dev-server/client?http://${HOST}:${PORT}`,
    'webpack/hot/dev-server'
  )

  config.module = {
    loaders: [
      loaders.img,
      loaders.scss,
      {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: {
        plugins: [
          [
            'react-transform',
            {transforms: [ {transform: 'react-transform-hmr', imports: ['react'], locals: ['module']} ]}
          ]
        ]
      }}
    ]
  }

  config.plugins.push(
    new webpack.HotModuleReplacementPlugin()
  )

  config.devServer = {
    contentBase: './src',
    historyApiFallback: true,
    host: HOST,
    hot: true,
    port: PORT,
    publicPath: config.output.publicPath,
    stats: {
      cached: true,
      cachedAssets: true,
      chunks: true,
      chunkModules: false,
      colors: true,
      hash: false,
      reasons: true,
      timings: true,
      version: false
    }
  }
}

//=====================================
//  PRODUCTION
//-------------------------------------
if (ENV_PRODUCTION) {
  config.module = {
    loaders: [
      loaders.js,
      loaders.img,
      loaders.scssProd,
    ]
  }

  config.plugins.push(
    new ExtractTextPlugin('styles.css'),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin({
      mangle: true,
      compress: {
        dead_code: true, // eslint-disable-line camelcase
        screw_ie8: true, // eslint-disable-line camelcase
        unused: true,
        warnings: false
      }
    }),
    new CopyWebpackPlugin([{
      from: 'src/img',
      to: 'img',
    }])
  )
}

//=====================================
//  TEST
//-------------------------------------
if (ENV_TEST) {
  config.devtool = 'inline-source-map'

  config.module = {
    loaders: [
      loaders.js,
      loaders.scss
    ]
  }
}
<|endoftext|>