> ## Documentation Index
> Fetch the complete documentation index at: https://docs-terra.withunify.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Test strategy and running tests

# Testing

> Terra uses Vitest for unit tests and Playwright for E2E tests.

## Test Stack

| Tool                  | Purpose                    |
| --------------------- | -------------------------- |
| Vitest                | Unit and integration tests |
| Playwright            | End-to-end browser tests   |
| React Testing Library | Component testing          |

## Running Tests

```bash theme={null}
# Unit tests
pnpm --dir apps/terra test

# Unit tests in watch mode
pnpm --dir apps/terra test --watch

# E2E tests
pnpm --dir apps/terra test:e2e

# E2E tests with UI
pnpm --dir apps/terra test:e2e:ui

# Load certification gate (status, submission, optional upload)
pnpm --dir apps/terra test:load

# Type check
pnpm --dir apps/terra tsc --noEmit
```

## Test Structure

```
apps/terra/
├── src/
│   ├── lib/
│   │   └── __tests__/
│   │       └── logic-engine.test.ts
│   └── components/
│       └── __tests__/
│           └── form-renderer.test.tsx
├── tests/
│   ├── status-lookup.spec.ts
│   └── form-submission.spec.ts
└── scripts/
    └── load-certification.mjs
```

## Load Certification

`pnpm test:load` runs a pass/fail capacity gate for public critical paths:

1. `GET /status` (public status lookup)
2. `GET /f/<slug>` (public submission form)
3. `POST /api/drive-upload` (optional when upload fixture is configured)

Default SLO gates:

| Scenario          | Max p95 latency | Max error rate | Min throughput |
| ----------------- | --------------- | -------------- | -------------- |
| Status lookup     | 1200ms          | 2%             | 8 rps          |
| Public submission | 1500ms          | 2%             | 6 rps          |
| Drive upload      | 2500ms          | 5%             | 2 rps          |

Common environment variables:

* `TERRA_LOAD_BASE_URL` (required, e.g. `http://127.0.0.1:3310`)
* `TERRA_LOAD_FORM_PATH` (default `/f/e2e-test-form`)
* `TERRA_LOAD_REQUESTS` (default `120`)
* `TERRA_LOAD_CONCURRENCY` (default `12`)
* `TERRA_LOAD_INCLUDE_UPLOAD=true`
* `TERRA_LOAD_UPLOAD_PATH` (include query params; use `{{index}}` in `submissionId` to avoid collisions)
* Per-scenario SLO overrides: `TERRA_LOAD_STATUS_MAX_P95_MS`, `TERRA_LOAD_SUBMISSION_MAX_ERROR_RATE`, `TERRA_LOAD_UPLOAD_MIN_RPS`, etc.

If a required fixture is missing, the gate fails by default. For local exploratory runs only, set `TERRA_LOAD_ALLOW_MISSING_FIXTURES=true`.

## Deterministic Time-Based Tests

If a test depends on "last 7 days" / "last 30 days" logic, freeze time and use explicit timestamps.

```typescript theme={null}
const FIXED_NOW = new Date("2026-02-22T12:00:00.000Z");

beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(FIXED_NOW);
});

afterEach(() => {
  vi.useRealTimers();
});
```

Why this matters:

* Prevents flaky tests caused by local clock drift or timezone differences.
* Makes date-window assertions stable in CI.
* Keeps failures tied to logic regressions, not timing noise.

## Writing Unit Tests

```typescript theme={null}
// src/lib/__tests__/logic-engine.test.ts
import { evaluateLogic } from "../logic-engine";

describe("evaluateLogic", () => {
  it("evaluates simple equality rule", () => {
    const rule = {
      type: "rule",
      fieldId: "status",
      operator: "eq",
      value: "active",
    };

    expect(evaluateLogic(rule, { status: "active" })).toBe(true);
    expect(evaluateLogic(rule, { status: "inactive" })).toBe(false);
  });
});
```

## Writing E2E Tests

```typescript theme={null}
// tests/submission.spec.ts
import { test, expect } from "@playwright/test";

test("submits a form", async ({ page }) => {
  await page.goto("/f/test-form");

  await page.fill('[name="full-name"]', "Jane Doe");
  await page.fill('[name="email"]', "jane@example.com");
  await page.click('button[type="submit"]');

  await expect(page.locator("text=Thank you")).toBeVisible();
});
```

***

<CardGroup cols={2}>
  <Card title="Local Development" icon="laptop" href="/operations/local-development">
    Dev setup
  </Card>

  <Card title="Contributing" icon="git" href="/operations/contributing">
    Contribution guide
  </Card>
</CardGroup>
