Merge branch 'refactor2'

This commit is contained in:
@kirill.komarov
2026-05-13 22:40:45 +05:00
parent c6b542bd95
commit 8165f75a78
17 changed files with 4122 additions and 15 deletions
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'
import { formatPriceRub } from '../format-price'
describe('formatPriceRub', () => {
it('formats cents to rubles', () => {
const result = formatPriceRub(10000)
expect(result).toContain('100')
expect(result).toContain('₽')
})
it('formats zero', () => {
const result = formatPriceRub(0)
expect(result).toContain('0')
expect(result).toContain('₽')
})
it('rounds to integer rubles', () => {
const result = formatPriceRub(10050)
expect(result).toContain('101')
expect(result).toContain('₽')
})
})
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest'
import { getErrorMessage } from '../get-error-message'
describe('getErrorMessage', () => {
it('returns error message for Error instance', () => {
expect(getErrorMessage(new Error('test error'))).toBe('test error')
})
it('returns fallback for non-Error', () => {
expect(getErrorMessage('string error')).toBe('Произошла ошибка')
})
it('returns custom fallback when provided', () => {
expect(getErrorMessage(null, 'custom')).toBe('custom')
})
it('returns fallback for Error with empty message', () => {
expect(getErrorMessage(new Error(''), 'empty')).toBe('empty')
})
})
@@ -0,0 +1,33 @@
import { describe, expect, it } from 'vitest'
import { groupOrdersByStatus } from '../group-orders-by-status'
const STATUSES = ['PENDING', 'PAID', 'DONE'] as const
describe('groupOrdersByStatus', () => {
it('groups and sorts orders by status', () => {
const orders = [
{ status: 'PAID', createdAt: '2024-01-02T10:00:00Z' },
{ status: 'PENDING', createdAt: '2024-01-01T10:00:00Z' },
{ status: 'PAID', createdAt: '2024-01-03T10:00:00Z' },
]
const result = groupOrdersByStatus(orders, STATUSES)
expect(result).toHaveLength(2)
expect(result[0].status).toBe('PENDING')
expect(result[1].status).toBe('PAID')
expect(result[1].items).toHaveLength(2)
expect(result[1].items[0].createdAt).toBe('2024-01-03T10:00:00Z')
})
it('returns empty array for no matching orders', () => {
const result = groupOrdersByStatus([], STATUSES)
expect(result).toHaveLength(0)
})
it('ignores unknown statuses', () => {
const orders = [{ status: 'UNKNOWN', createdAt: '2024-01-01T10:00:00Z' }]
const result = groupOrdersByStatus(orders, STATUSES)
expect(result).toHaveLength(0)
})
})
@@ -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'