Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@splitpro-db:5432
# https://next-auth.js.org/configuration/options#secret
NEXTAUTH_SECRET="secret"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_URL_INTERNAL="http://localhost:3000"

# Playwright uses only this disposable database and port.
TEST_DATABASE_URL="postgresql://splitpro:test-password@127.0.0.1:55439/splitpro_harness_test"
E2E_DATABASE_URL="postgresql://splitpro:test-password@127.0.0.1:55440/splitpro_e2e_test"
E2E_BASE_URL="http://127.0.0.1:3176"

# The default /home page is a blog page that may not be suitable for your use case.
# You can change it to /balances or any other URL you want.
Expand Down
13 changes: 12 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ SplitPro is a **Next.js PWA** expense-splitting app (Splitwise alternative). Cor
- `toSafeBigInt(input)` - Parse user input to BigInt (multiplies by 100)
- Access these via `CurrencyHelpersContext` in React components
- `BigMath` utility (`src/utils/numbers.ts`) provides safe arithmetic: `abs`, `sign`, `min`, `max`, `roundDiv`
- **Jest setup**: `BigInt.prototype.toJSON` is defined in `jest.config.ts` for JSON serialization
- **Jest setup**: `jest.shared.ts` configures real SuperJSON/ESM transforms; monetary values remain BigInts

### 2. **Double-Entry Balance Accounting**
Every expense creates **two balance records** (bidirectional):
Expand Down Expand Up @@ -258,3 +258,14 @@ library/API documentation. This means you should automatically use the Context7
tools to resolve library id and get library docs without me having to explicitly ask.

Do not generate documentation or tests if not explicitly requested.

## Testing Harness Rules

- Keep fast checks independent from PostgreSQL integration and Chromium E2E jobs.
- Select unit/component tests with `pnpm test`; select database tests with
`pnpm test:integration`; select browser tests with `pnpm exec playwright test --project=chromium`.
- Integration and E2E commands may only use a disposable local database whose name ends in
`_test`. Never run reset, push, or destructive test setup against shared or production data.
- Read `docs/testing-strategy.md` before changing test selection, CI workflows, or database setup.
- When browser tests fail, inspect `test-results/e2e` and `playwright-report` artifacts before
changing application code.
54 changes: 54 additions & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: E2E Tests

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
e2e:
name: Chromium E2E tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@v5
with:
run_install: false

- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22.16.0
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Prepare disposable database
run: pnpm test:e2e:db:up

- name: Install Chromium
run: pnpm exec playwright install --with-deps chromium

- name: Run Chromium E2E tests
run: pnpm test:e2e --project=chromium

- name: Upload Playwright artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: |
test-results/e2e
playwright-report
if-no-files-found: ignore

- name: Stop disposable database
if: always()
run: pnpm test:e2e:db:down
41 changes: 41 additions & 0 deletions .github/workflows/test-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Integration Tests

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
integration:
name: PostgreSQL integration tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@v5
with:
run_install: false

- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22.16.0
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Prepare disposable database
run: pnpm test:db:up

- name: Run integration tests
run: pnpm test:integration

- name: Stop disposable database
if: always()
run: pnpm test:db:down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

# testing
/coverage
/test-results
/playwright-report/
/playwright/.auth

# database
/prisma/db.sqlite
Expand Down
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,35 @@ Husky runs on commit:

Override with `git commit --no-verify` if needed.

## Testing Harness Workflow

Keep the fast checks (`pnpm prettier --check .`, `pnpm lint`, `pnpm tsgo --noEmit`,
`pnpm test`, and `pnpm build --no-lint`) independent from the disposable database and
browser jobs. CI runs PostgreSQL integration tests with `pnpm test:integration` and Chromium
E2E tests with `pnpm exec playwright test --project=chromium` in separate jobs.

### Test Selection

- `pnpm test` selects `src/**/*.{test,spec}.{ts,tsx}` and excludes `src/tests/integration/`.
- `pnpm test:integration` selects only `src/tests/integration/**/*.{test,spec}.{ts,tsx}`.
- `pnpm exec playwright test --project=chromium` selects `tests/e2e/` through
`playwright.config.ts`; each test receives an isolated authenticated scenario.
- Run one Jest file with `pnpm test src/tests/simplify.test.ts`, one integration file with
`pnpm test:integration src/tests/integration/expense.integration.test.ts`, or one browser
file with `pnpm exec playwright test tests/e2e/group-expense.spec.ts`.

### Database Safety

Integration and E2E databases must be local/disposable and end in `_test`. The integration
harness refuses non-local or non-test URLs; never point these commands at development,
staging, or production data. Prepare integration databases with `pnpm test:db:up` and browser
databases with `pnpm test:e2e:db:up`; the shared launcher deploys migrations and configures
pg_cron in native Nix PostgreSQL or Docker. Set `TEST_DATABASE_URL` and `E2E_DATABASE_URL`
to distinct ports/clusters per suite and worktree. Stop them with the corresponding `:down`
commands. Test database selection never falls back to application `DATABASE_URL`.

See `docs/testing-strategy.md` for the complete command matrix and agent workflow.

<!-- context7 -->

Use the `ctx7` CLI to fetch current documentation whenever the user asks about a library, framework, SDK, API, CLI tool, or cloud service -- even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot. This includes API syntax, configuration, version migration, library-specific debugging, setup instructions, and CLI tool usage. Use even when you think you know the answer -- your training data may not reflect recent changes. Prefer this over web search for library docs.
Expand Down
19 changes: 19 additions & 0 deletions docker/test/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
postgres:
image: ossapps/postgres:18.3-trixie
environment:
POSTGRES_USER: ${TEST_POSTGRES_USER:-splitpro}
POSTGRES_PASSWORD: ${TEST_POSTGRES_PASSWORD:-test-password}
POSTGRES_DB: ${TEST_POSTGRES_DB:-splitpro_harness_test}
command: >-
postgres
-c shared_preload_libraries=pg_cron
-c cron.database_name=${TEST_POSTGRES_DB:-splitpro_harness_test}
-c cron.timezone=UTC
ports:
- '127.0.0.1:${TEST_POSTGRES_PORT:-55439}:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"']
interval: 2s
timeout: 5s
retries: 15
88 changes: 88 additions & 0 deletions docs/testing-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Testing strategy

Fast checks, database integration tests, and browser tests are independent. CI runs each in
its own job. Fast checks require no running database; the other jobs provision disposable
PostgreSQL with the same launcher used locally.

## Commands

| Purpose | Command |
| ---------------------------- | ------------------------------------------------------------------------- |
| Formatting | `pnpm prettier --check .` |
| Lint | `pnpm lint` |
| Types | `pnpm tsgo --noEmit` |
| Unit/component tests | `pnpm test` |
| One unit file | `pnpm test src/tests/simplify.test.ts` |
| Prepare integration database | `pnpm test:db:up` |
| Integration tests | `pnpm test:integration` |
| One integration file | `pnpm test:integration src/tests/integration/expense.integration.test.ts` |
| Stop integration database | `pnpm test:db:down` |
| Prepare browser database | `pnpm test:e2e:db:up` |
| Install Chromium | `pnpm exec playwright install chromium` (CI adds `--with-deps`) |
| Chromium E2E | `pnpm test:e2e --project=chromium` |
| One browser file | `pnpm test:e2e tests/e2e/group-expense.spec.ts` |
| Stop browser database | `pnpm test:e2e:db:down` |
| Production build | `pnpm build` |

Pass arguments directly to pnpm scripts: an extra `--` is forwarded to Playwright and stops
option parsing. `pnpm test:e2e --list` lists tests without starting a server.

## Local databases and worktrees

The launchers create separate PostgreSQL instances with `pg_cron` preloaded and its database
name configured, wait for startup, and deploy Prisma migrations. Docker is used outside Nix;
`nix develop` uses native PostgreSQL and keeps its state under `.direnv/test-postgres-<port>`.
The Nix runtime supplies Chromium's Linux libraries; install the browser once with the command
above. Re-enter `nix develop` after changing the flake.

| Suite | Environment variable | Default port | Default database |
| ----------- | -------------------- | ------------ | ----------------------- |
| Integration | `TEST_DATABASE_URL` | 55439 | `splitpro_harness_test` |
| Browser | `E2E_DATABASE_URL` | 55440 | `splitpro_e2e_test` |

Both default URLs use `splitpro:test-password@127.0.0.1`. The launcher, Jest, and Playwright
load the root `.env` file before reading these variables; exported shell
variables take precedence. Application `DATABASE_URL` is not used to select test databases.
Use distinct test ports per worktree and per suite, and a database name ending in `_test`.
Native PostgreSQL supports IPv4/IPv6 loopback; Docker requires IPv4 loopback. A PostgreSQL
instance can host `pg_cron` in only one configured database, so suites cannot share a port.
`E2E_BASE_URL` defaults to `http://127.0.0.1:3176`; give each concurrent browser run its own
local HTTP port. Playwright starts its own app and refuses to reuse another running server.

Tests only accept local disposable `_test` databases. Integration resets application tables
before each test. Browser fixtures clean only their own users, groups, sessions, and expenses.
An advisory lock rejects competing integration suites against the same database. Docker teardown
removes disposable volumes; native teardown stops the server and preserves state for reuse.

## Reusable helpers and test selection

- `jest.shared.ts` owns aliases, mock clearing, SWC configuration, and ESM-package handling.
Unit tests default to Node. DOM tests declare `/** @jest-environment jsdom */`; the unit
setup loads browser shims and cleanup only in that environment.
- `renderWithProviders` supplies a fresh query client, router, session context, theme, and
currency helpers. Pass `router`, `session`, or `sessionLoading` for individual scenarios.
Translations use the real English resources with interpolation and namespaces. Store state,
browser storage, mounted components, and managed query clients are automatically reset.
- `databaseFactories.ts` builds typed users/groups and expense inputs for integration and E2E.
API lifecycle tests use the real caller and accounting; direct database seeding is for
arranging scenarios, not validating writes.
- Integration setup owns one Prisma client per Jest suite and disconnects it after the suite.
Only auth-session lookup and notification delivery are mocked. Nano ID and SuperJSON are real.
- Playwright fixtures own a client per worker and a new authenticated scenario per test. There
are no shared authentication files or setup dependency. Cookies follow the configured host.
The transport test checks BigInt/Date serialization through the actual Next.js API endpoint.

`pnpm test` selects `src/**/*.{test,spec}.{ts,tsx}` except `src/tests/integration/`.
`pnpm test:integration` selects that integration directory only. Playwright selects `tests/e2e/`.
Failures retain traces, screenshots, videos, and the HTML report even when local retries are
disabled. CI uploads `test-results/e2e` and `playwright-report` on failure and always stops its
database. Do not run integration resets against the browser database or a shared server.

## Agent workflow

1. Read the relevant helpers before adding fixtures or mocks; extend them rather than copying
test setup into individual suites.
2. Run the narrowest affected test first, then formatting, lint, types, and the unit suite.
3. Prepare disposable databases before integration/E2E; preserve failure artifacts for diagnosis.
4. Assert persisted accounting and permissions, including rejected mutations leaving state intact.
5. Report exact failures; do not weaken assertions, test selection, or database guards to pass CI.
26 changes: 26 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@
p.cacert
p.stdenv.cc.cc.lib
p.zlib
p.glib
p.nss
p.nspr
p.dbus
p.atk
p.at-spi2-atk
p.cups
p.libdrm
p.expat
p.libxkbcommon
p.xorg.libX11
p.xorg.libXcomposite
p.xorg.libXdamage
p.xorg.libXext
p.xorg.libXfixes
p.xorg.libXrandr
p.xorg.libxcb
p.mesa
p.libgbm
p.udev
p.pango
p.cairo
p.alsa-lib
p.fontconfig
p.freetype
p.bash
p.coreutils
p.findutils
Expand Down Expand Up @@ -98,6 +123,7 @@
pkgs.git
pkgs.openssl
pkgs.nixfmt-rfc-style
pkgs.dejavu_fonts
];
SPLITPRO_NATIVE_POSTGRES = "1";
shellHook = ''
Expand Down
Loading
Loading