/* global describe, it, expect */
import webdriver from 'next-webdriver'
import { join } from 'path'
import { check, File, waitFor, getReactErrorOverlayContent, getBrowserBodyText } from 'next-test-utils'

export default (context, render) => {
  describe('Error Recovery', () => {
    it('should recover from 404 after a page has been added', async () => {
      let browser
      const newPage = new File(join(__dirname, '../', 'pages', 'hmr', 'new-page.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/new-page')

        expect(await browser.elementByCss('body').text()).toMatch(/This page could not be found/)

        // Add the page
        newPage.write('export default () => (<div id="new-page">the-new-page</div>)')

        await check(
          () => getBrowserBodyText(browser),
          /the-new-page/
        )

        newPage.delete()

        await check(
          () => getBrowserBodyText(browser),
          /This page could not be found/
        )
      } catch (err) {
        newPage.delete()
        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should have installed the react-overlay-editor editor handler', async () => {
      let browser
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      aboutPage.replace('</div>', 'div')

      try {
        browser = await webdriver(context.appPort, '/hmr/about')

        // react-error-overlay uses the following inline style if an editorHandler is installed
        expect(await getReactErrorOverlayContent(browser)).toMatch(/style="cursor: pointer;"/)

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the about page/
        )
      } catch (err) {
        aboutPage.restore()
        if (browser) {
          await check(
            () => getBrowserBodyText(browser),
            /This is the about page/
          )
        }

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should detect syntax errors and recover', async () => {
      let browser
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/about')
        const text = await browser.elementByCss('p').text()
        expect(text).toBe('This is the about page.')

        aboutPage.replace('</div>', 'div')

        expect(await getReactErrorOverlayContent(browser)).toMatch(/Unterminated JSX contents/)

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the about page/
        )
      } catch (err) {
        aboutPage.restore()
        if (browser) {
          await check(
            () => getBrowserBodyText(browser),
            /This is the about page/
          )
        }

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should show the error on all pages', async () => {
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      let browser
      try {
        aboutPage.replace('</div>', 'div')

        browser = await webdriver(context.appPort, '/hmr/contact')

        expect(await getReactErrorOverlayContent(browser)).toMatch(/Unterminated JSX contents/)

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the contact page/
        )
      } catch (err) {
        aboutPage.restore()
        if (browser) {
          await check(
            () => getBrowserBodyText(browser),
            /This is the contact page/
          )
        }

        throw err
      } finally {
        aboutPage.restore()
        if (browser) {
          browser.close()
        }
      }
    })

    it('should detect runtime errors on the module scope', async () => {
      let browser
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/about')
        const text = await browser
          .elementByCss('p').text()
        expect(text).toBe('This is the about page.')

        aboutPage.replace('export', 'aa=20;\nexport')

        expect(await getReactErrorOverlayContent(browser)).toMatch(/aa is not defined/)

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the about page/
        )
      } finally {
        aboutPage.restore()
        if (browser) {
          browser.close()
        }
      }
    })

    it('should recover from errors in the render function', async () => {
      let browser
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/about')
        const text = await browser.elementByCss('p').text()

        expect(text).toBe('This is the about page.')

        aboutPage.replace('return', 'throw new Error("an-expected-error");\nreturn')

        expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error/)

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the about page/
        )
      } catch (err) {
        aboutPage.restore()
        if (browser) {
          await check(
            () => getBrowserBodyText(browser),
            /This is the about page/
          )
        }

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should recover after exporting an invalid page', async () => {
      let browser
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/about')
        const text = await browser.elementByCss('p').text()
        expect(text).toBe('This is the about page.')

        aboutPage.replace('export default', 'export default "not-a-page"\nexport const fn = ')

        await check(
          () => getBrowserBodyText(browser),
          /The default export is not a React Component/
        )

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the about page/
        )
      } catch (err) {
        aboutPage.restore()

        if (browser) {
          await check(
            () => getBrowserBodyText(browser),
            /This is the about page/
          )
        }

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should recover after a bad return from the render function', async () => {
      let browser
      const aboutPage = new File(join(__dirname, '../', 'pages', 'hmr', 'about.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/about')
        const text = await browser.elementByCss('p').text()
        expect(text).toBe('This is the about page.')

        aboutPage.replace('export default', 'export default () => /search/ \nexport const fn = ')

        await check(
          () => getBrowserBodyText(browser),
          /Objects are not valid as a React child/
        )

        aboutPage.restore()

        await check(
          () => getBrowserBodyText(browser),
          /This is the about page/
        )
      } catch (err) {
        aboutPage.restore()

        if (browser) {
          await check(
            () => getBrowserBodyText(browser),
            /This is the about page/
          )
        }

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should recover from errors in getInitialProps in client', async () => {
      let browser
      const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr')
        await browser.elementByCss('#error-in-gip-link').click()

        expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error-in-gip/)

        erroredPage.replace('throw error', 'return {}')

        await check(
          () => getBrowserBodyText(browser),
          /Hello/
        )

        erroredPage.restore()

        await check(
          async () => {
            await browser.refresh()
            const text = await browser.elementByCss('body').text()
            if (text.includes('Hello')) {
              await waitFor(2000)
              throw new Error('waiting')
            }
            return getReactErrorOverlayContent(browser)
          },
          /an-expected-error-in-gip/
        )
      } catch (err) {
        erroredPage.restore()

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })

    it('should recover after an error reported via SSR', async () => {
      let browser
      const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
      try {
        browser = await webdriver(context.appPort, '/hmr/error-in-gip')

        expect(await getReactErrorOverlayContent(browser)).toMatch(/an-expected-error-in-gip/)

        const erroredPage = new File(join(__dirname, '../', 'pages', 'hmr', 'error-in-gip.js'))
        erroredPage.replace('throw error', 'return {}')

        await check(
          () => getBrowserBodyText(browser),
          /Hello/
        )

        erroredPage.restore()

        await check(
          async () => {
            await browser.refresh()
            const text = await getBrowserBodyText(browser)
            if (text.includes('Hello')) {
              await waitFor(2000)
              throw new Error('waiting')
            }
            return getReactErrorOverlayContent(browser)
          },
          /an-expected-error-in-gip/
        )
      } catch (err) {
        erroredPage.restore()

        throw err
      } finally {
        if (browser) {
          browser.close()
        }
      }
    })
  })
}
