Merge branch 'refactor2'
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { Component, type ErrorInfo, type ReactNode } from 'react'
|
||||
import Alert from '@mui/material/Alert'
|
||||
import AlertTitle from '@mui/material/AlertTitle'
|
||||
import Box from '@mui/material/Box'
|
||||
import Button from '@mui/material/Button'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
fallback?: ReactNode
|
||||
onError?: (error: Error, errorInfo: ErrorInfo) => void
|
||||
}
|
||||
|
||||
type State = {
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = { error: null }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
||||
this.props.onError?.(error, errorInfo)
|
||||
}
|
||||
|
||||
private handleReset = (): void => {
|
||||
this.setState({ error: null })
|
||||
}
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.error) {
|
||||
if (this.props.fallback) return this.props.fallback
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 4, display: 'flex', justifyContent: 'center' }}>
|
||||
<Box sx={{ maxWidth: 480 }}>
|
||||
<Alert severity="error">
|
||||
<AlertTitle>Что-то пошло не так</AlertTitle>
|
||||
{this.state.error.message || 'Произошла непредвиденная ошибка.'}
|
||||
</Alert>
|
||||
<Button onClick={this.handleReset} variant="outlined" sx={{ mt: 2 }}>
|
||||
Попробовать снова
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ErrorBoundary } from '../ErrorBoundary'
|
||||
|
||||
const Bomb = ({ shouldThrow }: { shouldThrow?: boolean }) => {
|
||||
if (shouldThrow) throw new Error('boom')
|
||||
return <div>safe</div>
|
||||
}
|
||||
|
||||
describe('ErrorBoundary', () => {
|
||||
it('renders children when no error', () => {
|
||||
render(
|
||||
<ErrorBoundary>
|
||||
<div>hello</div>
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
expect(screen.getByText('hello')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders fallback on error', () => {
|
||||
render(
|
||||
<ErrorBoundary>
|
||||
<Bomb shouldThrow />
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
expect(screen.getByText('Что-то пошло не так')).toBeInTheDocument()
|
||||
expect(screen.getByText('boom')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders custom fallback when provided', () => {
|
||||
render(
|
||||
<ErrorBoundary fallback={<div>custom error ui</div>}>
|
||||
<Bomb shouldThrow />
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
expect(screen.getByText('custom error ui')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('resets after clicking retry button when children are fixed', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
const { rerender } = render(
|
||||
<ErrorBoundary>
|
||||
<Bomb shouldThrow />
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('Что-то пошло не так')).toBeInTheDocument()
|
||||
|
||||
rerender(
|
||||
<ErrorBoundary>
|
||||
<Bomb />
|
||||
</ErrorBoundary>,
|
||||
)
|
||||
|
||||
await user.click(screen.getByText('Попробовать снова'))
|
||||
|
||||
expect(screen.getByText('safe')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export { ErrorBoundary } from './ErrorBoundary'
|
||||
Reference in New Issue
Block a user