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:
@@ -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', () => {
|
||||
|
||||
13
src/App.tsx
13
src/App.tsx
@@ -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 />} />
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
background: var(--color-cream-alt);
|
||||
border-radius: 4px;
|
||||
padding: var(--space-md);
|
||||
max-width: 260px;
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
border-radius: 4px;
|
||||
padding: var(--space-md);
|
||||
min-width: 160px;
|
||||
max-width: 260px;
|
||||
text-align: center;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
Reference in New Issue
Block a user