Skip to content

test(frontend): cover ToastContainer a11y, public tracking, notification prefs + consolidate duplicate profile pages - #1430

Merged
mftee merged 1 commit into
CodeGirlsInc:mainfrom
Diggardson270:main
Aug 28, 2026
Merged

mftee merged 1 commit into
CodeGirlsInc:mainfrom
Diggardson270:main

Conversation

@Diggardson270

@Diggardson270 Diggardson270 commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Four frontend issues from the test-coverage / dead-route campaign. Three add
missing coverage — and FE-129 fixes a real frontend↔backend contract mismatch
uncovered while writing its tests. FE-130 consolidates a duplicate route.

No production dependency changes, no backend/schema changes.


FE-127 — ToastContainer aria-live announcement coverage (#1362)

Real-time shipment events (useShipmentSocket) surface through
components/ui/ToastContainer.tsx, so toast content must be announced to
assistive technology. There was no test locking that in.

Added components/ui/ToastContainer.test.tsx:

  • A rendered toast's text is exposed inside an aria-live="polite" region
    (asserted with within(liveRegion), so a future refactor that moves the text
    out of the region fails the test).
  • Each toast carries role="alert" for immediate announcement.
  • The live-region wrapper stays mounted even with zero toasts, so the next
    announcement is picked up.
  • Stacking: multiple concurrent toasts all render, newest-first.
  • Dismiss: clicking one toast's Dismiss button removes only that toast.
  • Auto-dismiss after the timeout leaves the live region in the tree.

components/ui/ToastContainer.tsx added to collectCoverageFrom (now 100%).

No production code changed.


FE-128 — Public tracking page: invalid / unknown tracking number (#1363)

app/track/[trackingNumber]/page.tsx is public. There was no coverage for the
invalid-tracking-number path, nor a test pinning what an anonymous viewer is
allowed to see.

Added app/track/[trackingNumber]/page.test.tsx:

  • A 404 from the API renders a clear "Shipment not found" state (not a raw
    error, not a blank page).
  • A malformed tracking number gets the same not-found treatment as an
    unknown-but-well-formed one — no "invalid format" / "bad pattern" wording that
    would let a caller enumerate valid tracking-number shapes.
  • Non-404 failures render a generic "Something went wrong" state.
  • While the lookup is in flight a loading indicator is shown (never a blank
    page).
  • Anonymous data boundary is asserted explicitly: the page resolves tracking
    data with no auth token / no login redirect, and exposes only the intended
    public fields (route, status, cargo summary, weight, price, party names, ETA)
    while not rendering contact emails, internal notes, or raw ids. This test
    now acts as a change-detector for that boundary.

Added to middleware.test.ts: /track/* passes through for unauthenticated
users and is not caught by any protected-route prefix.

No production code changed — behaviour was already correct; it's now locked in.


FE-129 — Notification preferences did not round-trip with the backend (#1364)

While writing the requested test I found the frontend and
backend/src/notification-preferences did not agree:

Before (frontend) Backend (notification-preferences)
GET shape expected NotificationPreference[] returns the NotificationPreferences entity object
Keys snake_case (shipment_in_transit) camelCase (shipmentInTransit)
Extra keys FE sent shipment_created, document_uploaded not accepted by UpdateNotificationPreferencesDto
Key FE never showed — shipmentCompleted

So getPreferences() would have set state to a non-array and thrown on render,
and every updatePreferences() PATCH would have been ignored by the DTO
whitelist. The toggles could not have persisted.

Fixed lib/api/notifications.api.ts:

  • Added NotificationPreferenceKey (the 7 canonical camelCase keys matching the
    entity + DTO exactly), NotificationPreferencesResponse, and
    NotificationPreferencesPatch.
  • getPreferences() now fetches the backend entity and adapts it to the
    display-ready NotificationPreference[] via toPreferenceList(), in a stable
    order (NOTIFICATION_PREFERENCE_KEYS).
  • updatePreferences() is typed to send only DTO-valid keys and returns the
    updated entity.
  • Added defaultPreferenceList() for the load-failure fallback.

Updated app/(dashboard)/settings/notifications/page.tsx:

  • Dropped the hand-written FALLBACK_PREFERENCES (which used the wrong keys) for
    defaultPreferenceList().
  • handleToggle is now typed NotificationPreferenceKey; the optimistic
    update / revert / toast flow is unchanged.

Added tests:

  • lib/api/notifications.api.test.ts — the adapter maps every entity flag onto
    a display row preserving on/off state; the key set is exactly the DTO's key
    set; getPreferences() calls the right endpoint and adapts; updatePreferences()
    PATCHes a camelCase key the backend understands.
  • app/(dashboard)/settings/notifications/page.test.tsx — drives apiClient
    against an in-memory copy of the backend row so GET reflects state and PATCH
    persists it:
    • toggles render reflecting persisted backend state (aria-checked),
    • toggling PATCHes with the DTO key and reflects the new state,
    • a value persisted by one mount is read back by a fresh mount (true
      round-trip),
    • a rejected PATCH reverts the toggle and shows the error toast,
    • a failed load falls back to the all-enabled default list.

lib/api/notifications.api.ts added to collectCoverageFrom (100%).


FE-130 — Two profile pages consolidated to one (#1365)

app/(dashboard)/profile/page.tsx and app/(dashboard)/settings/profile/page.tsx
both edited name + Stellar wallet; settings/profile additionally repeated the
password form that /settings already provides. Only /profile is linked
anywhere in the app (sidebar + mobile nav); grep finds zero references to
/settings/profile. It was an orphaned, confusing duplicate.

Consolidated onto the linked, canonical /profile:

  • app/(dashboard)/profile/page.tsx — ported the stricter Stellar address
    validation from the old settings page: walletAddress stays optional but is
    now validated against /^G[A-Z2-7]{55}$/ when present, with the error message
    rendered under the field.
  • app/(dashboard)/settings/profile/page.tsx — replaced with a server-component
    permanent redirect('/profile') plus a comment explaining the intentional
    consolidation and pointing at FE-130. /settings (password) is unaffected and
    still the place for password changes.

Added tests:

  • app/(dashboard)/settings/profile/page.test.tsx — the route redirects to
    /profile and renders no editable UI of its own.
  • app/(dashboard)/profile/page.test.tsx — /profile is the single editor
    (name + wallet inputs + Save, alongside read-only account details); a
    malformed Stellar address is rejected and not submitted; a well-formed one is
    accepted and submitted; an empty wallet field is treated as "no wallet"
    (optional), not an error.

Closes #1362
Closes #1363
Closes #1364
Closes #1365

…ion prefs + consolidate duplicate profile pages
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

@Diggardson270 is attempting to deploy a commit to the Mftee's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Aug 28, 2026

Copy link
Copy Markdown

@Diggardson270 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@mftee
mftee merged commit 178f7c8 into CodeGirlsInc:main Aug 28, 2026
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment