import { cleanup, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ErrorBoundary } from '../components/ErrorBoundary'

function ThrowOnRender(): React.JSX.Element {
  throw new Error('Exploded route')
}

describe('ErrorBoundary', () => {
  beforeEach(() => {
    cleanup()
  })

  it('renders its children when no error occurs', () => {
    render(
      <ErrorBoundary>
        <div>Healthy route</div>
      </ErrorBoundary>,
    )

    expect(screen.getByText('Healthy route')).toBeTruthy()
  })

  it('renders the HyperTwist fallback when a child throws', () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

    render(
      <ErrorBoundary>
        <ThrowOnRender />
      </ErrorBoundary>,
    )

    expect(screen.getByText('HyperTwist hit an unexpected browser error')).toBeTruthy()
    expect(screen.getByText('Exploded route')).toBeTruthy()

    errorSpy.mockRestore()
  })

  it('uses the custom fallback when one is provided', () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

    render(
      <ErrorBoundary fallback={<div>Custom fallback</div>}>
        <ThrowOnRender />
      </ErrorBoundary>,
    )

    expect(screen.getByText('Custom fallback')).toBeTruthy()

    errorSpy.mockRestore()
  })

  it('can retry the view after a transient failure', async () => {
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
    let shouldThrow = true

    function FlakyRoute(): React.JSX.Element {
      if (shouldThrow) {
        throw new Error('Transient failure')
      }
      return <div>Recovered route</div>
    }

    render(
      <ErrorBoundary>
        <FlakyRoute />
      </ErrorBoundary>,
    )

    expect(screen.getByText('Transient failure')).toBeTruthy()

    shouldThrow = false
    await userEvent.click(screen.getByRole('button', { name: 'Retry this view' }))

    expect(screen.getByText('Recovered route')).toBeTruthy()

    errorSpy.mockRestore()
  })
})
