Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
257 changes: 257 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
name: Test Suite

on:
pull_request:
branches: [main, dev, dev-*]
types: [opened, synchronize, reopened]
push:
branches: [main]

# Cancel in-progress runs when a new workflow with the same name is triggered
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: write
issues: write

jobs:
# Unit and Integration Tests
unit-tests:
name: Unit & Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run unit and integration tests
run: npm test -- --run --reporter=verbose
env:
CI: true

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: |
coverage/
test-results/
retention-days: 7

# E2E Tests
e2e-tests:
name: E2E Tests (Playwright)
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Start dev server
run: |
npm run dev &
npx wait-on http://localhost:8080 --timeout 60000
env:
CI: true

- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest

- name: Start Supabase local
run: |
supabase start
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

- name: Run Playwright tests
run: npx playwright test --project=chromium
env:
CI: true
# Add any required env variables for e2e tests
VITE_SUPABASE_URL: http://127.0.0.1:54321
VITE_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}

- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7

- name: Upload test screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-screenshots
path: test-results/
retention-days: 7

- name: Stop Supabase
if: always()
run: supabase stop

# Type Check
type-check:
name: TypeScript Type Check
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run type check
run: npm run type-check || npx tsc --noEmit

# Lint Check
lint:
name: ESLint Check
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run ESLint
run: npm run lint || npx eslint . --ext .ts,.tsx,.js,.jsx

# Build Check
build:
name: Build Check
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build project
run: npm run build
env:
CI: true

- name: Check build output
run: |
if [ ! -d "dist" ]; then
echo "Build failed: dist directory not created"
exit 1
fi

# Status Check - Required for PR merging
test-status:
name: Test Status Check
runs-on: ubuntu-latest
needs: [unit-tests, e2e-tests, type-check, lint, build]
if: always()

steps:
- name: Check test results
run: |
if [ "${{ needs.unit-tests.result }}" != "success" ] || \
[ "${{ needs.e2e-tests.result }}" != "success" ] || \
[ "${{ needs.type-check.result }}" != "success" ] || \
[ "${{ needs.lint.result }}" != "success" ] || \
[ "${{ needs.build.result }}" != "success" ]; then
echo "❌ One or more test jobs failed"
echo "Unit Tests: ${{ needs.unit-tests.result }}"
echo "E2E Tests: ${{ needs.e2e-tests.result }}"
echo "Type Check: ${{ needs.type-check.result }}"
echo "Lint: ${{ needs.lint.result }}"
echo "Build: ${{ needs.build.result }}"
exit 1
else
echo "✅ All tests passed!"
exit 0
fi

- name: Post status comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const status = {
unit: '${{ needs.unit-tests.result }}',
e2e: '${{ needs.e2e-tests.result }}',
typeCheck: '${{ needs.type-check.result }}',
lint: '${{ needs.lint.result }}',
build: '${{ needs.build.result }}'
};

const icon = (result) => result === 'success' ? '✅' : '❌';

const comment = `## 🧪 Test Results

| Job | Status |
|-----|--------|
| Unit & Integration Tests | ${icon(status.unit)} ${status.unit} |
| E2E Tests (Playwright) | ${icon(status.e2e)} ${status.e2e} |
| TypeScript Check | ${icon(status.typeCheck)} ${status.typeCheck} |
| ESLint | ${icon(status.lint)} ${status.lint} |
| Build | ${icon(status.build)} ${status.build} |

${Object.values(status).every(s => s === 'success') ? '✅ **All checks passed!** Ready to merge.' : '❌ **Some checks failed.** Please fix the issues before merging.'}`;

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
132 changes: 132 additions & 0 deletions docs/ARCHITECTURE_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Architecture Guide

This repo uses a **feature-first** folder structure.

## High-level map

- `src/features/<feature>/...`
- Feature-owned UI, hooks, services, and (optionally) feature-local tests.
- `src/pages/*`
- Thin route wrappers that re-export the corresponding feature page.
- `src/components/*`
- Shared UI and app-level components used by multiple features (layout, shared widgets).
- `src/components/ui/*` is the shared UI kit.
- `src/hooks/*`
- Cross-feature hooks (auth, subscription, theme, generic infra hooks).
- `src/services/*`
- Cross-feature services (analytics, test runner, editor bounds, etc.).
- `src/shared/*`
- Shared “app infrastructure” (e.g. notification service, shared queries) that is not feature-owned.
- `src/types/*`
- Cross-feature shared types.

## What goes where (rules of thumb)

### 1) Creating a new feature
Create a folder:

- `src/features/<feature>/`

Common subfolders:

- `src/features/<feature>/components/`
- `src/features/<feature>/hooks/`
- `src/features/<feature>/services/`
- `src/features/<feature>/types/` (only if types are feature-private)

Feature pages:

- `src/features/<feature>/<Feature>Page.tsx` (or `<Feature>HubPage.tsx`, `<Feature>SolverPage.tsx`, etc.)

Then create a thin wrapper route:

- `src/pages/<Feature>.tsx` that simply `export { default } from "@/features/<feature>/<...>";`

### 2) Creating a new component
Decide ownership first:

- **Feature-owned component**: put it in `src/features/<feature>/components/`.
- Examples: Survey steps, System Design canvas UI, Behavioral interview UI.
- **Shared component** (reused by multiple features): put it in `src/components/`.
- Examples: `Sidebar`, `ConfirmDialog`, shared editor/diagram components.
- **UI primitives** (buttons, dialog, tabs, etc.): keep in `src/components/ui/`.

### 3) Creating a new hook

- **Feature-specific behavior/data**: `src/features/<feature>/hooks/`.
- **Cross-feature / infrastructure** (auth, subscription, theme, localStorage helpers, etc.): `src/hooks/`.

### 4) Creating a new service

- **Feature-specific persistence/integration**: `src/features/<feature>/services/`.
- **Cross-feature infrastructure**: `src/services/`.

### 5) Shared utilities and types

- If multiple features use it:
- Types: `src/types/*`
- Utilities: `src/lib/*` or `src/utils/*`
- Shared services: `src/shared/*` or `src/services/*` depending on responsibility

## Import boundaries (recommended)

- Features can import from:
- `@/components/*` (shared UI)
- `@/components/ui/*`
- `@/hooks/*` (cross-feature)
- `@/services/*` (cross-feature)
- `@/shared/*`, `@/types/*`, `@/utils/*`, `@/lib/*`
- Their own feature code: `@/features/<feature>/*`

- Shared code (`src/components`, `src/hooks`, `src/services`) should **avoid** importing from feature folders when possible.
- Exception: “shared dashboard widgets” may currently import feature hooks (e.g. `useProblems`). If these are actually dashboard-owned, prefer moving them under `src/features/dashboard/components/*`.

## Testing conventions

This repo currently uses a **mixed** approach:

### A) Centralized test folders (current common pattern)

- Shared hooks: `src/hooks/__tests__/*`
- Shared services: `src/services/__tests__/*`
- Shared components: `src/components/__tests__/*`

This works well for cross-feature code.

### B) Co-located tests (recommended for new feature code)

For feature-owned code, prefer co-location:

- `src/features/<feature>/components/__tests__/*`
- `src/features/<feature>/hooks/__tests__/*`
- `src/features/<feature>/services/__tests__/*`

Example already in repo:

- `src/features/survey/components/steps/__tests__/PaywallStep.test.tsx`

### Suggested rule going forward

- **If the code is feature-owned**: co-locate tests under that feature.
- **If the code is shared cross-feature**: keep tests in the centralized `src/<area>/__tests__` folders.
- Avoid testing `src/pages/*` wrappers; test the feature pages instead.

## Known follow-up opportunities (optional cleanups)

These are not required for correctness, but they improve “clean architecture”:

- **Dashboard widgets in `src/components/*`**
- Several dashboard-centric components in `src/components` import feature hooks (e.g. `useProblems`). Consider moving them into `src/features/dashboard/components/*`.
- **Admin access logic duplication**
- `ADMIN_EMAILS` is duplicated in `Sidebar` and `AdminRoute`. Consider extracting to a single module (e.g. `src/features/admin/constants.ts` or `src/shared/auth/adminAccess.ts`).

## Quick checklist when adding code

- Is it feature-owned?
- Yes -> `src/features/<feature>/...`
- No -> shared folders (`src/components`, `src/hooks`, `src/services`, `src/shared`)
- Does it need a route?
- Add `src/pages/<RouteName>.tsx` wrapper
- Does it need tests?
- Feature-owned -> co-locate under the feature
- Shared -> keep in centralized `__tests__` folder
Loading
Loading