77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
|
|
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()
|
||
|
|
})
|
||
|
|
})
|