diff --git a/src/pages/Home.test.tsx b/src/pages/Home.test.tsx index 68e788b..ffbb0b1 100644 --- a/src/pages/Home.test.tsx +++ b/src/pages/Home.test.tsx @@ -5,9 +5,14 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' const productsOrderMock = vi.fn() const servicesOrderMock = vi.fn() -const fromMock = vi.fn<(table: string) => { select: () => { order: typeof productsOrderMock } }>((table: string) => { +const testimonialsOrderMock = vi.fn() +const siteContentSingleMock = vi.fn() +const siteContentEqMock = vi.fn(() => ({ single: siteContentSingleMock })) +const fromMock = vi.fn<(table: string) => { select: () => { order: typeof productsOrderMock } | { eq: typeof siteContentEqMock } }>((table: string) => { if (table === 'products') return { select: () => ({ order: productsOrderMock }) } if (table === 'services') return { select: () => ({ order: servicesOrderMock }) } + if (table === 'testimonials') return { select: () => ({ order: testimonialsOrderMock }) } + if (table === 'site_content') return { select: () => ({ eq: siteContentEqMock }) } throw new Error(`unexpected table: ${table}`) }) @@ -19,11 +24,24 @@ vi.mock('../lib/resolveImageUrl', () => ({ })) const PRODUCT_ROWS = [ - { id: 'edge-control', name: 'Gel Contour Soyeux', category: 'hair-care', price: 12, image: '', description: '', in_stock: true }, + { id: 'edge-control', name: 'Gel Contour Soyeux', category: 'hair-care', price: 12, image: '', description: '', in_stock: true, featured: true, featured_order: 0 }, + { id: 'braid-oil', name: 'Huile Nourrissante', category: 'hair-care', price: 16, image: '', description: '', in_stock: true, featured: false, featured_order: null }, ] const SERVICE_ROWS = [ { id: 'knotless', name: 'Tresses sans nœuds', duration_minutes: 240, price_from: 150, description: '', image: '' }, ] +const TESTIMONIAL_ROWS = [ + { id: 't1', quote: 'Fantastique service !', author: 'Amara T.', avatar_image: '', sort_order: 0 }, +] +const SITE_CONTENT_ROW = { + id: 'default', + hero_headline: 'Vos cheveux, votre couronne', + hero_tagline: 'Coiffures protectrices par Mira', + about_bio: 'Bio de test.', + about_portrait_image: '', + contact_email: 'hello@braidsbymira.com', + contact_hours: 'Mar–Sam, 9h à 18h', +} import Home from './Home' import Book from './Book' @@ -43,24 +61,33 @@ describe('Home page', () => { beforeEach(() => { productsOrderMock.mockReset().mockResolvedValue({ data: PRODUCT_ROWS, error: null }) servicesOrderMock.mockReset().mockResolvedValue({ data: SERVICE_ROWS, error: null }) + testimonialsOrderMock.mockReset().mockResolvedValue({ data: TESTIMONIAL_ROWS, error: null }) + siteContentSingleMock.mockReset().mockResolvedValue({ data: SITE_CONTENT_ROW, error: null }) fromMock.mockClear() }) it('renders the hero headline and all signature styles', async () => { renderHome() - expect(screen.getByText(/vos cheveux, votre couronne/i)).toBeInTheDocument() + expect(await screen.findByText(/vos cheveux, votre couronne/i)).toBeInTheDocument() for (const service of SERVICE_ROWS) { expect(await screen.findByText(service.name)).toBeInTheDocument() } }) - it('renders shop highlights and a link to the rules page', async () => { + it('renders only featured products in shop highlights, and a link to the rules page', async () => { renderHome() - await screen.findByText('Gel Contour Soyeux') + expect(await screen.findByText('Gel Contour Soyeux')).toBeInTheDocument() + expect(screen.queryByText('Huile Nourrissante')).not.toBeInTheDocument() expect(screen.getByRole('link', { name: /voir toute la boutique/i })).toHaveAttribute('href', '/shop') expect(screen.getByRole('link', { name: /règlements du salon/i })).toHaveAttribute('href', '/rules') }) + it('renders testimonials from site content', async () => { + renderHome() + expect(await screen.findByText(/fantastique service/i)).toBeInTheDocument() + expect(screen.getByText('Amara T.')).toBeInTheDocument() + }) + it('navigates to /book with the chosen service preselected when a signature style is clicked', async () => { renderHome() const button = await screen.findByRole('button', { name: new RegExp(SERVICE_ROWS[0].name) }) @@ -68,8 +95,9 @@ describe('Home page', () => { expect(screen.getByLabelText(/choisissez une date/i)).toBeInTheDocument() }) - it('renders the About Mira portrait', () => { + it('renders the About Mira portrait and bio from site content', async () => { renderHome() + expect(await screen.findByText('Bio de test.')).toBeInTheDocument() expect(screen.getByRole('img', { name: /mira/i })).toBeInTheDocument() }) }) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 229d66d..e8e5f7b 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -7,14 +7,10 @@ import ServiceCard from '../components/ServiceCard' import ProductCard from '../components/ProductCard' import { fetchServices, type Service } from '../data/services' import { fetchProducts, type Product } from '../data/products' +import { fetchTestimonials, type Testimonial } from '../data/testimonials' +import { fetchSiteContent, type SiteContent } from '../data/siteContent' import styles from './Home.module.css' -const TESTIMONIALS = [ - { quote: "Mira m'a fait sentir comme une reine sur son fauteuil. Mes tresses ont tenu 8 semaines !", author: 'Amara T.', avatar: '/testimonials/amara.jpg' }, - { quote: "La tresseuse la plus douce et la plus précise que j'aie jamais consultée.", author: 'Keisha R.', avatar: '/testimonials/keisha.jpg' }, - { quote: 'La réservation était si simple, et le résultat parle de lui-même.', author: 'Nadia F.', avatar: '/testimonials/nadia.jpg' }, -] - function Home() { const navigate = useNavigate() const { scrollYProgress } = useScroll() @@ -23,15 +19,24 @@ function Home() { const [services, setServices] = useState([]) const [products, setProducts] = useState([]) + const [testimonials, setTestimonials] = useState([]) + const [content, setContent] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState('') useEffect(() => { async function load() { try { - const [servicesResult, productsResult] = await Promise.all([fetchServices(), fetchProducts()]) + const [servicesResult, productsResult, testimonialsResult, contentResult] = await Promise.all([ + fetchServices(), + fetchProducts(), + fetchTestimonials(), + fetchSiteContent(), + ]) setServices(servicesResult) setProducts(productsResult) + setTestimonials(testimonialsResult) + setContent(contentResult) } catch (err) { setError(err && typeof err === 'object' && 'message' in err ? String((err as { message: unknown }).message) : String(err)) } finally { @@ -41,12 +46,16 @@ function Home() { void load() }, []) + const featuredProducts = products + .filter((p) => p.featured) + .sort((a, b) => (a.featuredOrder ?? 0) - (b.featuredOrder ?? 0)) + return (
-

Vos cheveux, votre couronne

-

Coiffures protectrices par Mira

+

{content?.heroHeadline ?? 'Vos cheveux, votre couronne'}

+

{content?.heroTagline ?? 'Coiffures protectrices par Mira'}

Réserver maintenant Voir les produits @@ -60,18 +69,14 @@ function Home() {

À propos de Mira

-

- Avec plus d'une décennie d'expérience derrière le fauteuil, Mira aborde chaque - installation comme une œuvre d'art — douce pour le cuir chevelu, précise à la - racine, et achevée pour une couronne que vous aurez envie d'afficher. -

+

{content?.aboutBio}

@@ -96,7 +101,7 @@ function Home() {

Sélection boutique

- {products.filter((p) => p.inStock).slice(0, 3).map((product) => ( + {featuredProducts.map((product) => ( navigate('/shop')} /> ))}
@@ -108,9 +113,9 @@ function Home() {

Avis de nos clientes

- {TESTIMONIALS.map((t) => ( -
- {t.author} + {testimonials.map((t) => ( +
+ {t.author}

“{t.quote}”

{t.author}