Skip to content

Testing

This page covers the AppView’s automated suite, run with Vitest. For manually testing the native shell on a given platform, including emulator, Simulator and real-device workflows, see Testing native builds instead. For the client’s own tests, see Client development.

Unit tests live in a *.test.ts file next to the code they cover, in every package and in the AppView app itself:

  • Directorypackages
    • lexicons/src/conformance.test.ts every lexicon document, checked structurally
    • db/src/schema/parity.test.ts the libSQL and Postgres schemas agree
    • space, space-sync, identity, community, projections, notifications, blobs, embeds, voice each has its own src/**/*.test.ts
  • Directoryapps
    • appview/src routes, views, the WebSocket topics, and Jetstream handling
    • appview/test/integration the one suite that needs a real PDS
Terminal window
pnpm test # unit tests, every package
pnpm test:watch # the same, in watch mode
pnpm test:integration # against a real spaces-alpha PDS, see below
pnpm typecheck # sources and tests

Both test and test:integration are Vitest projects defined in the root vitest.config.ts: unit includes every {packages,apps}/*/src/**/*.test.ts, integration includes {packages,apps}/*/test/integration/**/*.test.ts. Running pnpm test never touches the integration project, so a database or a PDS is never required to get a green run.

packages/lexicons/src/conformance.test.ts reads every lexicon document in the package and checks it structurally, without a server or a network call. Among other things it asserts that:

  • Every method (query, procedure, or subscription) declares an errors array, with one exception, social.colibri.beta.server.describeServer, listed in the test by name.
  • Every error name is PascalCase and has a description.
  • Every ref resolves to a definition that exists, and a same-document ref uses the short #name form rather than repeating the full NSID.
  • No lexicon description uses an em dash or a semicolon, the same house style this documentation follows.

There’s no separate error-code generator or route-parity check. A lexicon’s errors array and its schema are checked directly against the JSON rather than against generated code. See AppView development for where a new endpoint’s lexicon lives.

Anything that needs a database uses openTestDatabase() from @colibri-social/appview-db, not openDatabase({ url: ":memory:" }). It hands back a migrated database on a temporary file, plus a destroy() that closes it and removes the directory.

@libsql/client closes and reopens its local connection around a transaction, and an in-memory database doesn’t survive that: the reopened connection is a different, empty database, so every db.transaction() throws. A file-backed database keeps the atomicity the sync store depends on, where a repository’s records and its sync cursor move together or not at all.

pnpm test:integration runs apps/appview/test/integration/spaces.test.ts against a real spaces-alpha PDS and its own PLC directory in Docker:

Terminal window
docker compose -f docker-compose.integration.yml up -d --wait --build
pnpm test:integration
docker compose -f docker-compose.integration.yml down -v

The suite provisions a community, joins a member, writes a message straight to that member’s own repo, and checks the AppView syncs it, recovers from a drifted sync cursor, and removes a banned member. It’s the only place that exercises the real credential and sync path end to end rather than through injected fakes.

It’s not part of pull-request CI. It runs nightly on a schedule and on manual dispatch, so drift against the spaces alpha arrives as its own failure rather than as noise on an unrelated pull request.

Three jobs run on every pull request:

Job What it does
Lint pnpm biome ci .
Test pnpm build, pnpm typecheck, then pnpm test
Lexicons re-runs pnpm lexicons:codegen and fails on a dirty tree, then checks the vendored com.atproto.space and com.atproto.simplespace lexicons are byte-identical to the pinned upstream commit

The vendored lexicons are pinned, so an upstream change arrives as a re-vendor rather than as a codegen result that shifts underneath the AppView. packages/lexicons/lexicons/com/atproto/PINNED_COMMIT holds the commit, and packages/lexicons/README.md covers how to move it.