test: add component tests (Button, ProductCard) and test suite README
20 component tests covering Button (variants, disabled state, event handlers) and ProductCard (rendering, price formatting, sale badge, image fallback). README documents the full suite: 151 tests across 10 files, how to run, mock patterns, and what's missing by priority (checkout flow, admin routes, more components).
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { ProductCard } from '@/components/storefront/ProductCard'
|
||||
|
||||
vi.mock('next/link', () => ({
|
||||
default: ({ href, children, className }: any) => (
|
||||
<a href={href} className={className}>{children}</a>
|
||||
),
|
||||
}))
|
||||
|
||||
const baseProduct = {
|
||||
id: 'prod-1',
|
||||
title: 'Maglietta Blu',
|
||||
slug: 'maglietta-blu',
|
||||
basePrice: 2999,
|
||||
currency: 'EUR',
|
||||
images: [{ url: '/uploads/prod-1/photo.jpg', altText: 'Maglietta blu' }],
|
||||
}
|
||||
|
||||
describe('ProductCard', () => {
|
||||
it('renders the product title', () => {
|
||||
render(<ProductCard product={baseProduct} />)
|
||||
expect(screen.getByText('Maglietta Blu')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('formats price correctly (cents → euros with 2 decimals)', () => {
|
||||
render(<ProductCard product={baseProduct} />)
|
||||
expect(screen.getByText(/29\.99/)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows the currency', () => {
|
||||
render(<ProductCard product={baseProduct} />)
|
||||
expect(screen.getByText(/EUR/)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('links to the correct product URL', () => {
|
||||
const { container } = render(<ProductCard product={baseProduct} />)
|
||||
const link = container.querySelector('a')
|
||||
expect(link?.getAttribute('href')).toBe('/products/maglietta-blu')
|
||||
})
|
||||
|
||||
it('renders the product image when images are present', () => {
|
||||
render(<ProductCard product={baseProduct} />)
|
||||
const img = screen.getByRole('img')
|
||||
expect(img.getAttribute('src')).toBe('/uploads/prod-1/photo.jpg')
|
||||
})
|
||||
|
||||
it('uses altText when provided', () => {
|
||||
render(<ProductCard product={baseProduct} />)
|
||||
expect(screen.getByRole('img').getAttribute('alt')).toBe('Maglietta blu')
|
||||
})
|
||||
|
||||
it('falls back to product title as alt text when altText is null', () => {
|
||||
const product = {
|
||||
...baseProduct,
|
||||
images: [{ url: '/img.jpg', altText: null }],
|
||||
}
|
||||
render(<ProductCard product={product} />)
|
||||
expect(screen.getByRole('img').getAttribute('alt')).toBe('Maglietta Blu')
|
||||
})
|
||||
|
||||
it('shows "No image" placeholder when images array is empty', () => {
|
||||
render(<ProductCard product={{ ...baseProduct, images: [] }} />)
|
||||
expect(screen.getByText('No image')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('does not render img tag when no images', () => {
|
||||
render(<ProductCard product={{ ...baseProduct, images: [] }} />)
|
||||
expect(screen.queryByRole('img')).toBeNull()
|
||||
})
|
||||
|
||||
it('formats price = 0 correctly', () => {
|
||||
render(<ProductCard product={{ ...baseProduct, basePrice: 0 }} />)
|
||||
expect(screen.getByText(/0\.00/)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { Button } from '@/components/ui/Button'
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders children text', () => {
|
||||
render(<Button>Clicca qui</Button>)
|
||||
expect(screen.getByText('Clicca qui')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('is disabled when loading=true', () => {
|
||||
render(<Button loading>Salva</Button>)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('shows spinner SVG when loading=true', () => {
|
||||
const { container } = render(<Button loading>Salva</Button>)
|
||||
expect(container.querySelector('svg')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('does not show spinner when not loading', () => {
|
||||
const { container } = render(<Button>Salva</Button>)
|
||||
expect(container.querySelector('svg')).toBeNull()
|
||||
})
|
||||
|
||||
it('is disabled when disabled prop is set', () => {
|
||||
render(<Button disabled>Salva</Button>)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('calls onClick when clicked', async () => {
|
||||
const onClick = vi.fn()
|
||||
render(<Button onClick={onClick}>Clic</Button>)
|
||||
await userEvent.click(screen.getByRole('button'))
|
||||
expect(onClick).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('does not call onClick when disabled', async () => {
|
||||
const onClick = vi.fn()
|
||||
render(<Button disabled onClick={onClick}>Clic</Button>)
|
||||
await userEvent.click(screen.getByRole('button'))
|
||||
expect(onClick).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('applies primary variant class by default', () => {
|
||||
render(<Button>Primary</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('bg-blue-600')
|
||||
})
|
||||
|
||||
it('applies danger variant class', () => {
|
||||
render(<Button variant="danger">Elimina</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('bg-red-600')
|
||||
})
|
||||
|
||||
it('applies secondary variant class', () => {
|
||||
render(<Button variant="secondary">Annulla</Button>)
|
||||
expect(screen.getByRole('button').className).toContain('bg-gray-200')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user