import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { afterEach, describe, expect, it, vi } from 'vitest'

import { ErrorBoundary } from './ErrorBoundary'

function ThrowingChild(): never {
  throw new Error('boom')
}

function suppressBoundaryError() {
  const handler = (event: ErrorEvent) => {
    if (event.error instanceof Error && event.error.message === 'boom') {
      event.preventDefault()
    }
  }

  window.addEventListener('error', handler)
  return () => window.removeEventListener('error', handler)
}

describe('ErrorBoundary', () => {
  afterEach(() => {
    vi.restoreAllMocks()
  })

  it('renders children when no error is thrown', () => {
    render(
      <ErrorBoundary>
        <div>safe child</div>
      </ErrorBoundary>,
    )

    expect(screen.getByText('safe child')).toBeInTheDocument()
  })

  it('renders the provided fallback when a child throws', () => {
    vi.spyOn(console, 'error').mockImplementation(() => undefined)
    const cleanupErrorHandler = suppressBoundaryError()

    render(
      <ErrorBoundary fallback={<div>custom fallback</div>}>
        <ThrowingChild />
      </ErrorBoundary>,
    )

    expect(screen.getByText('custom fallback')).toBeInTheDocument()
    cleanupErrorHandler()
  })

  it('renders the default error state and resets to the root path', async () => {
    const user = userEvent.setup()
    vi.spyOn(console, 'error').mockImplementation(() => undefined)
    const cleanupErrorHandler = suppressBoundaryError()

    const locationDescriptor = Object.getOwnPropertyDescriptor(window, 'location')
    Object.defineProperty(window, 'location', {
      configurable: true,
      value: { href: '/current' },
    })

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

    expect(screen.getByText('页面出错了')).toBeInTheDocument()
    expect(screen.getByText('boom')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: '刷新页面' }))

    expect(window.location.href).toBe('/')
    cleanupErrorHandler()

    if (locationDescriptor) {
      Object.defineProperty(window, 'location', locationDescriptor)
    }
  })
})
