fix: address final review findings — code-split Home's 3D bundle, bound card widths, fix Book step transition, cleanup

- Lazy-load Home in App.tsx so three/@react-three/fiber (~238 kB gzip) only ships to the
  Home route, not to Shop/Book/Rules.
- Add max-width: 260px to ProductCard/ServiceCard so real images won't collapse Home/Book
  grid layouts.
- Restore AnimatePresence mode="wait" in Book.tsx to eliminate the exit/enter double-stack
  during step transitions, updating the three Book.test.tsx assertions that raced the
  transition to findBy* queries.
- Hero3DScene: drop the meaningless "static" className string in favor of a dedicated
  data-testid, add aria-hidden to the reduced-motion fallback branch, and remove the
  unused @react-three/drei dependency (adding @types/three directly since it was only
  present transitively via drei).
This commit is contained in:
belviskhoremk
2026-08-03 23:40:32 +00:00
parent 585c77ba42
commit c4180a90bf
10 changed files with 37 additions and 360 deletions

View File

@@ -3,9 +3,9 @@ import { describe, it, expect } from 'vitest'
import App from './App'
describe('App routing', () => {
it('renders the Home page at /', () => {
it('renders the Home page at /', async () => {
render(<App initialEntries={['/']} />)
expect(screen.getByText(/your hair, your crown/i)).toBeInTheDocument()
expect(await screen.findByText(/your hair, your crown/i)).toBeInTheDocument()
})
it('renders the Shop page at /shop', () => {

View File

@@ -1,10 +1,12 @@
import { lazy, Suspense } from 'react'
import { BrowserRouter, MemoryRouter, Routes, Route } from 'react-router-dom'
import Layout from './components/Layout'
import Home from './pages/Home'
import Shop from './pages/Shop'
import Book from './pages/Book'
import Rules from './pages/Rules'
const Home = lazy(() => import('./pages/Home'))
interface AppProps {
initialEntries?: string[]
}
@@ -13,7 +15,14 @@ function AppRoutes() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route
path="/"
element={
<Suspense fallback={null}>
<Home />
</Suspense>
}
/>
<Route path="/shop" element={<Shop />} />
<Route path="/book" element={<Book />} />
<Route path="/rules" element={<Rules />} />

View File

@@ -22,8 +22,7 @@ describe('Hero3DScene', () => {
})
const { default: Hero3DScene } = await import('./Hero3DScene')
render(<Hero3DScene />)
const wrapper = screen.getByTestId('hero-3d-scene')
expect(wrapper.className).toMatch(/static/)
expect(screen.getByTestId('hero-3d-fallback')).toBeInTheDocument()
expect(screen.queryByTestId('r3f-canvas')).not.toBeInTheDocument()
})

View File

@@ -71,7 +71,7 @@ function Hero3DScene() {
if (prefersReducedMotion) {
return (
<div className={`${styles.wrapper} static`} data-testid="hero-3d-scene">
<div className={styles.wrapper} data-testid="hero-3d-fallback" aria-hidden="true">
<div className={styles.fallbackMotif} />
</div>
)

View File

@@ -2,6 +2,7 @@
background: var(--color-cream-alt);
border-radius: 4px;
padding: var(--space-md);
max-width: 260px;
transition: transform 0.25s ease;
}

View File

@@ -3,6 +3,7 @@
border-radius: 4px;
padding: var(--space-md);
min-width: 160px;
max-width: 260px;
text-align: center;
border: none;
cursor: pointer;

View File

@@ -19,7 +19,7 @@ describe('Book page', () => {
renderBook([{ pathname: '/book' }])
expect(screen.getByText(/knotless braids/i)).toBeInTheDocument()
await userEvent.click(screen.getByRole('button', { name: /knotless braids/i }))
expect(screen.getByLabelText(/choose a date/i)).toBeInTheDocument()
expect(await screen.findByLabelText(/choose a date/i)).toBeInTheDocument()
})
it('preselects the service from route state and skips to the date step', () => {
@@ -33,12 +33,12 @@ describe('Book page', () => {
const slotButton = (await screen.findAllByRole('button', { name: /am|pm/i }))[0]
await userEvent.click(slotButton)
await userEvent.type(screen.getByLabelText(/^name$/i), 'Jane Doe')
await userEvent.type(await screen.findByLabelText(/^name$/i), 'Jane Doe')
await userEvent.type(screen.getByLabelText(/^email$/i), 'jane@example.com')
await userEvent.type(screen.getByLabelText(/^phone$/i), '5551234567')
await userEvent.click(screen.getByRole('button', { name: /confirm/i }))
expect(screen.getByText(/mira will see you then/i)).toBeInTheDocument()
expect(await screen.findByText(/mira will see you then/i)).toBeInTheDocument()
})
it('shows a friendly empty state with the next available date when a chosen date has none', async () => {

View File

@@ -29,7 +29,7 @@ function Book() {
<h1>Book an Appointment</h1>
<StepProgress currentStep={booking.state.step} />
<AnimatePresence>
<AnimatePresence mode="wait">
<motion.div
key={booking.state.step}
initial={{ opacity: 0, x: 12 }}