import {describe, expect, it, vi, beforeEach} from 'vitest' import {render, screen, userEvent, waitFor} from '../../test-utils' import {QuantitySelector} from './quantity-selector' describe('QuantitySelector', () => { const defaultProps = { quantity: 1, onQuantityChange: vi.fn(), maxQuantity: 10, } beforeEach(() => { vi.clearAllMocks() }) describe('Rendering', () => { it('renders with initial quantity', () => { render() expect(screen.getByText('3')).toBeInTheDocument() }) it('renders increment and decrement buttons', () => { render() const buttons = screen.getAllByRole('button') expect(buttons).toHaveLength(2) }) it('displays correct quantity value', () => { render() expect(screen.getByText('5')).toBeInTheDocument() }) }) describe('Increment Functionality', () => { it('increments quantity on plus button click', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const incrementButton = buttons[1] // Plus button is second await user.click(incrementButton) await waitFor(() => { expect(onQuantityChange).toHaveBeenCalledWith(2) }) }) it('does not increment beyond maxQuantity', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const incrementButton = buttons[1] await user.click(incrementButton) // Should not call onQuantityChange with value > maxQuantity await waitFor(() => { expect(onQuantityChange).toHaveBeenCalledWith(10) }) }) it('disables increment button at maxQuantity', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const incrementButton = buttons[1] // Clear previous calls onQuantityChange.mockClear() // Try to increment at max - quantity should not change await user.click(incrementButton) // Should not call onQuantityChange with a value greater than max expect(onQuantityChange).not.toHaveBeenCalledWith(11) expect(screen.getByText('10')).toBeInTheDocument() }) }) describe('Decrement Functionality', () => { it('decrements quantity on minus button click', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const decrementButton = buttons[0] // Minus button is first await user.click(decrementButton) await waitFor(() => { expect(onQuantityChange).toHaveBeenCalledWith(2) }) }) it('does not decrement below minQuantity', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const decrementButton = buttons[0] await user.click(decrementButton) // Should not call onQuantityChange with value < minQuantity await waitFor(() => { expect(onQuantityChange).toHaveBeenCalledWith(1) }) }) it('respects custom minQuantity', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const decrementButton = buttons[0] // Click multiple times to try to go below minQuantity await user.click(decrementButton) await user.click(decrementButton) await user.click(decrementButton) await waitFor(() => { // Should not go below 3 expect(onQuantityChange).toHaveBeenLastCalledWith(3) }) }) it('disables decrement button at minQuantity', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const decrementButton = buttons[0] // Clear previous calls onQuantityChange.mockClear() // Try to decrement at min - quantity should not change await user.click(decrementButton) // Should not call onQuantityChange with a value less than min expect(onQuantityChange).not.toHaveBeenCalledWith(0) expect(screen.getByText('1')).toBeInTheDocument() }) }) describe('Disabled State', () => { it('disables both buttons when disabled prop is true', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') await user.click(buttons[0]) // Try decrement await user.click(buttons[1]) // Try increment // onQuantityChange should still be called due to useEffect // but the buttons shouldn't change the internal state expect(screen.getByText('5')).toBeInTheDocument() }) }) describe('Props Updates', () => { it('updates quantity when prop changes', () => { const {rerender} = render( ) expect(screen.getByText('1')).toBeInTheDocument() rerender() expect(screen.getByText('5')).toBeInTheDocument() }) it('calls onQuantityChange when quantity changes', async () => { const onQuantityChange = vi.fn() const {rerender} = render( ) // Initial render calls onQuantityChange expect(onQuantityChange).toHaveBeenCalledWith(1) rerender( ) await waitFor(() => { expect(onQuantityChange).toHaveBeenCalledWith(3) }) }) }) describe('Edge Cases', () => { it('handles maxQuantity of 0', () => { render( ) expect(screen.getByText('0')).toBeInTheDocument() // Both buttons should be disabled at 0 // Verify the quantity stays at 0 expect(screen.getByText('0')).toBeInTheDocument() }) it('handles rapid clicking', async () => { const onQuantityChange = vi.fn() const user = userEvent.setup() render( ) const buttons = screen.getAllByRole('button') const incrementButton = buttons[1] // Rapid clicks await user.click(incrementButton) await user.click(incrementButton) await user.click(incrementButton) // Should handle all clicks properly await waitFor(() => { expect(onQuantityChange).toHaveBeenCalled() }) }) it('maintains bounds when quantity prop exceeds maxQuantity', () => { render( ) // Should display the exceeded value expect(screen.getByText('15')).toBeInTheDocument() // Increment should be disabled when exceeding max // Verify the quantity is above max expect(screen.getByText('15')).toBeInTheDocument() }) it('maintains bounds when quantity prop is below minQuantity', () => { render( ) // Should display the value below minimum expect(screen.getByText('0')).toBeInTheDocument() // Decrement should be disabled when below min // Verify the quantity is below min expect(screen.getByText('0')).toBeInTheDocument() }) }) describe('Visual Feedback', () => { it('applies correct styles to container', () => { render() const container = screen.getByTestId('QuantitySelector') expect(container).toHaveClass( 'inline-flex', 'bg-tertiary', 'rounded-[8px]' ) }) it('shows different icon styles for enabled/disabled states', () => { const {rerender} = render( ) // At minimum, quantity is 1 expect(screen.getByText('1')).toBeInTheDocument() // Change quantity to enable decrement rerender( ) // Should now show quantity 2 expect(screen.getByText('2')).toBeInTheDocument() }) }) })