import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { act } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { IndicatorButton } from './indicator-button' describe('IndicatorButton', () => { beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { cleanup() vi.clearAllMocks() vi.useRealTimers() }) describe('basic rendering', () => { it('renders button with correct index number', () => { const mockOnClick = vi.fn() render( , ) expect(screen.getByRole('button')).toBeInTheDocument() expect(screen.getByText('01')).toBeInTheDocument() }) it('renders two-digit index numbers', () => { const mockOnClick = vi.fn() render( , ) expect(screen.getByText('10')).toBeInTheDocument() }) it('pads single digit index numbers with leading zero', () => { const mockOnClick = vi.fn() render( , ) expect(screen.getByText('05')).toBeInTheDocument() }) }) describe('active state', () => { it('applies active styles when index equals selectedIndex', () => { const mockOnClick = vi.fn() render( , ) const button = screen.getByRole('button') expect(button).toHaveClass('bg-text-primary') }) it('applies inactive styles when index does not equal selectedIndex', () => { const mockOnClick = vi.fn() render( , ) const button = screen.getByRole('button') expect(button).toHaveClass('bg-components-panel-on-panel-item-bg') }) }) describe('click handling', () => { it('calls onClick when button is clicked', () => { const mockOnClick = vi.fn() render( , ) fireEvent.click(screen.getByRole('button')) expect(mockOnClick).toHaveBeenCalledTimes(1) }) it('stops event propagation when clicked', () => { const mockOnClick = vi.fn() const mockParentClick = vi.fn() render(
, ) fireEvent.click(screen.getByRole('button')) expect(mockOnClick).toHaveBeenCalledTimes(1) expect(mockParentClick).not.toHaveBeenCalled() }) }) describe('progress indicator', () => { it('does not show progress indicator when not next slide', () => { const mockOnClick = vi.fn() const { container } = render( , ) // Check for conic-gradient style which indicates progress indicator const progressIndicator = container.querySelector('[style*="conic-gradient"]') expect(progressIndicator).not.toBeInTheDocument() }) it('shows progress indicator when isNextSlide is true and not active', () => { const mockOnClick = vi.fn() const { container } = render( , ) const progressIndicator = container.querySelector('[style*="conic-gradient"]') expect(progressIndicator).toBeInTheDocument() }) it('does not show progress indicator when isNextSlide but also active', () => { const mockOnClick = vi.fn() const { container } = render( , ) const progressIndicator = container.querySelector('[style*="conic-gradient"]') expect(progressIndicator).not.toBeInTheDocument() }) }) describe('animation behavior', () => { it('starts progress from 0 when isNextSlide becomes true', () => { const mockOnClick = vi.fn() const { container, rerender } = render( , ) // Initially no progress indicator expect(container.querySelector('[style*="conic-gradient"]')).not.toBeInTheDocument() // Rerender with isNextSlide=true rerender( , ) // Now progress indicator should be visible expect(container.querySelector('[style*="conic-gradient"]')).toBeInTheDocument() }) it('resets progress when resetKey changes', () => { const mockOnClick = vi.fn() const { rerender, container } = render( , ) // Progress indicator should be present const progressIndicator = container.querySelector('[style*="conic-gradient"]') expect(progressIndicator).toBeInTheDocument() // Rerender with new resetKey - this should reset the progress animation rerender( , ) const newProgressIndicator = container.querySelector('[style*="conic-gradient"]') // The progress indicator should still be present after reset expect(newProgressIndicator).toBeInTheDocument() }) it('stops animation when isPaused is true', () => { const mockOnClick = vi.fn() const mockRequestAnimationFrame = vi.spyOn(window, 'requestAnimationFrame') render( , ) // The component should still render but animation should be paused // requestAnimationFrame might still be called for polling but progress won't update expect(screen.getByRole('button')).toBeInTheDocument() mockRequestAnimationFrame.mockRestore() }) it('cancels animation frame on unmount', () => { const mockOnClick = vi.fn() const mockCancelAnimationFrame = vi.spyOn(window, 'cancelAnimationFrame') const { unmount } = render( , ) // Trigger animation frame act(() => { vi.advanceTimersToNextTimer() }) unmount() expect(mockCancelAnimationFrame).toHaveBeenCalled() mockCancelAnimationFrame.mockRestore() }) it('cancels animation frame when isNextSlide becomes false', () => { const mockOnClick = vi.fn() const mockCancelAnimationFrame = vi.spyOn(window, 'cancelAnimationFrame') const { rerender } = render( , ) // Trigger animation frame act(() => { vi.advanceTimersToNextTimer() }) // Change isNextSlide to false - this should cancel the animation frame rerender( , ) expect(mockCancelAnimationFrame).toHaveBeenCalled() mockCancelAnimationFrame.mockRestore() }) it('continues polling when document is hidden', () => { const mockOnClick = vi.fn() const mockRequestAnimationFrame = vi.spyOn(window, 'requestAnimationFrame') // Mock document.hidden to be true Object.defineProperty(document, 'hidden', { writable: true, configurable: true, value: true, }) render( , ) // Component should still render expect(screen.getByRole('button')).toBeInTheDocument() // Reset document.hidden Object.defineProperty(document, 'hidden', { writable: true, configurable: true, value: false, }) mockRequestAnimationFrame.mockRestore() }) }) describe('isPaused prop default', () => { it('defaults isPaused to false when not provided', () => { const mockOnClick = vi.fn() const { container } = render( , ) // Progress indicator should be visible (animation running) expect(container.querySelector('[style*="conic-gradient"]')).toBeInTheDocument() }) }) describe('button styling', () => { it('has correct base classes', () => { const mockOnClick = vi.fn() render( , ) const button = screen.getByRole('button') expect(button).toHaveClass('relative') expect(button).toHaveClass('flex') expect(button).toHaveClass('items-center') expect(button).toHaveClass('justify-center') expect(button).toHaveClass('rounded-[7px]') expect(button).toHaveClass('border') expect(button).toHaveClass('transition-colors') }) }) })