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.
E2E is the slowest suite — the usual loop is unit + typecheck + lint while iterating, one ddev e2e before a PR.