feat: Добавлены тесты React компонентов. Тесты базовой логики, Граничных тестов, стилей

This commit is contained in:
Ilia Mashkov
2025-11-23 13:48:50 +03:00
parent ec6867d7a0
commit 6de84f3143
5 changed files with 242 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { Button } from './Button'
describe('Button', () => {
// Тест на рендеринг кнопки
it('должна рендериться корректно', () => {
render(<Button>Test Button</Button>)
expect(screen.getByText('Test Button')).toBeInTheDocument()
})
// Тест на применение класса варианта
it('должна применять класс варианта', () => {
render(<Button variant='regular'>Regular Button</Button>)
const button = screen.getByText('Regular Button')
// Проверяем наличие класса, который генерируется CSS модулями (частичное совпадение)
expect(button.className).toMatch(/regular/)
})
// Тест на обработку клика
it('должна вызывать обработчик onClick при клике', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click Me</Button>)
fireEvent.click(screen.getByText('Click Me'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
// Тест на отключенное состояние
it('должна быть отключена при передаче пропса disabled', () => {
render(<Button disabled>Disabled Button</Button>)
expect(screen.getByText('Disabled Button')).toBeDisabled()
})
})

View File

@@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react'
import { Card } from './Card'
describe('Card', () => {
// Тест на рендеринг заголовка и описания
it('должна рендерить заголовок и описание', () => {
render(<Card title='1992' description='Test Description' />)
expect(screen.getByText('1992')).toBeInTheDocument()
expect(screen.getByText('Test Description')).toBeInTheDocument()
})
// Тест на рендеринг числового заголовка
it('должна корректно рендерить числовой заголовок', () => {
render(<Card title={2023} description='Year Description' />)
expect(screen.getByText('2023')).toBeInTheDocument()
})
})

View File

@@ -0,0 +1,56 @@
import { fireEvent, render, screen } from '@testing-library/react'
import gsap from 'gsap'
import { HISTORICAL_PERIODS } from '@/entities/TimePeriod'
import { CircleTimeline } from './CircleTimeline'
describe('CircleTimeline', () => {
const mockOnPeriodChange = jest.fn()
const defaultProps = {
periods: HISTORICAL_PERIODS,
activeIndex: 0,
onPeriodChange: mockOnPeriodChange,
rotation: 0,
}
beforeEach(() => {
jest.clearAllMocks()
})
// Тест на рендеринг правильного количества точек
it('должна рендерить правильное количество точек', () => {
render(<CircleTimeline {...defaultProps} />)
const points = screen.getAllByRole('button')
expect(points).toHaveLength(HISTORICAL_PERIODS.length)
})
// Тест на активную точку
it('должна корректно отображать активную точку', () => {
render(<CircleTimeline {...defaultProps} activeIndex={1} />)
const points = screen.getAllByRole('button')
// Проверяем aria-current для доступности
expect(points[1]).toHaveAttribute('aria-current', 'true')
expect(points[0]).toHaveAttribute('aria-current', 'false')
})
// Тест на клик по точке
it('должна вызывать onPeriodChange при клике по точке', () => {
render(<CircleTimeline {...defaultProps} />)
const points = screen.getAllByRole('button')
fireEvent.click(points[2])
expect(mockOnPeriodChange).toHaveBeenCalledWith(2)
})
// Тест на вызов GSAP анимации
it('должна вызывать GSAP анимацию при изменении rotation', () => {
const { rerender } = render(<CircleTimeline {...defaultProps} />)
rerender(<CircleTimeline {...defaultProps} rotation={60} />)
// Проверяем, что gsap.to был вызван
expect(gsap.to).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,52 @@
import { render, screen } from '@testing-library/react'
import { EventsCarousel } from './EventsCarousel'
// Мокаем Swiper
jest.mock('swiper/react', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Swiper: ({ children }: any) => <div data-testid='swiper'>{children}</div>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
SwiperSlide: ({ children }: any) => (
<div data-testid='swiper-slide'>{children}</div>
),
}))
jest.mock('swiper/modules', () => ({
Navigation: jest.fn(),
Pagination: jest.fn(),
FreeMode: jest.fn(),
}))
// Мокаем стили Swiper
jest.mock('swiper/css', () => ({}))
jest.mock('swiper/css/navigation', () => ({}))
jest.mock('swiper/css/pagination', () => ({}))
describe('EventsCarousel', () => {
const mockEvents = [
{ id: 1, year: 1990, description: 'Event 1' },
{ id: 2, year: 1991, description: 'Event 2' },
]
// Тест на рендеринг событий
it('должен рендерить переданные события', () => {
render(<EventsCarousel events={mockEvents} visible={true} />)
// Проверяем, что слайды отрендерились
const slides = screen.getAllByTestId('swiper-slide')
expect(slides).toHaveLength(mockEvents.length)
// Проверяем контент внутри слайдов (Card компонент)
expect(screen.getByText('1990')).toBeInTheDocument()
expect(screen.getByText('Event 1')).toBeInTheDocument()
})
// Тест на видимость
it('должен применять класс visible, когда visible=true', () => {
render(<EventsCarousel events={mockEvents} visible={true} />)
// В реальном компоненте класс применяется к контейнеру Swiper или обертке
// Здесь мы проверяем наличие контента, так как opacity управляется через CSS/GSAP
expect(screen.getByTestId('swiper')).toBeInTheDocument()
})
})

View File

@@ -0,0 +1,82 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { HISTORICAL_PERIODS } from '@/entities/TimePeriod'
import { TimeFrameSlider } from './TimeFrameSlider'
// Мокаем дочерние компоненты, чтобы тестировать изолированно
jest.mock('../CircleTimeline/CircleTimeline', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
CircleTimeline: ({ activeIndex, onPeriodChange }: any) => (
<div data-testid='circle-timeline'>
<button onClick={() => onPeriodChange(activeIndex + 1)}>
Next Period
</button>
<span>Active: {activeIndex}</span>
</div>
),
}))
jest.mock('../EventsCarousel/EventsCarousel', () => ({
EventsCarousel: () => <div data-testid='events-carousel'>Carousel</div>,
}))
describe('TimeFrameSlider', () => {
beforeEach(() => {
jest.clearAllMocks()
})
// Тест на рендеринг заголовка
it('должен рендерить заголовок', () => {
render(<TimeFrameSlider />)
expect(screen.getByText('Исторические даты')).toBeInTheDocument()
})
// Тест на отображение начального периода
it('должен отображать начальный период (первый в списке)', () => {
render(<TimeFrameSlider />)
const firstPeriod = HISTORICAL_PERIODS[0]
expect(screen.getByText(firstPeriod.yearFrom)).toBeInTheDocument()
expect(screen.getByText(firstPeriod.yearTo)).toBeInTheDocument()
})
// Тест на переключение вперед
it('должен переключать период вперед при клике на кнопку "Следующий"', () => {
render(<TimeFrameSlider />)
const nextButton = screen.getByLabelText('Следующий период')
fireEvent.click(nextButton)
// Проверяем, что отображается второй период
const secondPeriod = HISTORICAL_PERIODS[1]
expect(screen.getByText(secondPeriod.yearFrom)).toBeInTheDocument()
})
// Тест на переключение назад
it('должен переключать период назад при клике на кнопку "Предыдущий"', () => {
render(<TimeFrameSlider />)
// Сначала переключаем вперед, чтобы не быть на первом элементе (хотя логика циклична)
const nextButton = screen.getByLabelText('Следующий период')
fireEvent.click(nextButton)
// Теперь назад
const prevButton = screen.getByLabelText('Предыдущий период')
fireEvent.click(prevButton)
// Должны вернуться к первому периоду
const firstPeriod = HISTORICAL_PERIODS[0]
expect(screen.getByText(firstPeriod.yearFrom)).toBeInTheDocument()
})
// Тест на пагинацию (точки)
it('должен переключать период при клике на точку пагинации', () => {
render(<TimeFrameSlider />)
const dots = screen.getAllByLabelText(/Перейти к периоду/)
fireEvent.click(dots[2]) // Клик по 3-й точке
const thirdPeriod = HISTORICAL_PERIODS[2]
expect(screen.getByText(thirdPeriod.yearFrom)).toBeInTheDocument()
})
})