@react-spa-scaffold/mcp 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -0
- package/dist/constants.js.map +1 -1
- package/dist/features/definitions/auth.d.ts +3 -0
- package/dist/features/definitions/auth.d.ts.map +1 -0
- package/dist/features/definitions/auth.js +17 -0
- package/dist/features/definitions/auth.js.map +1 -0
- package/dist/features/definitions/core.d.ts.map +1 -1
- package/dist/features/definitions/core.js +16 -1
- package/dist/features/definitions/core.js.map +1 -1
- package/dist/features/definitions/database.d.ts +3 -0
- package/dist/features/definitions/database.d.ts.map +1 -0
- package/dist/features/definitions/database.js +45 -0
- package/dist/features/definitions/database.js.map +1 -0
- package/dist/features/definitions/deployment.d.ts +3 -0
- package/dist/features/definitions/deployment.d.ts.map +1 -0
- package/dist/features/definitions/deployment.js +14 -0
- package/dist/features/definitions/deployment.js.map +1 -0
- package/dist/features/definitions/forms.d.ts.map +1 -1
- package/dist/features/definitions/forms.js +4 -0
- package/dist/features/definitions/forms.js.map +1 -1
- package/dist/features/definitions/index.d.ts +3 -0
- package/dist/features/definitions/index.d.ts.map +1 -1
- package/dist/features/definitions/index.js +3 -0
- package/dist/features/definitions/index.js.map +1 -1
- package/dist/features/definitions/mobile.d.ts.map +1 -1
- package/dist/features/definitions/mobile.js +11 -2
- package/dist/features/definitions/mobile.js.map +1 -1
- package/dist/features/definitions/observability.js +1 -1
- package/dist/features/definitions/observability.js.map +1 -1
- package/dist/features/definitions/routing.d.ts.map +1 -1
- package/dist/features/definitions/routing.js +2 -1
- package/dist/features/definitions/routing.js.map +1 -1
- package/dist/features/definitions/state.d.ts.map +1 -1
- package/dist/features/definitions/state.js +9 -2
- package/dist/features/definitions/state.js.map +1 -1
- package/dist/features/definitions/testing.d.ts.map +1 -1
- package/dist/features/definitions/testing.js +4 -2
- package/dist/features/definitions/testing.js.map +1 -1
- package/dist/features/registry.d.ts.map +1 -1
- package/dist/features/registry.js +4 -1
- package/dist/features/registry.js.map +1 -1
- package/dist/features/types.test.js +6 -2
- package/dist/features/types.test.js.map +1 -1
- package/dist/resources/docs.d.ts.map +1 -1
- package/dist/resources/docs.js +5 -0
- package/dist/resources/docs.js.map +1 -1
- package/dist/tools/add-features.js +1 -1
- package/dist/tools/add-features.js.map +1 -1
- package/dist/utils/docs.d.ts.map +1 -1
- package/dist/utils/docs.js +2 -0
- package/dist/utils/docs.js.map +1 -1
- package/dist/utils/scaffold/claude-md/index.d.ts.map +1 -1
- package/dist/utils/scaffold/claude-md/index.js +3 -1
- package/dist/utils/scaffold/claude-md/index.js.map +1 -1
- package/dist/utils/scaffold/claude-md/sections.d.ts +2 -0
- package/dist/utils/scaffold/claude-md/sections.d.ts.map +1 -1
- package/dist/utils/scaffold/claude-md/sections.js +132 -2
- package/dist/utils/scaffold/claude-md/sections.js.map +1 -1
- package/dist/utils/scaffold/compute.js +1 -1
- package/dist/utils/scaffold/compute.js.map +1 -1
- package/dist/utils/scaffold/generators.d.ts +2 -2
- package/dist/utils/scaffold/generators.d.ts.map +1 -1
- package/dist/utils/scaffold/generators.js +64 -22
- package/dist/utils/scaffold/generators.js.map +1 -1
- package/package.json +1 -1
- package/templates/.env.example +44 -10
- package/templates/.github/workflows/ci.yml +12 -4
- package/templates/.github/workflows/deploy.yml +59 -0
- package/templates/CLAUDE.md +251 -2
- package/templates/docs/ARCHITECTURE.md +13 -12
- package/templates/docs/AUTHENTICATION.md +325 -0
- package/templates/docs/CODING_STANDARDS.md +65 -0
- package/templates/docs/DEPLOYMENT.md +268 -0
- package/templates/docs/E2E_TESTING.md +133 -11
- package/templates/docs/SUPABASE_INTEGRATION.md +310 -0
- package/templates/docs/TESTING.md +195 -77
- package/templates/e2e/auth/auth.setup.ts +60 -0
- package/templates/e2e/fixtures/index.ts +24 -2
- package/templates/e2e/tests/profile.auth.spec.ts +103 -0
- package/templates/e2e/tests/profile.spec.ts +64 -0
- package/templates/e2e/tests/register-form.spec.ts +38 -0
- package/templates/gitignore +5 -0
- package/templates/package.json +15 -3
- package/templates/playwright.config.ts +39 -4
- package/templates/src/App.tsx +32 -19
- package/templates/src/components/layout/Header.test.tsx +17 -1
- package/templates/src/components/layout/Header.tsx +13 -1
- package/templates/src/components/shared/AccountButton/AccountButton.test.tsx +30 -0
- package/templates/src/components/shared/AccountButton/AccountButton.tsx +38 -0
- package/templates/src/components/shared/AccountButton/index.ts +1 -0
- package/templates/src/components/shared/ErrorBoundary/ErrorBoundary.test.tsx +4 -4
- package/templates/src/components/shared/ErrorBoundary/ErrorBoundary.tsx +55 -53
- package/templates/src/components/shared/ProfileSync/ProfileSync.test.tsx +44 -0
- package/templates/src/components/shared/ProfileSync/ProfileSync.tsx +104 -0
- package/templates/src/components/shared/ProfileSync/index.ts +1 -0
- package/templates/src/components/shared/ProtectedRoute/ProtectedRoute.test.tsx +43 -0
- package/templates/src/components/shared/ProtectedRoute/ProtectedRoute.tsx +35 -0
- package/templates/src/components/shared/ProtectedRoute/index.ts +1 -0
- package/templates/src/components/shared/index.ts +5 -2
- package/templates/src/contexts/clerkContext.tsx +45 -0
- package/templates/src/contexts/performanceContext.tsx +3 -3
- package/templates/src/contexts/supabaseContext.test.tsx +59 -0
- package/templates/src/contexts/supabaseContext.tsx +87 -0
- package/templates/src/hooks/index.ts +40 -2
- package/templates/src/hooks/supabase/index.ts +12 -0
- package/templates/src/hooks/supabase/useProfiles.test.tsx +207 -0
- package/templates/src/hooks/supabase/useProfiles.ts +213 -0
- package/templates/src/hooks/supabase/useSupabaseQuery.test.tsx +150 -0
- package/templates/src/hooks/supabase/useSupabaseQuery.ts +91 -0
- package/templates/src/hooks/useCopyFeedback.test.ts +129 -0
- package/templates/src/hooks/useCopyFeedback.ts +41 -0
- package/templates/src/hooks/useDebouncedCallback.test.ts +164 -0
- package/templates/src/hooks/useDebouncedCallback.ts +47 -0
- package/templates/src/hooks/useDocumentTitle.test.ts +59 -0
- package/templates/src/hooks/useDocumentTitle.ts +31 -0
- package/templates/src/hooks/useIOSViewportReset.test.ts +58 -0
- package/templates/src/hooks/useIOSViewportReset.ts +18 -0
- package/templates/src/hooks/useKeyboardShortcut.test.ts +86 -0
- package/templates/src/hooks/useKeyboardShortcuts.ts +44 -0
- package/templates/src/hooks/useLocalStorage.test.ts +111 -0
- package/templates/src/hooks/useLocalStorage.ts +77 -0
- package/templates/src/hooks/useSyncedFormData.test.ts +75 -0
- package/templates/src/hooks/useSyncedFormData.ts +21 -0
- package/templates/src/hooks/useSyncedState.test.ts +119 -0
- package/templates/src/hooks/useSyncedState.ts +30 -0
- package/templates/src/index.css +1 -0
- package/templates/src/lib/api.test.ts +30 -38
- package/templates/src/lib/api.ts +1 -7
- package/templates/src/lib/config.ts +54 -4
- package/templates/src/lib/constants.ts +10 -0
- package/templates/src/lib/createSelectors.test.ts +136 -0
- package/templates/src/lib/createSelectors.ts +31 -0
- package/templates/src/lib/env.ts +36 -14
- package/templates/src/lib/index.ts +5 -2
- package/templates/src/lib/routes.ts +1 -0
- package/templates/src/lib/sentry.ts +58 -0
- package/templates/src/lib/storage.ts +6 -2
- package/templates/src/lib/supabase/client.ts +58 -0
- package/templates/src/lib/supabase/index.ts +5 -0
- package/templates/src/main.tsx +19 -31
- package/templates/src/mocks/constants.ts +31 -0
- package/templates/src/mocks/fixtures/index.ts +3 -1
- package/templates/src/mocks/fixtures/profiles.ts +55 -0
- package/templates/src/mocks/fixtures/users.ts +91 -0
- package/templates/src/mocks/handlers/index.ts +2 -1
- package/templates/src/mocks/handlers/supabase.ts +64 -0
- package/templates/src/mocks/handlers/todos.ts +1 -1
- package/templates/src/mocks/index.ts +6 -0
- package/templates/src/pages/Profile.test.tsx +263 -0
- package/templates/src/pages/Profile.tsx +171 -0
- package/templates/src/pages/index.ts +1 -0
- package/templates/src/stores/preferencesStore.ts +35 -9
- package/templates/src/test/clerkMock.tsx +137 -0
- package/templates/src/test/fetchMock.ts +58 -0
- package/templates/src/test/index.ts +51 -2
- package/templates/src/test/mocks.ts +128 -1
- package/templates/src/test/providers.tsx +10 -4
- package/templates/src/test/supabaseMock.ts +112 -0
- package/templates/src/test-setup.ts +42 -2
- package/templates/src/types/database.ts +46 -0
- package/templates/src/types/index.ts +1 -0
- package/templates/src/types/supabase.ts +167 -0
- package/templates/src/vite-env.d.ts +6 -0
- package/templates/supabase/migrations/20260104000000_create_profiles_table.sql +67 -0
- package/templates/vitest.config.ts +9 -1
|
@@ -19,6 +19,71 @@ interface User {
|
|
|
19
19
|
|
|
20
20
|
See [Architecture Guide](./ARCHITECTURE.md#state-management) for when to use each solution.
|
|
21
21
|
|
|
22
|
+
### Zustand Best Practices
|
|
23
|
+
|
|
24
|
+
**Auto-generated selectors**: All stores use `createSelectors` for cleaner access:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// Store definition
|
|
28
|
+
const useStoreBase = create<State>()(/* ... */);
|
|
29
|
+
export const useStore = createSelectors(useStoreBase);
|
|
30
|
+
|
|
31
|
+
// Component usage - auto-generated selectors
|
|
32
|
+
const count = useStore.use.count();
|
|
33
|
+
const increment = useStore.use.increment();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Use `useShallow` for multiple values**: Prevents unnecessary re-renders:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
40
|
+
|
|
41
|
+
// Group state values with useShallow
|
|
42
|
+
const { searchQuery, sortBy } = useStore(
|
|
43
|
+
useShallow((s) => ({
|
|
44
|
+
searchQuery: s.searchQuery,
|
|
45
|
+
sortBy: s.sortBy,
|
|
46
|
+
})),
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Persist versioning**: Always include version and migrate for persisted stores:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
persist(
|
|
54
|
+
(set, get) => ({
|
|
55
|
+
/* ... */
|
|
56
|
+
}),
|
|
57
|
+
{
|
|
58
|
+
name: 'store-key',
|
|
59
|
+
version: 1, // Increment on breaking changes
|
|
60
|
+
migrate: (persisted, version) => {
|
|
61
|
+
if (version === 0) {
|
|
62
|
+
return { ...persisted, newField: 'default' };
|
|
63
|
+
}
|
|
64
|
+
return persisted;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Middleware order**: Stack middlewares correctly:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// devtools → persist → subscribeWithSelector → store
|
|
74
|
+
create<State>()(
|
|
75
|
+
devtools(
|
|
76
|
+
persist(
|
|
77
|
+
subscribeWithSelector((set, get) => ({
|
|
78
|
+
/* ... */
|
|
79
|
+
})),
|
|
80
|
+
{ name: 'key' },
|
|
81
|
+
),
|
|
82
|
+
{ name: 'StoreName', enabled: process.env.NODE_ENV === 'development' },
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
22
87
|
### Query Hooks
|
|
23
88
|
|
|
24
89
|
Extract the fetcher function:
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Deployment Guide
|
|
2
|
+
|
|
3
|
+
Automated deployment to Netlify with GitHub Actions for preview and production environments.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
PR Created/Updated → GitHub Actions → Build → Netlify Preview
|
|
11
|
+
↓
|
|
12
|
+
Comment with preview URL on PR
|
|
13
|
+
|
|
14
|
+
Push to main → GitHub Actions → Build → Netlify Production
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Features:**
|
|
18
|
+
|
|
19
|
+
- Automatic preview deploys for pull requests
|
|
20
|
+
- Production deploys on push to main/master
|
|
21
|
+
- PR comments with preview URLs
|
|
22
|
+
- Manual deploy via workflow_dispatch
|
|
23
|
+
- Security headers pre-configured
|
|
24
|
+
|
|
25
|
+
**Note:** Enable branch protection rules to require CI to pass before merging to main.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Netlify Setup
|
|
30
|
+
|
|
31
|
+
### 1. Create Netlify Site
|
|
32
|
+
|
|
33
|
+
1. Go to [app.netlify.com](https://app.netlify.com) and sign in
|
|
34
|
+
2. Click "Add new site" → "Import an existing project"
|
|
35
|
+
3. Connect your GitHub repository
|
|
36
|
+
4. Configure build settings (auto-detected from `netlify.toml`):
|
|
37
|
+
- Build command: `npm run build`
|
|
38
|
+
- Publish directory: `dist`
|
|
39
|
+
5. Click "Deploy site"
|
|
40
|
+
|
|
41
|
+
### 2. Get API Credentials
|
|
42
|
+
|
|
43
|
+
1. **Personal Access Token** (for `NETLIFY_AUTH_TOKEN`):
|
|
44
|
+
- User Settings → Applications → Personal access tokens
|
|
45
|
+
- Click "New access token", name it, and copy the token
|
|
46
|
+
|
|
47
|
+
2. **Site ID** (for `NETLIFY_SITE_ID`):
|
|
48
|
+
- Site Settings → General → Site details → Site ID
|
|
49
|
+
|
|
50
|
+
### 3. Add GitHub Secrets
|
|
51
|
+
|
|
52
|
+
Go to your repository → Settings → Secrets and variables → Actions → New repository secret:
|
|
53
|
+
|
|
54
|
+
| Secret Name | Description |
|
|
55
|
+
| -------------------- | -------------------------------- |
|
|
56
|
+
| `NETLIFY_AUTH_TOKEN` | Personal access token from above |
|
|
57
|
+
| `NETLIFY_SITE_ID` | Site ID from above |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Environment Variables
|
|
62
|
+
|
|
63
|
+
### Build-Time Variables
|
|
64
|
+
|
|
65
|
+
Set in Netlify Dashboard → Site Settings → Environment variables:
|
|
66
|
+
|
|
67
|
+
| Variable | Required | Description |
|
|
68
|
+
| ---------------------------- | ------------- | --------------------- |
|
|
69
|
+
| `VITE_CLERK_PUBLISHABLE_KEY` | If using auth | Clerk publishable key |
|
|
70
|
+
| `VITE_SUPABASE_DATABASE_URL` | If using db | Supabase project URL |
|
|
71
|
+
| `VITE_SUPABASE_ANON_KEY` | If using db | Supabase anon key |
|
|
72
|
+
|
|
73
|
+
### Context-Specific Variables
|
|
74
|
+
|
|
75
|
+
Use Netlify CLI to set variables for specific contexts:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Set for all contexts
|
|
79
|
+
netlify env:set VAR_NAME value
|
|
80
|
+
|
|
81
|
+
# Set for production only
|
|
82
|
+
netlify env:set VAR_NAME value --context production
|
|
83
|
+
|
|
84
|
+
# Set for deploy previews only
|
|
85
|
+
netlify env:set VAR_NAME value --context deploy-preview
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Supabase Integration
|
|
91
|
+
|
|
92
|
+
If using the database feature, connect Supabase to Netlify for automatic environment variable sync.
|
|
93
|
+
|
|
94
|
+
### Extension Setup
|
|
95
|
+
|
|
96
|
+
1. **Netlify Dashboard** → Extensions → Search "Supabase" → Install
|
|
97
|
+
2. **Project Settings** → General → Supabase → Connect
|
|
98
|
+
3. Authorize with Supabase and select your project
|
|
99
|
+
4. For Vite projects:
|
|
100
|
+
- Framework: Select "Other"
|
|
101
|
+
- Environment variable prefix: Enter `VITE_`
|
|
102
|
+
|
|
103
|
+
### Auto-Configured Variables
|
|
104
|
+
|
|
105
|
+
After connecting, these are automatically injected:
|
|
106
|
+
|
|
107
|
+
| Variable | Description |
|
|
108
|
+
| ---------------------------- | ---------------------------------------- |
|
|
109
|
+
| `VITE_SUPABASE_DATABASE_URL` | Project URL |
|
|
110
|
+
| `VITE_SUPABASE_ANON_KEY` | Client API key |
|
|
111
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Server-side only (not exposed to client) |
|
|
112
|
+
|
|
113
|
+
### Local Development
|
|
114
|
+
|
|
115
|
+
Run `netlify dev` to inject Supabase variables locally:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm install -g netlify-cli
|
|
119
|
+
netlify login
|
|
120
|
+
netlify link # Link to your Netlify site
|
|
121
|
+
netlify dev # Starts dev server with injected env vars
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Preview Deploys
|
|
127
|
+
|
|
128
|
+
Every pull request automatically gets a preview deployment:
|
|
129
|
+
|
|
130
|
+
1. Open a PR against main/master
|
|
131
|
+
2. GitHub Actions builds and deploys to Netlify
|
|
132
|
+
3. Bot comments on PR with preview URL
|
|
133
|
+
4. Preview updates on each push to the PR
|
|
134
|
+
5. Preview is deleted when PR is closed
|
|
135
|
+
|
|
136
|
+
### Preview URL Pattern
|
|
137
|
+
|
|
138
|
+
- PR previews: `https://pr-{number}--{site-name}.netlify.app`
|
|
139
|
+
- Branch deploys: `https://{branch}--{site-name}.netlify.app`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Production Deploys
|
|
144
|
+
|
|
145
|
+
Pushing to main/master triggers production deployment:
|
|
146
|
+
|
|
147
|
+
1. Deploy workflow builds the app
|
|
148
|
+
2. Deploys to production URL: `https://your-site.netlify.app`
|
|
149
|
+
|
|
150
|
+
**Important:** Enable branch protection rules on main/master to require CI to pass before merging. This ensures production only gets code that passed all checks.
|
|
151
|
+
|
|
152
|
+
### Manual Deploys
|
|
153
|
+
|
|
154
|
+
Use workflow_dispatch for manual production deploys:
|
|
155
|
+
|
|
156
|
+
1. Go to Actions → Deploy → Run workflow
|
|
157
|
+
2. Select branch
|
|
158
|
+
3. Click "Run workflow"
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Configuration
|
|
163
|
+
|
|
164
|
+
### netlify.toml
|
|
165
|
+
|
|
166
|
+
The `netlify.toml` file in your project root configures:
|
|
167
|
+
|
|
168
|
+
- **Build settings**: Command and publish directory
|
|
169
|
+
- **Redirects**: SPA fallback to index.html
|
|
170
|
+
- **Headers**: Security headers and caching rules
|
|
171
|
+
- **Context overrides**: Environment-specific settings
|
|
172
|
+
|
|
173
|
+
### Customizing Headers
|
|
174
|
+
|
|
175
|
+
Add custom headers in `netlify.toml`:
|
|
176
|
+
|
|
177
|
+
```toml
|
|
178
|
+
[[headers]]
|
|
179
|
+
for = "/api/*"
|
|
180
|
+
[headers.values]
|
|
181
|
+
Access-Control-Allow-Origin = "https://example.com"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Redirect Rules
|
|
185
|
+
|
|
186
|
+
Add redirects before the SPA fallback:
|
|
187
|
+
|
|
188
|
+
```toml
|
|
189
|
+
[[redirects]]
|
|
190
|
+
from = "/old-path"
|
|
191
|
+
to = "/new-path"
|
|
192
|
+
status = 301
|
|
193
|
+
|
|
194
|
+
# SPA fallback (keep last)
|
|
195
|
+
[[redirects]]
|
|
196
|
+
from = "/*"
|
|
197
|
+
to = "/index.html"
|
|
198
|
+
status = 200
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## CLI Commands
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Install Netlify CLI
|
|
207
|
+
npm install -g netlify-cli
|
|
208
|
+
|
|
209
|
+
# Login and link
|
|
210
|
+
netlify login
|
|
211
|
+
netlify link
|
|
212
|
+
|
|
213
|
+
# Local development with Netlify env vars
|
|
214
|
+
netlify dev
|
|
215
|
+
|
|
216
|
+
# Manual deploys
|
|
217
|
+
npm run deploy:preview # Deploy preview build
|
|
218
|
+
npm run deploy:prod # Deploy to production
|
|
219
|
+
|
|
220
|
+
# Environment variables
|
|
221
|
+
netlify env:list # List all variables
|
|
222
|
+
netlify env:set KEY value # Set variable
|
|
223
|
+
netlify env:get KEY # Get variable value
|
|
224
|
+
netlify env:unset KEY # Remove variable
|
|
225
|
+
|
|
226
|
+
# Build locally
|
|
227
|
+
netlify build # Test production build
|
|
228
|
+
netlify build --context deploy-preview # Test preview build
|
|
229
|
+
|
|
230
|
+
# Check status
|
|
231
|
+
netlify status
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Troubleshooting
|
|
237
|
+
|
|
238
|
+
| Issue | Cause | Solution |
|
|
239
|
+
| ---------------------------------- | ---------------------------- | --------------------------------------------- |
|
|
240
|
+
| Deploy fails with "Site not found" | Missing `NETLIFY_SITE_ID` | Add secret to GitHub repository |
|
|
241
|
+
| Deploy fails with "Unauthorized" | Invalid `NETLIFY_AUTH_TOKEN` | Regenerate token in Netlify |
|
|
242
|
+
| Preview not commenting on PR | Missing permissions | Check workflow has `pull-requests: write` |
|
|
243
|
+
| Env vars undefined in build | Not set in Netlify | Add to Netlify Dashboard or use `netlify env` |
|
|
244
|
+
| 404 on page refresh | SPA fallback not working | Check `netlify.toml` has `/* -> /index.html` |
|
|
245
|
+
| Production deploy not triggered | CI workflow failed | Check CI workflow status first |
|
|
246
|
+
|
|
247
|
+
### Debug Build Locally
|
|
248
|
+
|
|
249
|
+
Test production build locally:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm run build
|
|
253
|
+
npx serve dist
|
|
254
|
+
|
|
255
|
+
# Or with Netlify CLI:
|
|
256
|
+
netlify build
|
|
257
|
+
netlify deploy --dir=dist
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Resources
|
|
263
|
+
|
|
264
|
+
- [Netlify Documentation](https://docs.netlify.com/)
|
|
265
|
+
- [Netlify CLI Reference](https://cli.netlify.com/)
|
|
266
|
+
- [File-based Configuration](https://docs.netlify.com/configure-builds/file-based-configuration/)
|
|
267
|
+
- [GitHub Actions for Netlify](https://github.com/nwtgck/actions-netlify)
|
|
268
|
+
- [Netlify Supabase Extension](https://www.netlify.com/integrations/supabase/)
|
|
@@ -10,25 +10,29 @@
|
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
e2e/
|
|
13
|
+
├── auth/
|
|
14
|
+
│ └── auth.setup.ts # Clerk authentication setup
|
|
13
15
|
├── fixtures/
|
|
14
|
-
│ └── index.ts # setupPage,
|
|
16
|
+
│ └── index.ts # setupPage, setupCleanPage, test, expect
|
|
15
17
|
├── tests/ # Functional E2E tests
|
|
16
18
|
│ ├── home.spec.ts # Page structure, accessibility
|
|
17
19
|
│ ├── theme.spec.ts # Theme toggle, persistence
|
|
18
20
|
│ ├── language.spec.ts # Language switcher
|
|
19
|
-
│
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
│ ├── navigation.spec.ts # Routing, 404
|
|
22
|
+
│ ├── profile.spec.ts # Unauthenticated profile tests
|
|
23
|
+
│ └── profile.auth.spec.ts # Authenticated profile tests
|
|
24
|
+
├── performance/ # Performance regression tests
|
|
25
|
+
│ ├── setup.ts # Performance test fixture
|
|
26
|
+
│ └── home.spec.ts # Home page performance tests
|
|
27
|
+
└── .clerk/ # Auth state storage (gitignored)
|
|
28
|
+
└── user.json # Saved auth state for tests
|
|
23
29
|
```
|
|
24
30
|
|
|
25
31
|
## Imports
|
|
26
32
|
|
|
27
33
|
```typescript
|
|
28
34
|
import { expect, test } from '@playwright/test';
|
|
29
|
-
|
|
30
|
-
// For tests that need state clearing
|
|
31
|
-
import { setupPage } from '../fixtures';
|
|
35
|
+
import { setupPage, setupCleanPage } from '../fixtures';
|
|
32
36
|
```
|
|
33
37
|
|
|
34
38
|
## Core Patterns
|
|
@@ -107,13 +111,130 @@ await page.waitForTimeout(500);
|
|
|
107
111
|
## Running Tests
|
|
108
112
|
|
|
109
113
|
```bash
|
|
110
|
-
npm run e2e # Run
|
|
111
|
-
npm run e2e:
|
|
114
|
+
npm run e2e # Run desktop tests
|
|
115
|
+
npm run e2e:mobile # Run mobile tests (Pixel 5 emulation)
|
|
116
|
+
npm run e2e:all # Run both desktop and mobile
|
|
117
|
+
npm run e2e:ui # Interactive UI mode
|
|
112
118
|
npm run e2e:perf # Run performance tests
|
|
113
119
|
npm run e2e:perf:ui # Performance tests with interactive UI
|
|
114
|
-
|
|
120
|
+
|
|
121
|
+
# Authenticated tests (requires credentials)
|
|
122
|
+
npx playwright test --project=authenticated
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Authenticated Testing
|
|
126
|
+
|
|
127
|
+
Tests requiring authentication use `@clerk/testing` with Playwright. These tests run with a real authenticated user session.
|
|
128
|
+
|
|
129
|
+
### Setup
|
|
130
|
+
|
|
131
|
+
1. Install the testing package (already included):
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm install -D @clerk/testing
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
2. Set environment variables in `.env`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
CLERK_SECRET_KEY=sk_test_xxxxx
|
|
141
|
+
E2E_CLERK_USER_USERNAME=test@example.com
|
|
142
|
+
E2E_CLERK_USER_PASSWORD=your-test-password
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
3. Create a test user in your Clerk dashboard with the above credentials.
|
|
146
|
+
|
|
147
|
+
### File Naming Convention
|
|
148
|
+
|
|
149
|
+
- `*.spec.ts` - Regular tests (run in `desktop`/`mobile` projects)
|
|
150
|
+
- `*.auth.spec.ts` - Authenticated tests (run in `authenticated` project only)
|
|
151
|
+
|
|
152
|
+
### Writing Authenticated Tests
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// e2e/tests/my-feature.auth.spec.ts
|
|
156
|
+
import { expect, test } from '@playwright/test';
|
|
157
|
+
import { existsSync } from 'fs';
|
|
158
|
+
import { dirname, join } from 'path';
|
|
159
|
+
import { fileURLToPath } from 'url';
|
|
160
|
+
|
|
161
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
162
|
+
const authFile = join(__dirname, '../.clerk/user.json');
|
|
163
|
+
const hasAuthState = existsSync(authFile);
|
|
164
|
+
|
|
165
|
+
test.describe('My Authenticated Feature', () => {
|
|
166
|
+
// Skip if auth state doesn't exist
|
|
167
|
+
test.skip(!hasAuthState, 'Authentication required');
|
|
168
|
+
|
|
169
|
+
test.beforeEach(async ({ page }) => {
|
|
170
|
+
// User is already authenticated via storageState
|
|
171
|
+
await page.goto('/protected-page');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('can access protected content', async ({ page }) => {
|
|
175
|
+
await expect(page.getByText('Protected Content')).toBeVisible();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
115
178
|
```
|
|
116
179
|
|
|
180
|
+
### How It Works
|
|
181
|
+
|
|
182
|
+
1. **Setup project** runs `auth.setup.ts` which:
|
|
183
|
+
- Calls `clerkSetup()` to get a testing token
|
|
184
|
+
- Signs in with test credentials
|
|
185
|
+
- Saves auth state to `e2e/.clerk/user.json`
|
|
186
|
+
|
|
187
|
+
2. **Authenticated project** uses the saved state:
|
|
188
|
+
- Loads `storageState` from `user.json`
|
|
189
|
+
- Tests run with pre-authenticated session
|
|
190
|
+
|
|
191
|
+
3. **Tests skip gracefully** when credentials aren't configured
|
|
192
|
+
|
|
193
|
+
## Mobile Testing
|
|
194
|
+
|
|
195
|
+
Tests run on both desktop (Chrome) and mobile (Pixel 5) viewports. Use the `isMobile` fixture for device-specific behavior.
|
|
196
|
+
|
|
197
|
+
### Using isMobile Fixture
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { expect, test } from '@playwright/test';
|
|
201
|
+
|
|
202
|
+
test('theme toggle works on all devices', async ({ page, isMobile }) => {
|
|
203
|
+
await page.goto('/');
|
|
204
|
+
|
|
205
|
+
// Same test logic works on both platforms
|
|
206
|
+
await page.getByRole('button', { name: /dark mode/i }).click();
|
|
207
|
+
await expect(page.locator('html')).toHaveClass(/dark/);
|
|
208
|
+
|
|
209
|
+
// Add mobile-specific assertions if needed
|
|
210
|
+
if (isMobile) {
|
|
211
|
+
// Verify touch-friendly button size, etc.
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Skip Tests by Platform
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
test('hover tooltip shows', async ({ page, isMobile }) => {
|
|
220
|
+
test.skip(isMobile, 'Hover not available on touch devices');
|
|
221
|
+
// Desktop-only test
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('touch gesture works', async ({ page, isMobile }) => {
|
|
225
|
+
test.skip(!isMobile, 'Touch gesture only on mobile');
|
|
226
|
+
// Mobile-only test
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Common Patterns
|
|
231
|
+
|
|
232
|
+
| Pattern | Desktop | Mobile |
|
|
233
|
+
| -------------- | --------- | --------------- |
|
|
234
|
+
| Viewport width | 1280px | 393px (Pixel 5) |
|
|
235
|
+
| Touch events | Click | Tap |
|
|
236
|
+
| Hover states | Supported | Not applicable |
|
|
237
|
+
|
|
117
238
|
## Performance Testing
|
|
118
239
|
|
|
119
240
|
Performance tests use [react-performance-tracking](https://github.com/mkaczkowski/react-performance-tracking) to measure:
|
|
@@ -129,3 +250,4 @@ Performance tests use [react-performance-tracking](https://github.com/mkaczkowsk
|
|
|
129
250
|
- [ ] No arbitrary timeouts
|
|
130
251
|
- [ ] Tests behavior, not implementation
|
|
131
252
|
- [ ] Uses `setupPage` when testing persistence
|
|
253
|
+
- [ ] Considers mobile viewport when relevant
|