Examples
Jetpack Compose waitlist
A Jetpack Compose app on the Android emulator joins a waitlist through a Hono API on Postgres, which sends a welcome email through Resend.
A Jetpack Compose app on the Android emulator joins a waitlist through a Hono API on Postgres, which sends a welcome email through Resend. See it on GitHub.
What it shows
danwo build-androidbuilding a plain Gradle project, with danwo's network config added for the build only- The app's HTTPS calls to its production host reaching a local API through danwo's proxy, with no change to its code
- Locators on Compose:
testTagforgetByTestId, and a MaterialButtonfound by its role and label - An invariant that reads both sides: exactly one row in Postgres and exactly one welcome email
danwo matrixfaulting the app's own API, Resend and the API's SQL
Stack
Jetpack ComposeAndroid emulatorHonoPostgresEmulates: Resend. Runs on: A native Compose app on an Android emulator, with Postgres in Docker.
Run it
Copy examples/with-compose anywhere, then from its folder:
npm install
npx @danwo/cli testIts scripts:
npm run build:android # danwo build-android android
npm run api # node api/server.ts
npm run typecheck # tsc --noEmit
npm run test:e2e # danwo test
npm run matrix # danwo matrix tests/deterministic.danwo.ts --grep "joins the waitlist"
npm run matrix:plan # danwo matrix tests/deterministic.danwo.ts --grep "joins the waitlist" --planIts README says what else it needs and what the last full matrix found.
Until the danwo version it pins is on npm, install it from a local build instead: from the repository's root, bun run release <version> --pack, then node examples/link.ts with-compose, which installs a copy and prints where it is.
Native Android apps explains what this example leans on.
The test
import { joinedOnce, test } from "./waitlist.ts"
const EMAIL = "[email protected]"
test("shows the waitlist form", async ({ screen, expect }) => {
/* Every test starts the app afresh on the device; no `app.open` is needed. */
await expect(screen.getByLabel("Work email")).toBeVisible()
await expect(screen.getByRole("button", { name: "Join the waitlist" })).toBeVisible()
})
test("asks for a valid email", async ({ screen, expect }) => {
await screen.getByLabel("Work email").fill("ada")
await screen.getByRole("button", { name: "Join the waitlist" }).tap()
/* The API refuses it; the app shows the message it answered. */
await expect(screen.getByTestId("error")).toHaveText("Enter a valid email address.")
})
test("joins the waitlist", async ({ screen, expect }) => {
await screen.getByLabel("Work email").fill(EMAIL)
/* Joining saves the row and calls Resend; readiness waits for both before the next line. */
await screen.getByTestId("join").tap()
await expect(screen.getByTestId("status")).toHaveText("You're on the list")
await expect(screen.getByTestId("waitlist-count")).toHaveText("1")
}).invariant((fixtures) => joinedOnce({ ...fixtures, email: EMAIL }))SaaS with integrations
Relay, a team-notes app: workspaces on Clerk organizations, a Stripe trial and billing, Slack and Google Calendar connected by OAuth, live notes over Pusher, and Inngest jobs that send email through Resend.
Flutter waitlist
A Flutter app on the Android emulator or the iOS Simulator joins a waitlist through a Hono API on Postgres, which sends a welcome email through Resend.