Developers

Testing

The test suites — PHPUnit functional tests, Vitest unit tests, Playwright end-to-end.

Tests live with the code: core keeps PHPUnit suites in tests/ (functional, factories, stubs); client keeps Vitest specs in tests/unit/ and the Playwright suite in tests/e2e/. All commands below run inside the DDEV environments.

Core — PHPUnit

ddev test                          # whole suite
ddev test tests/Functional/Channel # one directory or file
ddev test --filter=testPinMessage  # one method

ddev test prepares the test database on first run (schema, JWT keys) — no manual setup. The functional suite drives the real HTTP kernel against MariaDB; each test runs inside a transaction that’s rolled back afterwards (DAMA), so tests are isolated and fast. Fixtures are built with Foundry factories (tests/Factory/). External services are stubbed in the test environment: no real Mercure publishes, in-memory search and presence.

Static analysis and code style gate every change:

ddev composer phpstan   # level 6
ddev composer checkcs   # or fixcs to auto-fix

Client — Vitest (unit)

ddev npm test           # or run test:watch / run test:coverage

Unit tests run fully in-process — no browser, no backend. Pure logic (utils, reducers, cache patchers) is the sweet spot; components with heavy server coupling are covered by E2E instead.

Client — Playwright (end-to-end)

ddev e2e                              # headless; resets core's test DB first
ddev npm run test:e2e:headed          # watch the browser
ddev npm run test:e2e:ui              # Playwright UI mode

The E2E suite needs both stacks running (core + client) and the E2E_TEST_TOKEN printed by core’s ddev init (written to the client’s .env.local by ddev setup). Tests run in parallel workers, each with an isolated data slice; a database reset endpoint (guarded by the token) restores a clean state per run.

Tip

E2E is the slowest suite — the usual loop is unit + typecheck + lint while iterating, one ddev e2e before a PR.