Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Optional comma-separated hostnames for proxied Next.js dev server origins.
NEXT_ALLOWED_DEV_ORIGINS=
# Enables local Playwright-only mock sessions. Never set in production.
E2E_AUTH_ENABLED=false

# Server-only configuration
DATABASE_URL=
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

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

# next.js
/.next/
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,43 @@ Keep machine-specific proxy hostnames and URLs out of committed source.
- `pnpm build`: create a production build.
- `pnpm start`: run the production build.
- `pnpm lint`: run ESLint.
- `pnpm test:e2e`: run Playwright responsive smoke tests.
- `pnpm test:e2e:install`: install the Playwright Chromium browser.
- `pnpm db:generate`: generate Drizzle SQL migrations from the schema.
- `pnpm db:migrate`: apply Drizzle migrations to `DATABASE_URL`.
- `pnpm db:reset:local`: drop and recreate the local `public` schema, then run migrations.
- `pnpm db:studio`: open Drizzle Studio for local database inspection.

## End-to-End QA

The Playwright suite checks public and gated dashboard routes across mobile,
tablet, and desktop viewports. It uses the local database state that is already
present. Detail-page checks skip with a clear message when the local database
does not contain a matching quarter or report.

On a fresh Ubuntu server, install browser system dependencies once:

```bash
pnpm exec playwright install-deps chromium
```

Then install the Chromium browser bundle:

```bash
pnpm test:e2e:install
```

Run the suite:

```bash
pnpm test:e2e
```

The tests start the Next.js dev server with `E2E_AUTH_ENABLED=true` and use a
local-only mock session endpoint for member, cleric, and admin route coverage.
That endpoint returns 404 unless `E2E_AUTH_ENABLED=true` and the app is not
running in production.

## Stack

- Next.js App Router.
Expand Down
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const eslintConfig = defineConfig([
".next/**",
"out/**",
"build/**",
"playwright-report/**",
"test-results/**",
"next-env.d.ts",
]),
]);
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"build": "next build",
"start": "next start",
"lint": "eslint .",
"test:e2e": "playwright test",
"test:e2e:install": "playwright install chromium",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:reset:local": "node scripts/reset-local-db.mjs",
Expand Down Expand Up @@ -43,6 +45,7 @@
"wagmi": "^3.6.16"
},
"devDependencies": {
"@playwright/test": "^1.61.1",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/pg": "^8.18.0",
Expand Down
56 changes: 56 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { defineConfig, devices } from "@playwright/test";

const port = Number(process.env.PLAYWRIGHT_PORT ?? 3131);
const host = "127.0.0.1";
const baseURL = `http://${host}:${port}`;

export default defineConfig({
expect: {
timeout: 10_000,
},
fullyParallel: false,
outputDir: "test-results/e2e",
reporter: [["list"], ["html", { open: "never" }]],
testDir: "./tests/e2e",
timeout: 45_000,
use: {
baseURL,
trace: "retain-on-failure",
},
webServer: {
command: `E2E_AUTH_ENABLED=true COREPACK_HOME=/tmp/corepack corepack pnpm exec next dev -H ${host} -p ${port}`,
reuseExistingServer: false,
timeout: 120_000,
url: baseURL,
},
projects: [
{
name: "mobile",
use: {
...devices["Pixel 5"],
viewport: { height: 844, width: 390 },
},
},
{
name: "small-mobile",
use: {
...devices["Pixel 5"],
viewport: { height: 740, width: 360 },
},
},
{
name: "tablet",
use: {
...devices["iPad (gen 7)"],
browserName: "chromium",
viewport: { height: 1024, width: 768 },
},
},
{
name: "desktop",
use: {
viewport: { height: 900, width: 1440 },
},
},
],
});
55 changes: 47 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions src/app/admin/providers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -479,8 +479,8 @@ function ProviderRankingTable({
</span>
</div>
{providers.length > 0 ? (
<div className="overflow-x-auto">
<table className="w-full min-w-[680px] text-left text-sm">
<div className="rounded-lg border border-border bg-card p-3 md:p-0">
<table className="mobile-card-table">
<thead className="border-b border-border text-xs uppercase text-muted-foreground">
<tr>
<th className="px-3 py-3 font-medium">Provider</th>
Expand All @@ -501,7 +501,7 @@ function ProviderRankingTable({
key={provider.id}
className="transition-colors hover:bg-muted/50"
>
<td className="p-0">
<td data-label="Provider" data-full="true" className="p-0">
<Link
href={href}
scroll={false}
Expand All @@ -510,7 +510,7 @@ function ProviderRankingTable({
{provider.name}
</Link>
</td>
<td className="p-0">
<td data-align="right" data-label="Spend" className="p-0">
<Link
href={href}
scroll={false}
Expand All @@ -519,7 +519,7 @@ function ProviderRankingTable({
{formatCurrency(spend?.totalUsd ?? "0")}
</Link>
</td>
<td className="p-0">
<td data-align="right" data-label="Entries" className="p-0">
<Link
href={href}
scroll={false}
Expand All @@ -528,7 +528,7 @@ function ProviderRankingTable({
{spend?.entryCount ?? 0}
</Link>
</td>
<td className="p-0">
<td data-label="Website" className="p-0">
<Link
href={href}
scroll={false}
Expand All @@ -537,7 +537,7 @@ function ProviderRankingTable({
{provider.website || "Not recorded"}
</Link>
</td>
<td className="p-0">
<td data-align="right" data-label="Addresses" className="p-0">
<Link
href={href}
scroll={false}
Expand All @@ -546,7 +546,7 @@ function ProviderRankingTable({
{provider.addresses.length}
</Link>
</td>
<td className="p-0">
<td data-label="Status" className="p-0">
<Link
href={href}
scroll={false}
Expand Down
Loading
Loading