3 Testing
Hardik edited this page 2026-06-19 14:06:07 +05:30
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Testing

The index for the test suite. Deep references (migrated from the retired Docs/):

Three layers, all run on PRs. Commands run from App/.

Layer Tool Env Command
Unit Vitest jsdom pnpm test
Integration Vitest Node + real DB pnpm test:integration
E2E Playwright Chromium + dev server pnpm test:e2e
pnpm test                # unit (fast, no DB)
pnpm test:watch          # unit watch mode
pnpm test:integration    # integration (needs seeded DB)
pnpm test:all            # unit + integration (pre-merge)
pnpm test:e2e            # E2E (needs running dev server)
pnpm test:e2e:ui         # Playwright interactive UI

# single files
pnpm test -- tests/unit/po-line-items-editor.test.tsx
pnpm test:integration -- tests/integration/create-po.test.ts

Tests live in tests/unit/, tests/integration/, tests/e2e/.

Unit tests (Vitest, jsdom)

Cover pure logic and components:

Subject Cases
lib/permissions.ts All 7 roles × key permissions; requirePermission throws
lib/po-state-machine.ts canPerformAction, getTransition, requiresNote, getAvailableActions
lib/po-import-parser.ts cellStr/cellNum/parseSheet/parseWorkbook (real + synthetic)
lib/validations/po.ts lineItemSchema, createPoSchema, TC defaults
components/po/po-line-items-editor.tsx edit/read-only modes, totals, add/remove
components/po/po-status-badge.tsx all status labels
lib/utils.ts formatCurrency, formatDate, generatePoNumber, status maps

Integration tests (Vitest + real DB)

Exercise Server Actions against a real Postgres test DB. They run serially in a single fork (poolOptions.forks.singleFork = true) to avoid DB conflicts; each suite isolates with a PREFIX constant and cleans up via afterEach(() => deletePosByTitle(PREFIX)).

  • Auth is mocked: vi.mock("@/auth") + makeSession(userId, role) from tests/integration/helpers.ts (also makePoForm(), fd()).
  • Side-effects mocked: @/lib/notifier (no email) and next/cache (revalidatePath).
File Feature
create-po.test.ts Draft, submit, line items, totals, notifications
approval-actions.test.ts Approve / reject / request edits / vendor ID / resubmit
payment-actions.test.ts Payment queue, mark paid
discard-po.test.ts Owner/MANAGER/SUPERUSER discard; status guard; cascade
vendor-approval.test.ts Vendor gate before approval; provide-vendor rules
manager-po-creation.test.ts MANAGER create/submit/discard; ACCOUNTS denied
products-search.test.ts Search API: auth, min-length, fields, max 10, Decimal serialised
import-api.test.ts Excel import: auth (403/401), bad file (400), correct parse

Prerequisites: a running Postgres with DATABASE_URL, schema applied (prisma migrate deploy / db push), data seeded (tsx prisma/seed.ts).

E2E tests (Playwright)

Browser-level checks against a live dev server + Postgres. The full E2E framework reference is the E2E Test Framework page; the feature-coverage matrix is the E2E Test Plan.

Config (playwright.config.ts)

testDir: "./tests/e2e",
fullyParallel: true,
retries: process.env.CI ? 2 : 1,
workers: process.env.CI ? 1 : 2,   // >2 floods NextAuth bcrypt on login
reporter: "html",
use: { baseURL: "http://localhost:3000", trace: "on-first-retry" },
webServer: { command: "pnpm dev", url: "http://localhost:3000",
             reuseExistingServer: !process.env.CI },

workers: 2 locally is deliberate — every login does a bcrypt hash + DB round-trip, and higher parallelism overwhelms the dev server, timing out the login redirect.

Shared helpers (tests/e2e/helpers/login.ts)

  • USERS — the seed credentials (see Getting Started).
  • login(page, creds) — fills /login, waits up to 20 s for the redirect (bcrypt + DB can exceed Playwright's 5 s default).
  • createDraftPo(page, title) / submitPo(page, title) — minimal PO setup.

Selector conventions (gotchas)

Situation Symptom Fix
getByLabel(/title/i) on PO form times out — no htmlFor/id use locator('input[name="title"]')
getByText("Technical") on profile strict-mode violation (also in header) scope to page.locator("dd span").filter(...)
Many workers logging in login times out keep workers ≤ 2; consider storageState
router.push soft nav URL still old after networkidle use page.waitForURL(pattern) concurrently
Re-clicking an expanded row row collapses, selectors vanish expand before soft navigation; React state persists

Specs log each assertion with a prefix and skip gracefully when seed preconditions are absent (rather than failing hard).

Coverage highlights

Rebrand, dashboard status badges, submit button, notification bell, export gate (incl. HTTP 403 on DRAFT, 200 on approved), payment history, partial receipt, vendor auto-verify, admin bordered buttons, profile + signature, inventory Cheapest/★Closest tags, cart icon, item/vendor detail pages, mobile (Desktop-Required overlay, manager cards, accounts payment, bottom nav), edit-highlight diff. See the E2E Test Plan for the full matrix and known flaky/needs-fix items.

Out of scope / known gaps

File upload to R2 (live creds; manual in staging), email body content (notifier mocked), PDF/XLSX content (endpoint status checked, content asserted manually), GstService lookup (tested independently), load testing, cross-browser, automated a11y. CI runs unit + integration + E2E on every PR (workers: 1, retries: 2, forbidOnly: true in CI mode).