claude-flow-novice 1.5.0 → 1.5.1
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/.claude/commands/fullstack.md +134 -0
- package/.claude/commands/index.js +4 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +938 -0
- package/package.json +1 -1
- package/src/cli/simple-commands/init/templates/CLAUDE.md +2 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Launch full-stack development team with coordinated consensus validation"
|
|
3
|
+
argument-hint: "<goal description>"
|
|
4
|
+
allowed-tools: ["Task", "TodoWrite", "Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Full-Stack Development Team
|
|
8
|
+
|
|
9
|
+
Launch a coordinated full-stack development team to accomplish complex features with built-in consensus validation.
|
|
10
|
+
|
|
11
|
+
**Goal**: $ARGUMENTS
|
|
12
|
+
|
|
13
|
+
## Team Composition
|
|
14
|
+
|
|
15
|
+
The fullstack team includes:
|
|
16
|
+
- **Researcher**: Requirements analysis, pattern detection, architectural planning
|
|
17
|
+
- **Coder**: Implementation of backend and frontend features
|
|
18
|
+
- **Tester**: Comprehensive testing including unit, integration, and E2E tests
|
|
19
|
+
- **Reviewer**: Code quality analysis, security review, performance optimization
|
|
20
|
+
- **Architect** (for complex tasks): System design and architectural decisions
|
|
21
|
+
|
|
22
|
+
## Execution Pattern
|
|
23
|
+
|
|
24
|
+
### Phase 1: Execute
|
|
25
|
+
1. **Launch Primary Swarm** (3-5 agents in parallel using Claude Code's Task tool)
|
|
26
|
+
- All agents execute concurrently in a single message
|
|
27
|
+
- Each agent receives full context and specific responsibilities
|
|
28
|
+
- Agents produce deliverables with confidence scores
|
|
29
|
+
|
|
30
|
+
2. **Progress Tracking**
|
|
31
|
+
- Use TodoWrite to track all tasks (5-10+ items minimum)
|
|
32
|
+
- Mark tasks as in_progress before starting
|
|
33
|
+
- Complete tasks immediately after finishing (no batching)
|
|
34
|
+
|
|
35
|
+
### Phase 2: Verify (Only when Primary Swarm believes it's done)
|
|
36
|
+
1. **Self-Assessment**: Primary swarm evaluates completion (confidence ≥75% required)
|
|
37
|
+
2. **Launch Consensus Swarm** (2-4 validators in parallel)
|
|
38
|
+
- Independent verification with Byzantine fault tolerance
|
|
39
|
+
- Comprehensive validation: tests, security, performance, architecture
|
|
40
|
+
- Voting mechanism with critical criteria checks
|
|
41
|
+
|
|
42
|
+
### Phase 3: Decision
|
|
43
|
+
**PASS Criteria** (≥90% agreement + all critical checks):
|
|
44
|
+
- All tests passing (100% for critical paths, ≥80% coverage overall)
|
|
45
|
+
- No security vulnerabilities
|
|
46
|
+
- Performance within acceptable thresholds
|
|
47
|
+
- Architecture review approved
|
|
48
|
+
|
|
49
|
+
**FAIL Criteria**:
|
|
50
|
+
- <90% validator agreement OR critical criteria failed
|
|
51
|
+
- Round counter increments
|
|
52
|
+
- Feedback injected into context
|
|
53
|
+
|
|
54
|
+
### Phase 4: Action
|
|
55
|
+
- **PASS**: Store results → Move to next task → Report completion
|
|
56
|
+
- **FAIL**:
|
|
57
|
+
- If Round < 10: Inject feedback → Relaunch primary swarm with improvements
|
|
58
|
+
- If Round ≥ 10: Escalate to human with full history + recommendations
|
|
59
|
+
|
|
60
|
+
### Phase 5: Repeat
|
|
61
|
+
Iterative improvement with accumulated context from all previous rounds until:
|
|
62
|
+
- Success achieved (PASS)
|
|
63
|
+
- Maximum rounds reached (10)
|
|
64
|
+
- Human intervention requested
|
|
65
|
+
|
|
66
|
+
## Consensus Validation Requirements
|
|
67
|
+
|
|
68
|
+
**Validator Team** (4 agents for Byzantine fault tolerance):
|
|
69
|
+
1. **Validator 1** - Test Coverage & Quality
|
|
70
|
+
2. **Validator 2** - Security & Compliance
|
|
71
|
+
3. **Validator 3** - Performance & Optimization
|
|
72
|
+
4. **Validator 4** - Architecture & Design
|
|
73
|
+
|
|
74
|
+
**Scoring System**:
|
|
75
|
+
- Overall Score: 0-100 (weighted average)
|
|
76
|
+
- Pass Threshold: ≥90/100 with unanimous approval
|
|
77
|
+
- Tier 2: 85-94 (production-ready with minor issues)
|
|
78
|
+
- Tier 3: 75-84 (needs improvements)
|
|
79
|
+
- Fail: <75 (significant issues)
|
|
80
|
+
|
|
81
|
+
**Critical Criteria** (all must pass):
|
|
82
|
+
- Tests executing and passing
|
|
83
|
+
- No critical security vulnerabilities
|
|
84
|
+
- Code compiles/builds successfully
|
|
85
|
+
- Core functionality working
|
|
86
|
+
|
|
87
|
+
## Usage Examples
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Simple feature
|
|
91
|
+
/fullstack Add user authentication with JWT tokens and password hashing
|
|
92
|
+
|
|
93
|
+
# Complex feature with multiple components
|
|
94
|
+
/fullstack Build a real-time chat system with user presence, typing indicators, message history, and file sharing
|
|
95
|
+
|
|
96
|
+
# Backend API
|
|
97
|
+
/fullstack Create REST API for product catalog with CRUD operations, search, filtering, and pagination
|
|
98
|
+
|
|
99
|
+
# Frontend feature
|
|
100
|
+
/fullstack Implement responsive dashboard with data visualization, real-time updates, and mobile support
|
|
101
|
+
|
|
102
|
+
# Full-stack integration
|
|
103
|
+
/fullstack Develop e-commerce checkout flow with payment processing, order management, and email notifications
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Execution Instructions
|
|
107
|
+
|
|
108
|
+
When this command is invoked:
|
|
109
|
+
|
|
110
|
+
1. **Parse the goal** from $ARGUMENTS
|
|
111
|
+
2. **Create comprehensive task list** using TodoWrite (5-10+ items)
|
|
112
|
+
3. **Launch primary swarm** using Claude Code's Task tool in a SINGLE message:
|
|
113
|
+
```
|
|
114
|
+
Task("Research and analyze requirements", "Analyze goal: [goal]. Research patterns, identify requirements, create detailed specifications...", "researcher")
|
|
115
|
+
Task("Implement core features", "Implement goal: [goal]. Write production code following best practices...", "coder")
|
|
116
|
+
Task("Create comprehensive tests", "Test goal: [goal]. Write unit, integration, and E2E tests...", "tester")
|
|
117
|
+
Task("Review and optimize", "Review implementation of goal: [goal]. Check security, performance, architecture...", "reviewer")
|
|
118
|
+
```
|
|
119
|
+
4. **Monitor progress** and update TodoWrite in real-time
|
|
120
|
+
5. **Self-assess** when primary work complete
|
|
121
|
+
6. **Launch consensus swarm** if confidence ≥75%
|
|
122
|
+
7. **Process results** according to validation outcome
|
|
123
|
+
8. **Iterate or complete** based on consensus decision
|
|
124
|
+
|
|
125
|
+
## Important Notes
|
|
126
|
+
|
|
127
|
+
- **Always use Task tool in parallel** - spawn all agents in ONE message
|
|
128
|
+
- **Update TodoWrite immediately** - never batch completions
|
|
129
|
+
- **Run post-edit hooks** after EVERY file modification
|
|
130
|
+
- **Store results in appropriate directories** - never save to root
|
|
131
|
+
- **Follow file organization** - use /src, /tests, /docs structure
|
|
132
|
+
- **Enable consensus validation** - for production-ready code quality
|
|
133
|
+
|
|
134
|
+
Execute the fullstack development workflow for the specified goal with comprehensive validation.
|
|
@@ -43,6 +43,7 @@ export { default as registerClaudeMd } from './register-claude-md.js';
|
|
|
43
43
|
export const Commands = {
|
|
44
44
|
SPARC: 'sparc',
|
|
45
45
|
SWARM: 'swarm',
|
|
46
|
+
FULLSTACK: 'fullstack',
|
|
46
47
|
HOOKS: 'hooks',
|
|
47
48
|
NEURAL: 'neural',
|
|
48
49
|
PERFORMANCE: 'performance',
|
|
@@ -57,6 +58,8 @@ export const Commands = {
|
|
|
57
58
|
*/
|
|
58
59
|
export const Aliases = {
|
|
59
60
|
s: 'swarm',
|
|
61
|
+
fs: 'fullstack',
|
|
62
|
+
full: 'fullstack',
|
|
60
63
|
h: 'hooks',
|
|
61
64
|
n: 'neural',
|
|
62
65
|
p: 'performance',
|
|
@@ -91,6 +94,7 @@ export function getQuickHelp() {
|
|
|
91
94
|
🚀 **CLAUDE-FLOW SLASH COMMANDS**
|
|
92
95
|
|
|
93
96
|
**Essential Commands:**
|
|
97
|
+
- \`/fullstack "goal"\` - Launch full-stack development team with consensus validation
|
|
94
98
|
- \`/swarm init mesh 8\` - Initialize agent swarm
|
|
95
99
|
- \`/hooks enable\` - Enable automation hooks
|
|
96
100
|
- \`/sparc spec "task"\` - Run SPARC methodology
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### ✨ Added
|
|
11
|
+
- **`/fullstack` Slash Command**: Launch coordinated full-stack development team with consensus validation
|
|
12
|
+
- Automatic team composition (researcher, coder, tester, reviewer, architect)
|
|
13
|
+
- Built-in consensus validation with 4-validator Byzantine fault tolerance
|
|
14
|
+
- Iterative improvement with up to 10 rounds and feedback injection
|
|
15
|
+
- Production-ready code quality (≥90% agreement + critical criteria)
|
|
16
|
+
- User can specify custom goals: `/fullstack "Add authentication with JWT"`
|
|
17
|
+
- Command aliases: `/fs`, `/full`
|
|
18
|
+
- Comprehensive documentation in `docs/commands/fullstack.md`
|
|
19
|
+
- Auto-discovered by Claude Code (no manual registration required)
|
|
20
|
+
|
|
8
21
|
## [1.4.1] - 2025-09-29
|
|
9
22
|
|
|
10
23
|
### 🔧 Fixed
|
package/CLAUDE.md
CHANGED
|
@@ -1050,3 +1050,941 @@ class TestAPI:
|
|
|
1050
1050
|
assert response.json()["status"] == "success"
|
|
1051
1051
|
```
|
|
1052
1052
|
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
## Custom Configuration
|
|
1056
|
+
|
|
1057
|
+
<!-- Preserved from existing CLAUDE.md -->
|
|
1058
|
+
hooks/ # Custom hooks (if React)
|
|
1059
|
+
types/ # Type definitions (if TypeScript)
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
**Testing Patterns:**
|
|
1063
|
+
```javascript
|
|
1064
|
+
import { jest } from '@jest/globals';
|
|
1065
|
+
|
|
1066
|
+
describe('Module Tests', () => {
|
|
1067
|
+
beforeEach(() => {
|
|
1068
|
+
jest.clearAllMocks();
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
test('handles async operations correctly', async () => {
|
|
1072
|
+
const mockFn = jest.fn().mockResolvedValue({ success: true });
|
|
1073
|
+
const result = await asyncOperation(mockFn);
|
|
1074
|
+
|
|
1075
|
+
expect(result).toEqual({ success: true });
|
|
1076
|
+
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
test('handles errors gracefully', async () => {
|
|
1080
|
+
const mockFn = jest.fn().mockRejectedValue(new Error('Test error'));
|
|
1081
|
+
|
|
1082
|
+
await expect(asyncOperation(mockFn)).rejects.toThrow('Test error');
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
**Performance Best Practices:**
|
|
1088
|
+
- Use code splitting for large applications
|
|
1089
|
+
- Implement lazy loading for routes/components
|
|
1090
|
+
- Minimize bundle size with tree shaking
|
|
1091
|
+
- Use Web Workers for heavy computations
|
|
1092
|
+
- Implement proper caching strategies
|
|
1093
|
+
|
|
1094
|
+
**Error Handling:**
|
|
1095
|
+
```javascript
|
|
1096
|
+
// Global error handler
|
|
1097
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
1098
|
+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
|
1099
|
+
// Application specific logging logic here
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
// Async error wrapper
|
|
1103
|
+
const asyncHandler = (fn) => (req, res, next) => {
|
|
1104
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
1105
|
+
};
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
**Environment Configuration:**
|
|
1109
|
+
```javascript
|
|
1110
|
+
// config/index.js
|
|
1111
|
+
const config = {
|
|
1112
|
+
development: {
|
|
1113
|
+
api: {
|
|
1114
|
+
baseURL: 'http://localhost:3000',
|
|
1115
|
+
timeout: 5000
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
production: {
|
|
1119
|
+
api: {
|
|
1120
|
+
baseURL: process.env.API_BASE_URL,
|
|
1121
|
+
timeout: 10000
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
export default config[process.env.NODE_ENV || 'development'];
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
hooks/ # Custom hooks (if React)
|
|
1131
|
+
useApi.ts # Generic API hook
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
hooks/ # Custom hooks
|
|
1136
|
+
useApi.js
|
|
1137
|
+
useLocalStorage.js
|
|
1138
|
+
useAuth.js
|
|
1139
|
+
context/ # React context providers
|
|
1140
|
+
AuthContext.jsx
|
|
1141
|
+
ThemeContext.jsx
|
|
1142
|
+
services/ # API services
|
|
1143
|
+
api.js
|
|
1144
|
+
auth.js
|
|
1145
|
+
utils/ # Helper functions
|
|
1146
|
+
validators.js
|
|
1147
|
+
formatters.js
|
|
1148
|
+
styles/ # Global styles
|
|
1149
|
+
globals.css
|
|
1150
|
+
variables.css
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
**Component Patterns:**
|
|
1154
|
+
```jsx
|
|
1155
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
1156
|
+
import PropTypes from 'prop-types';
|
|
1157
|
+
|
|
1158
|
+
// Functional component with hooks
|
|
1159
|
+
const UserProfile = ({ userId, onUpdate }) => {
|
|
1160
|
+
const [user, setUser] = useState(null);
|
|
1161
|
+
const [loading, setLoading] = useState(true);
|
|
1162
|
+
const [error, setError] = useState(null);
|
|
1163
|
+
|
|
1164
|
+
// Memoized expensive calculations
|
|
1165
|
+
const userStats = useMemo(() => {
|
|
1166
|
+
if (!user) return null;
|
|
1167
|
+
return {
|
|
1168
|
+
totalPosts: user.posts?.length || 0,
|
|
1169
|
+
joinedDate: new Date(user.createdAt).toLocaleDateString()
|
|
1170
|
+
};
|
|
1171
|
+
}, [user]);
|
|
1172
|
+
|
|
1173
|
+
// Memoized callbacks to prevent unnecessary re-renders
|
|
1174
|
+
const handleUpdateUser = useCallback(async (updates) => {
|
|
1175
|
+
try {
|
|
1176
|
+
const updatedUser = await updateUser(userId, updates);
|
|
1177
|
+
setUser(updatedUser);
|
|
1178
|
+
onUpdate?.(updatedUser);
|
|
1179
|
+
} catch (err) {
|
|
1180
|
+
setError(err.message);
|
|
1181
|
+
}
|
|
1182
|
+
}, [userId, onUpdate]);
|
|
1183
|
+
|
|
1184
|
+
useEffect(() => {
|
|
1185
|
+
let cancelled = false;
|
|
1186
|
+
|
|
1187
|
+
const fetchUser = async () => {
|
|
1188
|
+
try {
|
|
1189
|
+
setLoading(true);
|
|
1190
|
+
const userData = await getUserById(userId);
|
|
1191
|
+
if (!cancelled) {
|
|
1192
|
+
setUser(userData);
|
|
1193
|
+
}
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
if (!cancelled) {
|
|
1196
|
+
setError(err.message);
|
|
1197
|
+
}
|
|
1198
|
+
} finally {
|
|
1199
|
+
if (!cancelled) {
|
|
1200
|
+
setLoading(false);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
fetchUser();
|
|
1206
|
+
|
|
1207
|
+
return () => {
|
|
1208
|
+
cancelled = true;
|
|
1209
|
+
};
|
|
1210
|
+
}, [userId]);
|
|
1211
|
+
|
|
1212
|
+
if (loading) return <LoadingSpinner />;
|
|
1213
|
+
if (error) return <ErrorMessage message={error} />;
|
|
1214
|
+
if (!user) return <NotFound />;
|
|
1215
|
+
|
|
1216
|
+
return (
|
|
1217
|
+
<div className="user-profile">
|
|
1218
|
+
<ProfileHeader user={user} stats={userStats} />
|
|
1219
|
+
<ProfileContent user={user} onUpdate={handleUpdateUser} />
|
|
1220
|
+
</div>
|
|
1221
|
+
);
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
UserProfile.propTypes = {
|
|
1225
|
+
userId: PropTypes.string.isRequired,
|
|
1226
|
+
onUpdate: PropTypes.func
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
export default React.memo(UserProfile);
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
**Custom Hooks Pattern:**
|
|
1233
|
+
```jsx
|
|
1234
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
1235
|
+
|
|
1236
|
+
// Generic API hook
|
|
1237
|
+
export const useApi = (url, options = {}) => {
|
|
1238
|
+
const [data, setData] = useState(null);
|
|
1239
|
+
const [loading, setLoading] = useState(true);
|
|
1240
|
+
const [error, setError] = useState(null);
|
|
1241
|
+
|
|
1242
|
+
const fetchData = useCallback(async () => {
|
|
1243
|
+
try {
|
|
1244
|
+
setLoading(true);
|
|
1245
|
+
setError(null);
|
|
1246
|
+
const response = await fetch(url, options);
|
|
1247
|
+
|
|
1248
|
+
if (!response.ok) {
|
|
1249
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const result = await response.json();
|
|
1253
|
+
setData(result);
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
setError(err.message);
|
|
1256
|
+
} finally {
|
|
1257
|
+
setLoading(false);
|
|
1258
|
+
}
|
|
1259
|
+
}, [url, options]);
|
|
1260
|
+
|
|
1261
|
+
useEffect(() => {
|
|
1262
|
+
fetchData();
|
|
1263
|
+
}, [fetchData]);
|
|
1264
|
+
|
|
1265
|
+
const refetch = useCallback(() => {
|
|
1266
|
+
fetchData();
|
|
1267
|
+
}, [fetchData]);
|
|
1268
|
+
|
|
1269
|
+
return { data, loading, error, refetch };
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// Local storage hook
|
|
1273
|
+
export const useLocalStorage = (key, initialValue) => {
|
|
1274
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
1275
|
+
try {
|
|
1276
|
+
const item = window.localStorage.getItem(key);
|
|
1277
|
+
return item ? JSON.parse(item) : initialValue;
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
1280
|
+
return initialValue;
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
const setValue = useCallback((value) => {
|
|
1285
|
+
try {
|
|
1286
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
1287
|
+
setStoredValue(valueToStore);
|
|
1288
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
1291
|
+
}
|
|
1292
|
+
}, [key, storedValue]);
|
|
1293
|
+
|
|
1294
|
+
return [storedValue, setValue];
|
|
1295
|
+
};
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
**Context Pattern:**
|
|
1299
|
+
```jsx
|
|
1300
|
+
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
|
1301
|
+
|
|
1302
|
+
// State and actions
|
|
1303
|
+
const initialState = {
|
|
1304
|
+
user: null,
|
|
1305
|
+
isAuthenticated: false,
|
|
1306
|
+
loading: false,
|
|
1307
|
+
error: null
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const authReducer = (state, action) => {
|
|
1311
|
+
switch (action.type) {
|
|
1312
|
+
case 'LOGIN_START':
|
|
1313
|
+
return { ...state, loading: true, error: null };
|
|
1314
|
+
case 'LOGIN_SUCCESS':
|
|
1315
|
+
return {
|
|
1316
|
+
...state,
|
|
1317
|
+
user: action.payload,
|
|
1318
|
+
isAuthenticated: true,
|
|
1319
|
+
loading: false,
|
|
1320
|
+
error: null
|
|
1321
|
+
};
|
|
1322
|
+
case 'LOGIN_FAILURE':
|
|
1323
|
+
return {
|
|
1324
|
+
...state,
|
|
1325
|
+
user: null,
|
|
1326
|
+
isAuthenticated: false,
|
|
1327
|
+
loading: false,
|
|
1328
|
+
error: action.payload
|
|
1329
|
+
};
|
|
1330
|
+
case 'LOGOUT':
|
|
1331
|
+
return initialState;
|
|
1332
|
+
default:
|
|
1333
|
+
return state;
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
// Context creation
|
|
1338
|
+
const AuthContext = createContext();
|
|
1339
|
+
|
|
1340
|
+
// Provider component
|
|
1341
|
+
export const AuthProvider = ({ children }) => {
|
|
1342
|
+
const [state, dispatch] = useReducer(authReducer, initialState);
|
|
1343
|
+
|
|
1344
|
+
const login = useCallback(async (credentials) => {
|
|
1345
|
+
dispatch({ type: 'LOGIN_START' });
|
|
1346
|
+
try {
|
|
1347
|
+
const user = await authService.login(credentials);
|
|
1348
|
+
dispatch({ type: 'LOGIN_SUCCESS', payload: user });
|
|
1349
|
+
return user;
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
dispatch({ type: 'LOGIN_FAILURE', payload: error.message });
|
|
1352
|
+
throw error;
|
|
1353
|
+
}
|
|
1354
|
+
}, []);
|
|
1355
|
+
|
|
1356
|
+
const logout = useCallback(() => {
|
|
1357
|
+
authService.logout();
|
|
1358
|
+
dispatch({ type: 'LOGOUT' });
|
|
1359
|
+
}, []);
|
|
1360
|
+
|
|
1361
|
+
const value = {
|
|
1362
|
+
...state,
|
|
1363
|
+
login,
|
|
1364
|
+
logout
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
return (
|
|
1368
|
+
<AuthContext.Provider value={value}>
|
|
1369
|
+
{children}
|
|
1370
|
+
</AuthContext.Provider>
|
|
1371
|
+
);
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
// Custom hook for using auth context
|
|
1375
|
+
export const useAuth = () => {
|
|
1376
|
+
const context = useContext(AuthContext);
|
|
1377
|
+
if (!context) {
|
|
1378
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
1379
|
+
}
|
|
1380
|
+
return context;
|
|
1381
|
+
};
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
**Testing with React Testing Library:**
|
|
1385
|
+
```jsx
|
|
1386
|
+
import React from 'react';
|
|
1387
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
1388
|
+
import userEvent from '@testing-library/user-event';
|
|
1389
|
+
import { jest } from '@jest/globals';
|
|
1390
|
+
import UserProfile from '../UserProfile';
|
|
1391
|
+
import * as api from '../../services/api';
|
|
1392
|
+
|
|
1393
|
+
// Mock the API module
|
|
1394
|
+
jest.mock('../../services/api');
|
|
1395
|
+
|
|
1396
|
+
describe('UserProfile', () => {
|
|
1397
|
+
const mockUser = {
|
|
1398
|
+
id: '123',
|
|
1399
|
+
name: 'John Doe',
|
|
1400
|
+
email: 'john@example.com',
|
|
1401
|
+
posts: [{ id: 1, title: 'Test Post' }]
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
beforeEach(() => {
|
|
1405
|
+
jest.clearAllMocks();
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
test('renders user profile after loading', async () => {
|
|
1409
|
+
api.getUserById.mockResolvedValue(mockUser);
|
|
1410
|
+
|
|
1411
|
+
render(<UserProfile userId="123" />);
|
|
1412
|
+
|
|
1413
|
+
// Check loading state
|
|
1414
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
1415
|
+
|
|
1416
|
+
// Wait for user data to load
|
|
1417
|
+
await waitFor(() => {
|
|
1418
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
1422
|
+
expect(screen.getByText('1 Posts')).toBeInTheDocument();
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
test('handles update user interaction', async () => {
|
|
1426
|
+
const user = userEvent.setup();
|
|
1427
|
+
const onUpdate = jest.fn();
|
|
1428
|
+
|
|
1429
|
+
api.getUserById.mockResolvedValue(mockUser);
|
|
1430
|
+
api.updateUser.mockResolvedValue({ ...mockUser, name: 'Jane Doe' });
|
|
1431
|
+
|
|
1432
|
+
render(<UserProfile userId="123" onUpdate={onUpdate} />);
|
|
1433
|
+
|
|
1434
|
+
await waitFor(() => {
|
|
1435
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
const editButton = screen.getByRole('button', { name: /edit/i });
|
|
1439
|
+
await user.click(editButton);
|
|
1440
|
+
|
|
1441
|
+
const nameInput = screen.getByDisplayValue('John Doe');
|
|
1442
|
+
await user.clear(nameInput);
|
|
1443
|
+
await user.type(nameInput, 'Jane Doe');
|
|
1444
|
+
|
|
1445
|
+
const saveButton = screen.getByRole('button', { name: /save/i });
|
|
1446
|
+
await user.click(saveButton);
|
|
1447
|
+
|
|
1448
|
+
await waitFor(() => {
|
|
1449
|
+
expect(onUpdate).toHaveBeenCalledWith({ ...mockUser, name: 'Jane Doe' });
|
|
1450
|
+
});
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
test('displays error message on API failure', async () => {
|
|
1454
|
+
api.getUserById.mockRejectedValue(new Error('Network error'));
|
|
1455
|
+
|
|
1456
|
+
render(<UserProfile userId="123" />);
|
|
1457
|
+
|
|
1458
|
+
await waitFor(() => {
|
|
1459
|
+
expect(screen.getByText(/network error/i)).toBeInTheDocument();
|
|
1460
|
+
});
|
|
1461
|
+
});
|
|
1462
|
+
});
|
|
1463
|
+
```
|
|
1464
|
+
|
|
1465
|
+
**Performance Optimization:**
|
|
1466
|
+
```jsx
|
|
1467
|
+
import React, { memo, lazy, Suspense } from 'react';
|
|
1468
|
+
|
|
1469
|
+
// Lazy loading for code splitting
|
|
1470
|
+
const LazyUserProfile = lazy(() => import('./UserProfile'));
|
|
1471
|
+
const LazyUserSettings = lazy(() => import('./UserSettings'));
|
|
1472
|
+
|
|
1473
|
+
// Memoized component to prevent unnecessary re-renders
|
|
1474
|
+
const UserCard = memo(({ user, onUpdate }) => {
|
|
1475
|
+
return (
|
|
1476
|
+
<div className="user-card">
|
|
1477
|
+
<h3>{user.name}</h3>
|
|
1478
|
+
<p>{user.email}</p>
|
|
1479
|
+
<button onClick={() => onUpdate(user.id)}>
|
|
1480
|
+
Update
|
|
1481
|
+
</button>
|
|
1482
|
+
</div>
|
|
1483
|
+
);
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
// Main app with lazy loading
|
|
1487
|
+
const App = () => {
|
|
1488
|
+
return (
|
|
1489
|
+
<div className="app">
|
|
1490
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
1491
|
+
<Routes>
|
|
1492
|
+
<Route path="/profile" element={<LazyUserProfile />} />
|
|
1493
|
+
<Route path="/settings" element={<LazyUserSettings />} />
|
|
1494
|
+
</Routes>
|
|
1495
|
+
</Suspense>
|
|
1496
|
+
</div>
|
|
1497
|
+
);
|
|
1498
|
+
};
|
|
1499
|
+
```
|
|
1500
|
+
|
|
1501
|
+
**State Management with Zustand:**
|
|
1502
|
+
```jsx
|
|
1503
|
+
import { create } from 'zustand';
|
|
1504
|
+
import { devtools, persist } from 'zustand/middleware';
|
|
1505
|
+
|
|
1506
|
+
const useUserStore = create(
|
|
1507
|
+
devtools(
|
|
1508
|
+
persist(
|
|
1509
|
+
(set, get) => ({
|
|
1510
|
+
users: [],
|
|
1511
|
+
currentUser: null,
|
|
1512
|
+
loading: false,
|
|
1513
|
+
|
|
1514
|
+
fetchUsers: async () => {
|
|
1515
|
+
set({ loading: true });
|
|
1516
|
+
try {
|
|
1517
|
+
const users = await api.getUsers();
|
|
1518
|
+
set({ users, loading: false });
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
set({ loading: false });
|
|
1521
|
+
throw error;
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
|
|
1525
|
+
setCurrentUser: (user) => set({ currentUser: user }),
|
|
1526
|
+
|
|
1527
|
+
updateUser: async (userId, updates) => {
|
|
1528
|
+
const updatedUser = await api.updateUser(userId, updates);
|
|
1529
|
+
set((state) => ({
|
|
1530
|
+
users: state.users.map((user) =>
|
|
1531
|
+
user.id === userId ? updatedUser : user
|
|
1532
|
+
),
|
|
1533
|
+
currentUser: state.currentUser?.id === userId ? updatedUser : state.currentUser
|
|
1534
|
+
}));
|
|
1535
|
+
return updatedUser;
|
|
1536
|
+
}
|
|
1537
|
+
}),
|
|
1538
|
+
{ name: 'user-store' }
|
|
1539
|
+
)
|
|
1540
|
+
)
|
|
1541
|
+
);
|
|
1542
|
+
|
|
1543
|
+
// Usage in component
|
|
1544
|
+
const UserList = () => {
|
|
1545
|
+
const { users, loading, fetchUsers } = useUserStore();
|
|
1546
|
+
|
|
1547
|
+
useEffect(() => {
|
|
1548
|
+
fetchUsers();
|
|
1549
|
+
}, [fetchUsers]);
|
|
1550
|
+
|
|
1551
|
+
if (loading) return <LoadingSpinner />;
|
|
1552
|
+
|
|
1553
|
+
return (
|
|
1554
|
+
<div>
|
|
1555
|
+
{users.map((user) => (
|
|
1556
|
+
<UserCard key={user.id} user={user} />
|
|
1557
|
+
))}
|
|
1558
|
+
</div>
|
|
1559
|
+
);
|
|
1560
|
+
};
|
|
1561
|
+
```
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
hooks/ # Custom hooks
|
|
1565
|
+
useApi.js
|
|
1566
|
+
useLocalStorage.js
|
|
1567
|
+
useAuth.js
|
|
1568
|
+
context/ # React context providers
|
|
1569
|
+
AuthContext.jsx
|
|
1570
|
+
ThemeContext.jsx
|
|
1571
|
+
services/ # API services
|
|
1572
|
+
api.js
|
|
1573
|
+
auth.js
|
|
1574
|
+
utils/ # Helper functions
|
|
1575
|
+
validators.js
|
|
1576
|
+
formatters.js
|
|
1577
|
+
styles/ # Global styles
|
|
1578
|
+
globals.css
|
|
1579
|
+
variables.css
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
**Component Patterns:**
|
|
1583
|
+
```jsx
|
|
1584
|
+
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
1585
|
+
import PropTypes from 'prop-types';
|
|
1586
|
+
|
|
1587
|
+
// Functional component with hooks
|
|
1588
|
+
const UserProfile = ({ userId, onUpdate }) => {
|
|
1589
|
+
const [user, setUser] = useState(null);
|
|
1590
|
+
const [loading, setLoading] = useState(true);
|
|
1591
|
+
const [error, setError] = useState(null);
|
|
1592
|
+
|
|
1593
|
+
// Memoized expensive calculations
|
|
1594
|
+
const userStats = useMemo(() => {
|
|
1595
|
+
if (!user) return null;
|
|
1596
|
+
return {
|
|
1597
|
+
totalPosts: user.posts?.length || 0,
|
|
1598
|
+
joinedDate: new Date(user.createdAt).toLocaleDateString()
|
|
1599
|
+
};
|
|
1600
|
+
}, [user]);
|
|
1601
|
+
|
|
1602
|
+
// Memoized callbacks to prevent unnecessary re-renders
|
|
1603
|
+
const handleUpdateUser = useCallback(async (updates) => {
|
|
1604
|
+
try {
|
|
1605
|
+
const updatedUser = await updateUser(userId, updates);
|
|
1606
|
+
setUser(updatedUser);
|
|
1607
|
+
onUpdate?.(updatedUser);
|
|
1608
|
+
} catch (err) {
|
|
1609
|
+
setError(err.message);
|
|
1610
|
+
}
|
|
1611
|
+
}, [userId, onUpdate]);
|
|
1612
|
+
|
|
1613
|
+
useEffect(() => {
|
|
1614
|
+
let cancelled = false;
|
|
1615
|
+
|
|
1616
|
+
const fetchUser = async () => {
|
|
1617
|
+
try {
|
|
1618
|
+
setLoading(true);
|
|
1619
|
+
const userData = await getUserById(userId);
|
|
1620
|
+
if (!cancelled) {
|
|
1621
|
+
setUser(userData);
|
|
1622
|
+
}
|
|
1623
|
+
} catch (err) {
|
|
1624
|
+
if (!cancelled) {
|
|
1625
|
+
setError(err.message);
|
|
1626
|
+
}
|
|
1627
|
+
} finally {
|
|
1628
|
+
if (!cancelled) {
|
|
1629
|
+
setLoading(false);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
fetchUser();
|
|
1635
|
+
|
|
1636
|
+
return () => {
|
|
1637
|
+
cancelled = true;
|
|
1638
|
+
};
|
|
1639
|
+
}, [userId]);
|
|
1640
|
+
|
|
1641
|
+
if (loading) return <LoadingSpinner />;
|
|
1642
|
+
if (error) return <ErrorMessage message={error} />;
|
|
1643
|
+
if (!user) return <NotFound />;
|
|
1644
|
+
|
|
1645
|
+
return (
|
|
1646
|
+
<div className="user-profile">
|
|
1647
|
+
<ProfileHeader user={user} stats={userStats} />
|
|
1648
|
+
<ProfileContent user={user} onUpdate={handleUpdateUser} />
|
|
1649
|
+
</div>
|
|
1650
|
+
);
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
UserProfile.propTypes = {
|
|
1654
|
+
userId: PropTypes.string.isRequired,
|
|
1655
|
+
onUpdate: PropTypes.func
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
export default React.memo(UserProfile);
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
**Custom Hooks Pattern:**
|
|
1662
|
+
```jsx
|
|
1663
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
1664
|
+
|
|
1665
|
+
// Generic API hook
|
|
1666
|
+
export const useApi = (url, options = {}) => {
|
|
1667
|
+
const [data, setData] = useState(null);
|
|
1668
|
+
const [loading, setLoading] = useState(true);
|
|
1669
|
+
const [error, setError] = useState(null);
|
|
1670
|
+
|
|
1671
|
+
const fetchData = useCallback(async () => {
|
|
1672
|
+
try {
|
|
1673
|
+
setLoading(true);
|
|
1674
|
+
setError(null);
|
|
1675
|
+
const response = await fetch(url, options);
|
|
1676
|
+
|
|
1677
|
+
if (!response.ok) {
|
|
1678
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
const result = await response.json();
|
|
1682
|
+
setData(result);
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
setError(err.message);
|
|
1685
|
+
} finally {
|
|
1686
|
+
setLoading(false);
|
|
1687
|
+
}
|
|
1688
|
+
}, [url, options]);
|
|
1689
|
+
|
|
1690
|
+
useEffect(() => {
|
|
1691
|
+
fetchData();
|
|
1692
|
+
}, [fetchData]);
|
|
1693
|
+
|
|
1694
|
+
const refetch = useCallback(() => {
|
|
1695
|
+
fetchData();
|
|
1696
|
+
}, [fetchData]);
|
|
1697
|
+
|
|
1698
|
+
return { data, loading, error, refetch };
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
// Local storage hook
|
|
1702
|
+
export const useLocalStorage = (key, initialValue) => {
|
|
1703
|
+
const [storedValue, setStoredValue] = useState(() => {
|
|
1704
|
+
try {
|
|
1705
|
+
const item = window.localStorage.getItem(key);
|
|
1706
|
+
return item ? JSON.parse(item) : initialValue;
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
1709
|
+
return initialValue;
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
const setValue = useCallback((value) => {
|
|
1714
|
+
try {
|
|
1715
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
1716
|
+
setStoredValue(valueToStore);
|
|
1717
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
1718
|
+
} catch (error) {
|
|
1719
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
1720
|
+
}
|
|
1721
|
+
}, [key, storedValue]);
|
|
1722
|
+
|
|
1723
|
+
return [storedValue, setValue];
|
|
1724
|
+
};
|
|
1725
|
+
```
|
|
1726
|
+
|
|
1727
|
+
**Context Pattern:**
|
|
1728
|
+
```jsx
|
|
1729
|
+
import React, { createContext, useContext, useReducer, useCallback } from 'react';
|
|
1730
|
+
|
|
1731
|
+
// State and actions
|
|
1732
|
+
const initialState = {
|
|
1733
|
+
user: null,
|
|
1734
|
+
isAuthenticated: false,
|
|
1735
|
+
loading: false,
|
|
1736
|
+
error: null
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
const authReducer = (state, action) => {
|
|
1740
|
+
switch (action.type) {
|
|
1741
|
+
case 'LOGIN_START':
|
|
1742
|
+
return { ...state, loading: true, error: null };
|
|
1743
|
+
case 'LOGIN_SUCCESS':
|
|
1744
|
+
return {
|
|
1745
|
+
...state,
|
|
1746
|
+
user: action.payload,
|
|
1747
|
+
isAuthenticated: true,
|
|
1748
|
+
loading: false,
|
|
1749
|
+
error: null
|
|
1750
|
+
};
|
|
1751
|
+
case 'LOGIN_FAILURE':
|
|
1752
|
+
return {
|
|
1753
|
+
...state,
|
|
1754
|
+
user: null,
|
|
1755
|
+
isAuthenticated: false,
|
|
1756
|
+
loading: false,
|
|
1757
|
+
error: action.payload
|
|
1758
|
+
};
|
|
1759
|
+
case 'LOGOUT':
|
|
1760
|
+
return initialState;
|
|
1761
|
+
default:
|
|
1762
|
+
return state;
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
// Context creation
|
|
1767
|
+
const AuthContext = createContext();
|
|
1768
|
+
|
|
1769
|
+
// Provider component
|
|
1770
|
+
export const AuthProvider = ({ children }) => {
|
|
1771
|
+
const [state, dispatch] = useReducer(authReducer, initialState);
|
|
1772
|
+
|
|
1773
|
+
const login = useCallback(async (credentials) => {
|
|
1774
|
+
dispatch({ type: 'LOGIN_START' });
|
|
1775
|
+
try {
|
|
1776
|
+
const user = await authService.login(credentials);
|
|
1777
|
+
dispatch({ type: 'LOGIN_SUCCESS', payload: user });
|
|
1778
|
+
return user;
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
dispatch({ type: 'LOGIN_FAILURE', payload: error.message });
|
|
1781
|
+
throw error;
|
|
1782
|
+
}
|
|
1783
|
+
}, []);
|
|
1784
|
+
|
|
1785
|
+
const logout = useCallback(() => {
|
|
1786
|
+
authService.logout();
|
|
1787
|
+
dispatch({ type: 'LOGOUT' });
|
|
1788
|
+
}, []);
|
|
1789
|
+
|
|
1790
|
+
const value = {
|
|
1791
|
+
...state,
|
|
1792
|
+
login,
|
|
1793
|
+
logout
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
return (
|
|
1797
|
+
<AuthContext.Provider value={value}>
|
|
1798
|
+
{children}
|
|
1799
|
+
</AuthContext.Provider>
|
|
1800
|
+
);
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
// Custom hook for using auth context
|
|
1804
|
+
export const useAuth = () => {
|
|
1805
|
+
const context = useContext(AuthContext);
|
|
1806
|
+
if (!context) {
|
|
1807
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
1808
|
+
}
|
|
1809
|
+
return context;
|
|
1810
|
+
};
|
|
1811
|
+
```
|
|
1812
|
+
|
|
1813
|
+
**Testing with React Testing Library:**
|
|
1814
|
+
```jsx
|
|
1815
|
+
import React from 'react';
|
|
1816
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
1817
|
+
import userEvent from '@testing-library/user-event';
|
|
1818
|
+
import { jest } from '@jest/globals';
|
|
1819
|
+
import UserProfile from '../UserProfile';
|
|
1820
|
+
import * as api from '../../services/api';
|
|
1821
|
+
|
|
1822
|
+
// Mock the API module
|
|
1823
|
+
jest.mock('../../services/api');
|
|
1824
|
+
|
|
1825
|
+
describe('UserProfile', () => {
|
|
1826
|
+
const mockUser = {
|
|
1827
|
+
id: '123',
|
|
1828
|
+
name: 'John Doe',
|
|
1829
|
+
email: 'john@example.com',
|
|
1830
|
+
posts: [{ id: 1, title: 'Test Post' }]
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
beforeEach(() => {
|
|
1834
|
+
jest.clearAllMocks();
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
test('renders user profile after loading', async () => {
|
|
1838
|
+
api.getUserById.mockResolvedValue(mockUser);
|
|
1839
|
+
|
|
1840
|
+
render(<UserProfile userId="123" />);
|
|
1841
|
+
|
|
1842
|
+
// Check loading state
|
|
1843
|
+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
|
|
1844
|
+
|
|
1845
|
+
// Wait for user data to load
|
|
1846
|
+
await waitFor(() => {
|
|
1847
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
1851
|
+
expect(screen.getByText('1 Posts')).toBeInTheDocument();
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
test('handles update user interaction', async () => {
|
|
1855
|
+
const user = userEvent.setup();
|
|
1856
|
+
const onUpdate = jest.fn();
|
|
1857
|
+
|
|
1858
|
+
api.getUserById.mockResolvedValue(mockUser);
|
|
1859
|
+
api.updateUser.mockResolvedValue({ ...mockUser, name: 'Jane Doe' });
|
|
1860
|
+
|
|
1861
|
+
render(<UserProfile userId="123" onUpdate={onUpdate} />);
|
|
1862
|
+
|
|
1863
|
+
await waitFor(() => {
|
|
1864
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
1865
|
+
});
|
|
1866
|
+
|
|
1867
|
+
const editButton = screen.getByRole('button', { name: /edit/i });
|
|
1868
|
+
await user.click(editButton);
|
|
1869
|
+
|
|
1870
|
+
const nameInput = screen.getByDisplayValue('John Doe');
|
|
1871
|
+
await user.clear(nameInput);
|
|
1872
|
+
await user.type(nameInput, 'Jane Doe');
|
|
1873
|
+
|
|
1874
|
+
const saveButton = screen.getByRole('button', { name: /save/i });
|
|
1875
|
+
await user.click(saveButton);
|
|
1876
|
+
|
|
1877
|
+
await waitFor(() => {
|
|
1878
|
+
expect(onUpdate).toHaveBeenCalledWith({ ...mockUser, name: 'Jane Doe' });
|
|
1879
|
+
});
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
test('displays error message on API failure', async () => {
|
|
1883
|
+
api.getUserById.mockRejectedValue(new Error('Network error'));
|
|
1884
|
+
|
|
1885
|
+
render(<UserProfile userId="123" />);
|
|
1886
|
+
|
|
1887
|
+
await waitFor(() => {
|
|
1888
|
+
expect(screen.getByText(/network error/i)).toBeInTheDocument();
|
|
1889
|
+
});
|
|
1890
|
+
});
|
|
1891
|
+
});
|
|
1892
|
+
```
|
|
1893
|
+
|
|
1894
|
+
**Performance Optimization:**
|
|
1895
|
+
```jsx
|
|
1896
|
+
import React, { memo, lazy, Suspense } from 'react';
|
|
1897
|
+
|
|
1898
|
+
// Lazy loading for code splitting
|
|
1899
|
+
const LazyUserProfile = lazy(() => import('./UserProfile'));
|
|
1900
|
+
const LazyUserSettings = lazy(() => import('./UserSettings'));
|
|
1901
|
+
|
|
1902
|
+
// Memoized component to prevent unnecessary re-renders
|
|
1903
|
+
const UserCard = memo(({ user, onUpdate }) => {
|
|
1904
|
+
return (
|
|
1905
|
+
<div className="user-card">
|
|
1906
|
+
<h3>{user.name}</h3>
|
|
1907
|
+
<p>{user.email}</p>
|
|
1908
|
+
<button onClick={() => onUpdate(user.id)}>
|
|
1909
|
+
Update
|
|
1910
|
+
</button>
|
|
1911
|
+
</div>
|
|
1912
|
+
);
|
|
1913
|
+
});
|
|
1914
|
+
|
|
1915
|
+
// Main app with lazy loading
|
|
1916
|
+
const App = () => {
|
|
1917
|
+
return (
|
|
1918
|
+
<div className="app">
|
|
1919
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
1920
|
+
<Routes>
|
|
1921
|
+
<Route path="/profile" element={<LazyUserProfile />} />
|
|
1922
|
+
<Route path="/settings" element={<LazyUserSettings />} />
|
|
1923
|
+
</Routes>
|
|
1924
|
+
</Suspense>
|
|
1925
|
+
</div>
|
|
1926
|
+
);
|
|
1927
|
+
};
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
**State Management with Zustand:**
|
|
1931
|
+
```jsx
|
|
1932
|
+
import { create } from 'zustand';
|
|
1933
|
+
import { devtools, persist } from 'zustand/middleware';
|
|
1934
|
+
|
|
1935
|
+
const useUserStore = create(
|
|
1936
|
+
devtools(
|
|
1937
|
+
persist(
|
|
1938
|
+
(set, get) => ({
|
|
1939
|
+
users: [],
|
|
1940
|
+
currentUser: null,
|
|
1941
|
+
loading: false,
|
|
1942
|
+
|
|
1943
|
+
fetchUsers: async () => {
|
|
1944
|
+
set({ loading: true });
|
|
1945
|
+
try {
|
|
1946
|
+
const users = await api.getUsers();
|
|
1947
|
+
set({ users, loading: false });
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
set({ loading: false });
|
|
1950
|
+
throw error;
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1954
|
+
setCurrentUser: (user) => set({ currentUser: user }),
|
|
1955
|
+
|
|
1956
|
+
updateUser: async (userId, updates) => {
|
|
1957
|
+
const updatedUser = await api.updateUser(userId, updates);
|
|
1958
|
+
set((state) => ({
|
|
1959
|
+
users: state.users.map((user) =>
|
|
1960
|
+
user.id === userId ? updatedUser : user
|
|
1961
|
+
),
|
|
1962
|
+
currentUser: state.currentUser?.id === userId ? updatedUser : state.currentUser
|
|
1963
|
+
}));
|
|
1964
|
+
return updatedUser;
|
|
1965
|
+
}
|
|
1966
|
+
}),
|
|
1967
|
+
{ name: 'user-store' }
|
|
1968
|
+
)
|
|
1969
|
+
)
|
|
1970
|
+
);
|
|
1971
|
+
|
|
1972
|
+
// Usage in component
|
|
1973
|
+
const UserList = () => {
|
|
1974
|
+
const { users, loading, fetchUsers } = useUserStore();
|
|
1975
|
+
|
|
1976
|
+
useEffect(() => {
|
|
1977
|
+
fetchUsers();
|
|
1978
|
+
}, [fetchUsers]);
|
|
1979
|
+
|
|
1980
|
+
if (loading) return <LoadingSpinner />;
|
|
1981
|
+
|
|
1982
|
+
return (
|
|
1983
|
+
<div>
|
|
1984
|
+
{users.map((user) => (
|
|
1985
|
+
<UserCard key={user.id} user={user} />
|
|
1986
|
+
))}
|
|
1987
|
+
</div>
|
|
1988
|
+
);
|
|
1989
|
+
};
|
|
1990
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow-novice",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Standalone Claude Flow for beginners - AI agent orchestration made easy with enhanced TDD testing pipeline. Enhanced init command creates complete agent system, MCP configuration with 30 essential tools, and automated hooks with single-file testing, real-time coverage analysis, and advanced validation. Fully standalone with zero external dependencies, complete project setup in one command.",
|
|
5
5
|
"mcpName": "io.github.ruvnet/claude-flow",
|
|
6
6
|
"main": ".claude-flow-novice/dist/index.js",
|
|
@@ -174,7 +174,8 @@ claude mcp add claude-flow-novice npx claude-flow-novice mcp start
|
|
|
174
174
|
## Essential Commands
|
|
175
175
|
- `npx claude-flow-novice status` - System health
|
|
176
176
|
- `npx claude-flow-novice --help` - Available commands
|
|
177
|
-
- `/
|
|
177
|
+
- `/fullstack "goal"` - Launch full-stack development team with consensus validation
|
|
178
|
+
- `/swarm`, `/sparc`, `/hooks` - Other slash commands (auto-discovered)
|
|
178
179
|
|
|
179
180
|
## DEVELOPMENT FLOW
|
|
180
181
|
1. Execute - Primary swarm (3-8 agents) produces deliverables with confidence score
|