Files
textbee/web/test
isra el 366a47913b feat: redesign the billing page and stop calling free users "Unknown"
/billing/current-subscription answers in two shapes. A subscriber gets the
Subscription document, which always carries a status because the schema
requires one. A user with no subscription gets a synthesised
{ plan, isActive, usage } with no status, no amount and no dates.

The page read that second shape as a subscription with missing fields, so
every free user was told their subscription status was "Unknown" and shown
two "N/A" billing dates. Absence of a status is not an unknown status.

lib/billing.ts interprets the payload instead of guessing at it. A paid plan
arriving without a status is still genuinely unknown and is still reported
that way, so the earlier fix for fabricated "Active" is preserved.

The redesign splits one flat card into plan identity and usage. The old
inner panels were bg-card with shadow-sm sitting on a bg-card parent, so the
nesting was invisible; they are bordered muted panels now. Free accounts
drop the status pill, the always-N/A dates and the portal link they have
nothing to manage with.

CTAs are Buttons rather than hand-rolled Links with background utilities,
and the upgrade target comes from position on the plan ladder, so Scale
subscribers are no longer sold a tier above Scale and bespoke plans are not
pushed onto the self-serve ladder at all. Added a "Compare all plans" link
to textbee.dev/pricing, matching the label the onboarding plan picker
already uses for the same destination.

Also corrected the Pro fixture from 1900 to 999 cents, a price we do not
charge, and gave the portal link noopener noreferrer.

Verified: build clean, 0 lint errors (21 warnings, unchanged), 153 unit
tests (from 121), 78 e2e (from 75). The three free-account guards and the
pricing link guard were each confirmed to fail against the pre-change
component before being trusted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 03:23:00 +03:00
..

Testing

The web app has two test layers. Both run against mocked API data. Tests must never point at a real backend.

Unit / component tests (Vitest + React Testing Library + MSW)

  • Runner: Vitest with jsdom.
  • API mocking: MSW node server (test/msw/server.ts) with handlers in test/msw/handlers.ts. Unhandled requests throw (onUnhandledRequest: 'error'), so a test can never silently reach a live backend.
  • Render helper: test/render.tsx exposes renderWithProviders / render, which wrap components in the app providers (react-query with retries off, a mock SessionProvider). Import render, screen, userEvent from @/test/render.
  • Fixtures: test/fixtures.ts is the single source of truth for mocked API data, shared with the e2e layer.

Commands:

pnpm test          # run once
pnpm test:watch    # watch mode
pnpm coverage      # with coverage report

Place tests next to the code as *.test.tsx or under __tests__/.

End-to-end tests (Playwright)

  • Runner: Playwright, specs in e2e/.
  • The real Next dev server boots on port 3100 with a fixed NEXTAUTH_SECRET and a backend URL (localhost:3999) that nothing listens on.
  • e2e/mock-api.ts intercepts every /api/v1/** request and serves the shared fixtures, so no request can reach a real backend.
  • e2e/session.ts mints a valid NextAuth session cookie so tests run authenticated without the real login flow.

Commands:

pnpm test:e2e      # headless
pnpm test:e2e:ui   # interactive UI

First-time setup downloads the browser: pnpm exec playwright install chromium.

The rule

If a new screen calls a new endpoint, add a handler in test/msw/handlers.ts and a branch in e2e/mock-api.ts, both backed by a fixture in test/fixtures.ts. Never let a test hit the network.