Add testing strategy: Vitest for unit tests, Playwright for E2E
- Vitest: unit tests for packages, components, and app logic (jsdom env, @testing-library/react, co-located test files) - Playwright: E2E browser tests per app (journal.test.ts, planner.test.ts), auto-starts dev servers, scoped via testMatch - Sample unit test for types package - Smoke E2E tests for both apps - Updated CLAUDE.md with test commands and strategy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4fac18c165
commit
40b9c425a0
11 changed files with 873 additions and 6 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -8,3 +8,6 @@ dist/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
.crit.json
|
.crit.json
|
||||||
|
e2e/results/
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
|
|
|
||||||
14
CLAUDE.md
14
CLAUDE.md
|
|
@ -58,9 +58,21 @@ turbo dev # Start both apps in dev mode
|
||||||
turbo build # Build all packages and apps
|
turbo build # Build all packages and apps
|
||||||
turbo typecheck # Type-check all packages
|
turbo typecheck # Type-check all packages
|
||||||
turbo lint # Lint all packages
|
turbo lint # Lint all packages
|
||||||
turbo test # Run tests
|
pnpm test # Run unit tests (vitest)
|
||||||
|
pnpm test:watch # Run unit tests in watch mode
|
||||||
|
pnpm test:e2e # Run E2E tests (playwright, requires dev servers)
|
||||||
|
pnpm test:e2e:ui # Run E2E tests with Playwright UI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
- **Unit tests** (Vitest + jsdom): For packages, components, utilities, and app logic.
|
||||||
|
Place test files next to source: `foo.ts` → `foo.test.ts`.
|
||||||
|
Uses `@testing-library/react` for component tests.
|
||||||
|
- **E2E tests** (Playwright): For browser behavior across both apps.
|
||||||
|
Tests live in `e2e/` at repo root. Scoped per app via `testMatch` in `playwright.config.ts`.
|
||||||
|
Playwright auto-starts dev servers if not already running.
|
||||||
|
|
||||||
## Code Conventions
|
## Code Conventions
|
||||||
|
|
||||||
- All user-facing strings must use i18n (`useTranslation()` hook, never hardcode strings)
|
- All user-facing strings must use i18n (`useTranslation()` hook, never hardcode strings)
|
||||||
|
|
|
||||||
9
e2e/journal.test.ts
Normal file
9
e2e/journal.test.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.describe("Journal", () => {
|
||||||
|
test("loads the home page", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page).toHaveTitle("trails.cool");
|
||||||
|
await expect(page.getByText("Your outdoor activity journal")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
9
e2e/planner.test.ts
Normal file
9
e2e/planner.test.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.describe("Planner", () => {
|
||||||
|
test("loads the home page", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page).toHaveTitle("trails.cool Planner");
|
||||||
|
await expect(page.getByText("Collaborative route planning")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
13
package.json
13
package.json
|
|
@ -11,19 +11,27 @@
|
||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"lint": "turbo lint",
|
"lint": "turbo lint",
|
||||||
"test": "turbo test",
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
"typecheck": "turbo typecheck"
|
"typecheck": "turbo typecheck"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@playwright/test": "^1.58.2",
|
||||||
"@react-router/dev": "^7.13.1",
|
"@react-router/dev": "^7.13.1",
|
||||||
"@react-router/node": "^7.13.1",
|
"@react-router/node": "^7.13.1",
|
||||||
"@react-router/serve": "^7.13.1",
|
"@react-router/serve": "^7.13.1",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"eslint": "^10.1.0",
|
"eslint": "^10.1.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"jsdom": "^29.0.1",
|
||||||
|
"playwright": "^1.58.2",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
|
|
@ -31,6 +39,7 @@
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
"turbo": "^2.8.20",
|
"turbo": "^2.8.20",
|
||||||
"typescript-eslint": "^8.57.1",
|
"typescript-eslint": "^8.57.1",
|
||||||
"vite": "catalog:"
|
"vite": "catalog:",
|
||||||
|
"vitest": "^4.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
packages/types/src/index.test.ts
Normal file
15
packages/types/src/index.test.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import type { Waypoint, Route } from "./index";
|
||||||
|
|
||||||
|
describe("types", () => {
|
||||||
|
it("Waypoint type accepts valid data", () => {
|
||||||
|
const waypoint: Waypoint = { lat: 52.52, lon: 13.405 };
|
||||||
|
expect(waypoint.lat).toBe(52.52);
|
||||||
|
expect(waypoint.lon).toBe(13.405);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Waypoint supports isDayBreak flag", () => {
|
||||||
|
const waypoint: Waypoint = { lat: 48.137, lon: 11.576, name: "Munich", isDayBreak: true };
|
||||||
|
expect(waypoint.isDayBreak).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
45
playwright.config.ts
Normal file
45
playwright.config.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./e2e",
|
||||||
|
outputDir: "./e2e/results",
|
||||||
|
fullyParallel: true,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
reporter: process.env.CI ? "github" : "list",
|
||||||
|
use: {
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
trace: "on-first-retry",
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "journal",
|
||||||
|
testMatch: "journal.test.ts",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
baseURL: "http://localhost:3000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "planner",
|
||||||
|
testMatch: "planner.test.ts",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
baseURL: "http://localhost:3001",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: [
|
||||||
|
{
|
||||||
|
command: "pnpm --filter @trails-cool/journal dev",
|
||||||
|
url: "http://localhost:3000",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: "pnpm --filter @trails-cool/planner dev",
|
||||||
|
url: "http://localhost:3001",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
756
pnpm-lock.yaml
generated
756
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,9 +12,6 @@
|
||||||
"lint": {
|
"lint": {
|
||||||
"dependsOn": ["^build"]
|
"dependsOn": ["^build"]
|
||||||
},
|
},
|
||||||
"test": {
|
|
||||||
"dependsOn": ["^build"]
|
|
||||||
},
|
|
||||||
"typecheck": {
|
"typecheck": {
|
||||||
"dependsOn": ["^build"]
|
"dependsOn": ["^build"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
vitest.config.ts
Normal file
11
vitest.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: "jsdom",
|
||||||
|
include: ["packages/*/src/**/*.test.{ts,tsx}", "apps/*/app/**/*.test.{ts,tsx}"],
|
||||||
|
exclude: ["**/e2e/**"],
|
||||||
|
setupFiles: ["./vitest.setup.ts"],
|
||||||
|
},
|
||||||
|
});
|
||||||
1
vitest.setup.ts
Normal file
1
vitest.setup.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
Loading…
Add table
Add a link
Reference in a new issue