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()
})
})
})