diff --git a/src/pages/Home.module.css b/src/pages/Home.module.css index d5f06a4..4e21ca2 100644 --- a/src/pages/Home.module.css +++ b/src/pages/Home.module.css @@ -3,6 +3,10 @@ padding: var(--space-xl) var(--space-lg); } +.error { + color: #a33; +} + .headline { font-family: var(--font-script); font-size: 3rem; diff --git a/src/pages/Home.test.tsx b/src/pages/Home.test.tsx index 318b1b8..68e788b 100644 --- a/src/pages/Home.test.tsx +++ b/src/pages/Home.test.tsx @@ -1,10 +1,32 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { MemoryRouter, Routes, Route } from 'react-router-dom' -import { describe, it, expect } from 'vitest' +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) => { + if (table === 'products') return { select: () => ({ order: productsOrderMock }) } + if (table === 'services') return { select: () => ({ order: servicesOrderMock }) } + throw new Error(`unexpected table: ${table}`) +}) + +vi.mock('../lib/supabase', () => ({ + supabase: { from: (table: string) => fromMock(table) }, +})) +vi.mock('../lib/resolveImageUrl', () => ({ + resolveImageUrl: (path: string) => path, +})) + +const PRODUCT_ROWS = [ + { id: 'edge-control', name: 'Gel Contour Soyeux', category: 'hair-care', price: 12, image: '', description: '', in_stock: true }, +] +const SERVICE_ROWS = [ + { id: 'knotless', name: 'Tresses sans nœuds', duration_minutes: 240, price_from: 150, description: '', image: '' }, +] + import Home from './Home' import Book from './Book' -import { services } from '../data/services' function renderHome() { return render( @@ -18,23 +40,31 @@ function renderHome() { } describe('Home page', () => { - it('renders the hero headline and all four signature styles', () => { + beforeEach(() => { + productsOrderMock.mockReset().mockResolvedValue({ data: PRODUCT_ROWS, error: null }) + servicesOrderMock.mockReset().mockResolvedValue({ data: SERVICE_ROWS, error: null }) + fromMock.mockClear() + }) + + it('renders the hero headline and all signature styles', async () => { renderHome() expect(screen.getByText(/vos cheveux, votre couronne/i)).toBeInTheDocument() - for (const service of services) { - expect(screen.getByText(service.name)).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', () => { + it('renders shop highlights and a link to the rules page', async () => { renderHome() + await screen.findByText('Gel Contour Soyeux') 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('navigates to /book with the chosen service preselected when a signature style is clicked', async () => { renderHome() - await userEvent.click(screen.getByRole('button', { name: new RegExp(services[0].name) })) + const button = await screen.findByRole('button', { name: new RegExp(SERVICE_ROWS[0].name) }) + await userEvent.click(button) expect(screen.getByLabelText(/choisissez une date/i)).toBeInTheDocument() }) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 568c777..1221b64 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,11 +1,12 @@ +import { useEffect, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { motion, useScroll, useTransform, useReducedMotion } from 'framer-motion' import Hero3DScene from '../components/Hero3DScene' import ScrollReveal from '../components/ScrollReveal' import ServiceCard from '../components/ServiceCard' import ProductCard from '../components/ProductCard' -import { services } from '../data/services' -import { products } from '../data/products' +import { fetchServices, type Service } from '../data/services' +import { fetchProducts, type Product } from '../data/products' import styles from './Home.module.css' const TESTIMONIALS = [ @@ -20,6 +21,23 @@ function Home() { const prefersReducedMotion = useReducedMotion() const portraitY = useTransform(scrollYProgress, [0, 1], prefersReducedMotion ? [0, 0] : [0, -40]) + const [services, setServices] = useState([]) + const [products, setProducts] = useState([]) + const [error, setError] = useState('') + + useEffect(() => { + async function load() { + try { + const [servicesResult, productsResult] = await Promise.all([fetchServices(), fetchProducts()]) + setServices(servicesResult) + setProducts(productsResult) + } catch (err) { + setError(err && typeof err === 'object' && 'message' in err ? String((err as { message: unknown }).message) : String(err)) + } + } + void load() + }, []) + return (
@@ -32,6 +50,8 @@ function Home() {
+ {error &&

{error}

} +