Current completed phase: Phase 7 — a real automated test suite (scope confirmed with the user up front: "real test suite", flagged as pending since Phase 1).
Phase 6 (mock accounts/auth + real About/Contact content) arrived already
complete in the uploaded project zip, including its own
HANDOFF-PHASE-6.md. This session verified it (clean install, clean
vite build, clean oxlint) rather than rebuilding any of it, then
moved straight into Phase 7.
- Vitest test runner, configured separately from the app's
vite.config.ts(newvitest.config.ts, same plugins/alias) so test-only settings never touch the production build config. src/test/setup.ts— global test setup: registers@testing-library/jest-dommatchers and clearslocalStorageafter every test, since every provider in this app (CartProvider,WishlistProvider,AuthProvider) reads/writes it - without this, state written in one test would leak into the next.src/test/utils.tsx—AllProviders(wraps children inMemoryRouter+AuthProvider+CartProvider+WishlistProvider, matching the nestingApp.tsxmounts) andrenderWithProviders(), so component/page tests don't each hand-roll the same wrapper.- Lib unit tests (pure functions, no rendering):
src/lib/currency.test.ts-formatPHPpeso formattingsrc/lib/cn.test.ts- class merging incl. Tailwind conflict resolutionsrc/lib/auth.test.ts-validateLogin/validateSignupsrc/lib/checkout.test.ts-validateCheckout+placeOrder(shipping-fee threshold, order shape) using fake timers for the 900ms simulated delaysrc/lib/orders.test.ts-getOrdersForUser/saveOrderForUserlocalStorage persistence, corrupt-data fallback, per-user isolationsrc/lib/productFilters.test.ts-filterAndSortProductscategory/ query filtering and every sort mode, incl. a stable-sort check for items missingsalesRank
- Context provider tests (via
renderHook):src/context/CartProvider.test.tsx- add/remove/update, theMAX_QTY(10) cap,lines/subtotal/totalCountderivations, localStorage persistence across remounts, dropping stored items for products no longer in the catalog,useCartthrowing outside its providersrc/context/WishlistProvider.test.tsx- same shape of coverage for toggle/remove/clear,itemsjoin, persistence, catalog-drift filtering,useWishlistthrowing outside its providersrc/context/AuthProvider.test.tsx- signup/login/logout, duplicate- email and wrong-password rejections (case-insensitive email matching), session persistence across remounts, using fake timers for the 700ms simulated delay
- Component/page tests (via Testing Library +
userEvent, real timers):src/components/auth/RequireAuth.test.tsx- unauthenticated visitor is redirected to/loginsrc/pages/Login.test.tsx- blank-form validation error, signup → successful account creation → redirect to the pageRequireAuthsent the visitor from, mismatched confirm-password inline error, wrong- password error against a pre-seeded account, and the login/signup mode toggle
npm test(single run, used in CI-style checks) andnpm run test:watchscripts added topackage.json..oxlintrc.json- added a scoped override turning offreact/only-export-componentsforsrc/test/**only, sincesrc/test/utils.tsxintentionally exports both a wrapper component and a render helper function - that file is never part of the app's fast-refresh tree, so the rule doesn't apply there. No other rules or scopes changed.
- Unhandled-promise-rejection warnings in
AuthProvider.test.tsx. The duplicate-signup, unknown-login, and wrong-password tests were advancing fake timers (vi.runAllTimersAsync()) before attaching theexpect(promise).rejects.toThrow(...)handler - the promise could settle (reject) in that gap, which Vitest correctly flagged as an unhandled rejection even though the test itself passed. Fixed by creating theexpect(...).rejectsassertion immediately after callinglogin/signup(beforeawait vi.runAllTimersAsync()), so the rejection handler is always registered before the promise can settle. Verified clean - 0 unhandled-rejection warnings on the full suite re-run.
- None beyond the test suite itself - no application code was changed this phase (Phase 6 was received already complete and correct).
vitest.config.ts
src/
├── test/
│ ├── setup.ts
│ └── utils.tsx
├── lib/
│ ├── currency.test.ts
│ ├── cn.test.ts
│ ├── auth.test.ts
│ ├── checkout.test.ts
│ ├── orders.test.ts
│ └── productFilters.test.ts
├── context/
│ ├── CartProvider.test.tsx
│ ├── WishlistProvider.test.tsx
│ └── AuthProvider.test.tsx
├── components/
│ └── auth/
│ └── RequireAuth.test.tsx
├── pages/
│ └── Login.test.tsx
HANDOFF-PHASE-7.md
package.json- addedtest/test:watchscripts and newdevDependencies:vitest,jsdom,@testing-library/react,@testing-library/jest-dom,@testing-library/user-event..oxlintrc.json- added thesrc/test/**override described above.
- None.
- New reusable test utility:
renderWithProviders()/AllProvidersinsrc/test/utils.tsx- use this for any future component/page test that needs cart, wishlist, or auth context (which is most of them, sinceApp.tsxmounts all three globally). - No state management changes - Cart/Wishlist/Auth context shapes are
untouched; tests only exercise the existing public API (
useCart(),useWishlist(),useAuth()). - No API or database changes - this project has no real backend
(everything is
localStorage-backed mocks), so there was nothing to test at that layer beyond whatorders.test.tsand the provider tests already cover. - Configuration:
vitest.config.tsis intentionally a separate file fromvite.config.tsrather than merged viamergeConfig- keeps test-only settings (jsdom environment, setup files) from ever affecting the production build config, mirroring howtsconfig.app.jsonandtsconfig.node.jsonare already kept separate in this project. - Testing patterns established for future phases:
- Pure functions (
src/lib/*) get plain Vitest unit tests, no rendering. - Context providers get
renderHook()tests exercising the public hook API directly, not through a consuming component. - Pages/components that need real user interaction get
@testing-library/user-eventwithrenderWithProviders(). - Anything using
setTimeout-based mock delays (AuthProvider,checkout.ts) usesvi.useFakeTimers()+await vi.runAllTimersAsync()- always attach anyexpect(promise).rejects...assertion before advancing timers (see Bugs Fixed above).
- Pure functions (
Automated (all passing as of this handoff - npm test, 11 test files,
77 tests, 0 unhandled errors/warnings):
-
formatPHP- whole-peso formatting, rounding, zero, thousands separators -
cn- class joining, falsy-value dropping, Tailwind conflict resolution, conditional object syntax -
validateLogin/validateSignup- every required-field and format error path, including the exact minimum password length -
validateCheckout- every required field, notes optional -
placeOrder- standard vs. free shipping fee, order number format, order line snapshot, shipping details carried through -
getOrdersForUser/saveOrderForUser- empty state, corrupt-JSON fallback, non-array fallback, save+retrieve, most-recent-first ordering, case-insensitive email keys, per-user isolation -
filterAndSortProducts- category filter, "all" category, name and category query matching, whitespace/empty query handling, combined filters, every sort mode (price-asc/price-desc/newest/best-selling/featured), stable-sort behavior for items withoutsalesRank, non-mutation of the input array -
CartProvider- empty start, add (new + existing item),MAX_QTYcap on both add and update, remove, quantity-to-zero removal, clear,lines/subtotal/totalCountderivations, localStorage persistence across remounts, catalog-drift filtering,useCart-outside-provider error -
WishlistProvider- same shape of coverage as CartProvider above -
AuthProvider- signed-out start, signup success + session write, duplicate-email rejection (case-insensitive), login success after signup, unknown-email rejection, wrong-password rejection, logout clearing user + session key, session restore across remounts -
RequireAuth- unauthenticated visitor redirected to/login -
Loginpage - blank-form validation error, mode toggle (login ↔ signup) with error state reset, full signup flow ending in redirect, mismatched-confirm-password inline error, wrong-password error against a pre-seeded account -
npm run buildsucceeds (tsc -b && vite build) -
npx oxlint srcreports 0 errors/0 warnings
Still needs a human pass:
- No coverage tool is wired up yet (
@vitest/coverage-v8not installed) - line/branch coverage numbers aren't available, only "which behaviors are tested" as listed above - No tests yet for:
Shop/ProductDetail/Cart/Checkout/OrderConfirmation/Wishlist/Account/About/Contactpages,Navbar, or anycomponents/ui/*primitives - this phase prioritized the highest-risk logic (auth, cart/wishlist state, checkout math, product filtering) over exhaustive page coverage; see Remaining Tasks - No CI workflow wired up to run
npm testautomatically on push/PR (no.github/workflows/in this project yet) - Real browser check on an actual mobile device (carried over from Phase 6, still outstanding - unrelated to this phase's test work)
- Nothing new introduced this phase. All Known Issues from
HANDOFF-PHASE-6.md(mock auth is not production-secure, no password reset, no account editing, per-browser order history, the logout-navigation gotcha, hardcodedMAX_QTY) are unchanged and still apply - see that file for the full detail if needed. - The Login page tests use real timers (not fake ones) and wait out the
actual 700ms
AuthProviderdelay viawaitFor/findBy*- this makes those four tests the slowest in the suite (~400-1200ms each vs. single- digit ms for everything else). Acceptable for a suite this size (whole run is ~3.5s of actual test time); worth switching to fake timers withuserEvent.setup({ advanceTimers: vi.advanceTimersByTime })if the suite grows large enough for this to matter.
- Nothing outstanding for Phase 7 as scoped (a real test suite now
exists and is passing). Coverage is deliberately concentrated on
state/logic rather than every page - broadening to page-level tests for
Shop/Checkout/Account/etc., adding
@vitest/coverage-v8for numeric coverage, and/or wiring a CI workflow are natural follow-ups but were not part of this phase's confirmed scope.
Phase 8 — not yet defined/confirmed with the user. Worth raising:
broader page-level test coverage (Shop, Checkout, Account, Cart flows),
wiring npm test into a CI workflow, adding coverage reporting, or
something new the user has in mind. Do not begin Phase 8 work until
scope is confirmed.
- Stack: unchanged from Phase 1-6 - Vite + React 19 + TypeScript +
Tailwind v4 (CSS-first config via
@themeinsrc/index.css) + React Router v7 + Framer Motion. Testing stack (new): Vitest 4 + jsdom +@testing-library/react+@testing-library/jest-dom+@testing-library/user-event. - Running tests:
npm test(single run) ornpm run test:watch(watch mode). Config isvitest.config.tsat the project root; global setup issrc/test/setup.ts. - Writing new tests: use
renderWithProviders()from@/test/utilsfor anything needing Cart/Wishlist/Auth context or routing. Follow the established patterns (see Architecture Notes above) - plain unit tests forsrc/lib/*,renderHook()for contexts,userEvent+ real timers for interactive pages. - The
deleted node_modules/distfolders were stripped before zipping this deliverable (standard practice for this project's handoffs) - runnpm installbeforenpm test/npm run build/npm run devin the next session. - Completed: full Phase 1 foundation + Phase 2 homepage + Phase 3 shop + Phase 4 product detail/cart + Phase 5 wishlist and cart/checkout/order-confirmation + Phase 6 accounts and real About/Contact content + Phase 7 test suite (77 tests, 11 files), as listed above.
- Pending: Phase 8 onward - not yet scoped, confirm with the user before starting anything.
- No known bugs as of this handoff - production build, lint, and the
full test suite are all verified clean. The one issue found this
session (unhandled-rejection warnings in three
AuthProvidertests) was a test-code timing issue, not an application bug, and was fixed by reordering when the rejection assertion is attached relative to advancing fake timers.