@malamute/ai-rules 1.0.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.
Files changed (46) hide show
  1. package/README.md +174 -0
  2. package/bin/cli.js +5 -0
  3. package/configs/_shared/.claude/commands/fix-issue.md +38 -0
  4. package/configs/_shared/.claude/commands/generate-tests.md +49 -0
  5. package/configs/_shared/.claude/commands/review-pr.md +77 -0
  6. package/configs/_shared/.claude/rules/accessibility.md +270 -0
  7. package/configs/_shared/.claude/rules/performance.md +226 -0
  8. package/configs/_shared/.claude/rules/security.md +188 -0
  9. package/configs/_shared/.claude/skills/debug/SKILL.md +118 -0
  10. package/configs/_shared/.claude/skills/learning/SKILL.md +224 -0
  11. package/configs/_shared/.claude/skills/review/SKILL.md +86 -0
  12. package/configs/_shared/.claude/skills/spec/SKILL.md +112 -0
  13. package/configs/_shared/CLAUDE.md +174 -0
  14. package/configs/angular/.claude/rules/components.md +257 -0
  15. package/configs/angular/.claude/rules/state.md +250 -0
  16. package/configs/angular/.claude/rules/testing.md +422 -0
  17. package/configs/angular/.claude/settings.json +31 -0
  18. package/configs/angular/CLAUDE.md +251 -0
  19. package/configs/dotnet/.claude/rules/api.md +370 -0
  20. package/configs/dotnet/.claude/rules/architecture.md +199 -0
  21. package/configs/dotnet/.claude/rules/database/efcore.md +408 -0
  22. package/configs/dotnet/.claude/rules/testing.md +389 -0
  23. package/configs/dotnet/.claude/settings.json +9 -0
  24. package/configs/dotnet/CLAUDE.md +319 -0
  25. package/configs/nestjs/.claude/rules/auth.md +321 -0
  26. package/configs/nestjs/.claude/rules/database/prisma.md +305 -0
  27. package/configs/nestjs/.claude/rules/database/typeorm.md +379 -0
  28. package/configs/nestjs/.claude/rules/modules.md +215 -0
  29. package/configs/nestjs/.claude/rules/testing.md +315 -0
  30. package/configs/nestjs/.claude/rules/validation.md +279 -0
  31. package/configs/nestjs/.claude/settings.json +15 -0
  32. package/configs/nestjs/CLAUDE.md +263 -0
  33. package/configs/nextjs/.claude/rules/components.md +211 -0
  34. package/configs/nextjs/.claude/rules/state/redux-toolkit.md +429 -0
  35. package/configs/nextjs/.claude/rules/state/zustand.md +299 -0
  36. package/configs/nextjs/.claude/rules/testing.md +315 -0
  37. package/configs/nextjs/.claude/settings.json +29 -0
  38. package/configs/nextjs/CLAUDE.md +376 -0
  39. package/configs/python/.claude/rules/database/sqlalchemy.md +355 -0
  40. package/configs/python/.claude/rules/fastapi.md +272 -0
  41. package/configs/python/.claude/rules/flask.md +332 -0
  42. package/configs/python/.claude/rules/testing.md +374 -0
  43. package/configs/python/.claude/settings.json +18 -0
  44. package/configs/python/CLAUDE.md +273 -0
  45. package/package.json +41 -0
  46. package/src/install.js +315 -0
@@ -0,0 +1,429 @@
1
+ ---
2
+ paths:
3
+ - "**/*slice*.ts"
4
+ - "**/*store*.ts"
5
+ - "**/store/**/*.ts"
6
+ - "**/redux/**/*.ts"
7
+ ---
8
+
9
+ # Redux Toolkit State Management
10
+
11
+ Use Redux Toolkit (RTK) for complex client-side state management.
12
+
13
+ ## When to Use
14
+
15
+ - Large applications with complex state
16
+ - Team already familiar with Redux
17
+ - Need advanced DevTools (time-travel debugging)
18
+ - Using RTK Query for data fetching
19
+ - Complex async logic with middleware
20
+
21
+ ## Store Structure
22
+
23
+ ```
24
+ libs/
25
+ [domain]/
26
+ data-access/
27
+ store/
28
+ index.ts # Store configuration
29
+ hooks.ts # Typed hooks
30
+ slices/
31
+ user-slice.ts
32
+ cart-slice.ts
33
+ api/
34
+ user-api.ts # RTK Query
35
+ ```
36
+
37
+ ## Store Setup
38
+
39
+ ### Configure Store
40
+
41
+ ```typescript
42
+ // store/index.ts
43
+ import { configureStore } from '@reduxjs/toolkit';
44
+ import { userSlice } from '../slices/user-slice';
45
+ import { cartSlice } from '../slices/cart-slice';
46
+ import { apiSlice } from '../api/api-slice';
47
+
48
+ export const makeStore = () => {
49
+ return configureStore({
50
+ reducer: {
51
+ user: userSlice.reducer,
52
+ cart: cartSlice.reducer,
53
+ [apiSlice.reducerPath]: apiSlice.reducer,
54
+ },
55
+ middleware: (getDefaultMiddleware) =>
56
+ getDefaultMiddleware().concat(apiSlice.middleware),
57
+ });
58
+ };
59
+
60
+ export type AppStore = ReturnType<typeof makeStore>;
61
+ export type RootState = ReturnType<AppStore['getState']>;
62
+ export type AppDispatch = AppStore['dispatch'];
63
+ ```
64
+
65
+ ### Typed Hooks
66
+
67
+ ```typescript
68
+ // store/hooks.ts
69
+ import { useDispatch, useSelector, useStore } from 'react-redux';
70
+ import type { AppDispatch, AppStore, RootState } from './index';
71
+
72
+ export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
73
+ export const useAppSelector = useSelector.withTypes<RootState>();
74
+ export const useAppStore = useStore.withTypes<AppStore>();
75
+ ```
76
+
77
+ ### Provider Setup
78
+
79
+ ```tsx
80
+ // app/providers.tsx
81
+ 'use client';
82
+
83
+ import { useRef } from 'react';
84
+ import { Provider } from 'react-redux';
85
+ import { makeStore, AppStore } from '@/store';
86
+
87
+ export function StoreProvider({ children }: { children: React.ReactNode }) {
88
+ const storeRef = useRef<AppStore>();
89
+
90
+ if (!storeRef.current) {
91
+ storeRef.current = makeStore();
92
+ }
93
+
94
+ return <Provider store={storeRef.current}>{children}</Provider>;
95
+ }
96
+ ```
97
+
98
+ ```tsx
99
+ // app/layout.tsx
100
+ import { StoreProvider } from './providers';
101
+
102
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
103
+ return (
104
+ <html>
105
+ <body>
106
+ <StoreProvider>{children}</StoreProvider>
107
+ </body>
108
+ </html>
109
+ );
110
+ }
111
+ ```
112
+
113
+ ## Creating Slices
114
+
115
+ ### Basic Slice
116
+
117
+ ```typescript
118
+ // slices/user-slice.ts
119
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
120
+
121
+ interface User {
122
+ id: string;
123
+ name: string;
124
+ email: string;
125
+ }
126
+
127
+ interface UserState {
128
+ currentUser: User | null;
129
+ isAuthenticated: boolean;
130
+ }
131
+
132
+ const initialState: UserState = {
133
+ currentUser: null,
134
+ isAuthenticated: false,
135
+ };
136
+
137
+ export const userSlice = createSlice({
138
+ name: 'user',
139
+ initialState,
140
+ reducers: {
141
+ setUser: (state, action: PayloadAction<User>) => {
142
+ state.currentUser = action.payload;
143
+ state.isAuthenticated = true;
144
+ },
145
+ logout: (state) => {
146
+ state.currentUser = null;
147
+ state.isAuthenticated = false;
148
+ },
149
+ updateProfile: (state, action: PayloadAction<Partial<User>>) => {
150
+ if (state.currentUser) {
151
+ state.currentUser = { ...state.currentUser, ...action.payload };
152
+ }
153
+ },
154
+ },
155
+ });
156
+
157
+ export const { setUser, logout, updateProfile } = userSlice.actions;
158
+
159
+ // Selectors
160
+ export const selectCurrentUser = (state: RootState) => state.user.currentUser;
161
+ export const selectIsAuthenticated = (state: RootState) => state.user.isAuthenticated;
162
+ ```
163
+
164
+ ### Slice with Async Thunks
165
+
166
+ ```typescript
167
+ // slices/cart-slice.ts
168
+ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
169
+
170
+ interface CartItem {
171
+ id: string;
172
+ name: string;
173
+ price: number;
174
+ quantity: number;
175
+ }
176
+
177
+ interface CartState {
178
+ items: CartItem[];
179
+ isLoading: boolean;
180
+ error: string | null;
181
+ }
182
+
183
+ const initialState: CartState = {
184
+ items: [],
185
+ isLoading: false,
186
+ error: null,
187
+ };
188
+
189
+ // Async thunk
190
+ export const fetchCart = createAsyncThunk(
191
+ 'cart/fetchCart',
192
+ async (userId: string, { rejectWithValue }) => {
193
+ try {
194
+ const response = await fetch(`/api/cart/${userId}`);
195
+ if (!response.ok) throw new Error('Failed to fetch cart');
196
+ return response.json();
197
+ } catch (error) {
198
+ return rejectWithValue('Failed to fetch cart');
199
+ }
200
+ }
201
+ );
202
+
203
+ export const cartSlice = createSlice({
204
+ name: 'cart',
205
+ initialState,
206
+ reducers: {
207
+ addItem: (state, action: PayloadAction<CartItem>) => {
208
+ const existingItem = state.items.find((item) => item.id === action.payload.id);
209
+ if (existingItem) {
210
+ existingItem.quantity += action.payload.quantity;
211
+ } else {
212
+ state.items.push(action.payload);
213
+ }
214
+ },
215
+ removeItem: (state, action: PayloadAction<string>) => {
216
+ state.items = state.items.filter((item) => item.id !== action.payload);
217
+ },
218
+ updateQuantity: (state, action: PayloadAction<{ id: string; quantity: number }>) => {
219
+ const item = state.items.find((item) => item.id === action.payload.id);
220
+ if (item) {
221
+ item.quantity = action.payload.quantity;
222
+ }
223
+ },
224
+ clearCart: (state) => {
225
+ state.items = [];
226
+ },
227
+ },
228
+ extraReducers: (builder) => {
229
+ builder
230
+ .addCase(fetchCart.pending, (state) => {
231
+ state.isLoading = true;
232
+ state.error = null;
233
+ })
234
+ .addCase(fetchCart.fulfilled, (state, action) => {
235
+ state.isLoading = false;
236
+ state.items = action.payload;
237
+ })
238
+ .addCase(fetchCart.rejected, (state, action) => {
239
+ state.isLoading = false;
240
+ state.error = action.payload as string;
241
+ });
242
+ },
243
+ });
244
+
245
+ export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;
246
+
247
+ // Selectors
248
+ export const selectCartItems = (state: RootState) => state.cart.items;
249
+ export const selectCartTotal = (state: RootState) =>
250
+ state.cart.items.reduce((total, item) => total + item.price * item.quantity, 0);
251
+ export const selectCartItemCount = (state: RootState) =>
252
+ state.cart.items.reduce((count, item) => count + item.quantity, 0);
253
+ ```
254
+
255
+ ## RTK Query (Data Fetching)
256
+
257
+ ```typescript
258
+ // api/api-slice.ts
259
+ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
260
+
261
+ export const apiSlice = createApi({
262
+ reducerPath: 'api',
263
+ baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
264
+ tagTypes: ['User', 'Product'],
265
+ endpoints: (builder) => ({
266
+ // Queries
267
+ getUsers: builder.query<User[], void>({
268
+ query: () => '/users',
269
+ providesTags: ['User'],
270
+ }),
271
+ getUser: builder.query<User, string>({
272
+ query: (id) => `/users/${id}`,
273
+ providesTags: (result, error, id) => [{ type: 'User', id }],
274
+ }),
275
+
276
+ // Mutations
277
+ createUser: builder.mutation<User, Partial<User>>({
278
+ query: (body) => ({
279
+ url: '/users',
280
+ method: 'POST',
281
+ body,
282
+ }),
283
+ invalidatesTags: ['User'],
284
+ }),
285
+ updateUser: builder.mutation<User, { id: string; body: Partial<User> }>({
286
+ query: ({ id, body }) => ({
287
+ url: `/users/${id}`,
288
+ method: 'PATCH',
289
+ body,
290
+ }),
291
+ invalidatesTags: (result, error, { id }) => [{ type: 'User', id }],
292
+ }),
293
+ deleteUser: builder.mutation<void, string>({
294
+ query: (id) => ({
295
+ url: `/users/${id}`,
296
+ method: 'DELETE',
297
+ }),
298
+ invalidatesTags: ['User'],
299
+ }),
300
+ }),
301
+ });
302
+
303
+ export const {
304
+ useGetUsersQuery,
305
+ useGetUserQuery,
306
+ useCreateUserMutation,
307
+ useUpdateUserMutation,
308
+ useDeleteUserMutation,
309
+ } = apiSlice;
310
+ ```
311
+
312
+ ## Using in Components
313
+
314
+ ### Basic Usage
315
+
316
+ ```tsx
317
+ 'use client';
318
+
319
+ import { useAppDispatch, useAppSelector } from '@/store/hooks';
320
+ import { selectCurrentUser, logout } from '@/slices/user-slice';
321
+
322
+ export function UserProfile() {
323
+ const dispatch = useAppDispatch();
324
+ const user = useAppSelector(selectCurrentUser);
325
+
326
+ if (!user) return null;
327
+
328
+ return (
329
+ <div>
330
+ <h1>{user.name}</h1>
331
+ <p>{user.email}</p>
332
+ <button onClick={() => dispatch(logout())}>Logout</button>
333
+ </div>
334
+ );
335
+ }
336
+ ```
337
+
338
+ ### Using RTK Query
339
+
340
+ ```tsx
341
+ 'use client';
342
+
343
+ import { useGetUsersQuery, useDeleteUserMutation } from '@/api/api-slice';
344
+
345
+ export function UserList() {
346
+ const { data: users, isLoading, error } = useGetUsersQuery();
347
+ const [deleteUser, { isLoading: isDeleting }] = useDeleteUserMutation();
348
+
349
+ if (isLoading) return <div>Loading...</div>;
350
+ if (error) return <div>Error loading users</div>;
351
+
352
+ return (
353
+ <ul>
354
+ {users?.map((user) => (
355
+ <li key={user.id}>
356
+ {user.name}
357
+ <button
358
+ onClick={() => deleteUser(user.id)}
359
+ disabled={isDeleting}
360
+ >
361
+ Delete
362
+ </button>
363
+ </li>
364
+ ))}
365
+ </ul>
366
+ );
367
+ }
368
+ ```
369
+
370
+ ## Testing
371
+
372
+ ### Slice Testing
373
+
374
+ ```typescript
375
+ import { userSlice, setUser, logout, selectCurrentUser } from './user-slice';
376
+
377
+ describe('userSlice', () => {
378
+ const initialState = { currentUser: null, isAuthenticated: false };
379
+
380
+ it('should handle setUser', () => {
381
+ const user = { id: '1', name: 'John', email: 'john@test.com' };
382
+ const state = userSlice.reducer(initialState, setUser(user));
383
+
384
+ expect(state.currentUser).toEqual(user);
385
+ expect(state.isAuthenticated).toBe(true);
386
+ });
387
+
388
+ it('should handle logout', () => {
389
+ const loggedInState = {
390
+ currentUser: { id: '1', name: 'John', email: 'john@test.com' },
391
+ isAuthenticated: true,
392
+ };
393
+ const state = userSlice.reducer(loggedInState, logout());
394
+
395
+ expect(state.currentUser).toBeNull();
396
+ expect(state.isAuthenticated).toBe(false);
397
+ });
398
+ });
399
+ ```
400
+
401
+ ### Component Testing with Store
402
+
403
+ ```tsx
404
+ import { render, screen } from '@testing-library/react';
405
+ import { Provider } from 'react-redux';
406
+ import { makeStore } from '@/store';
407
+ import { UserProfile } from './user-profile';
408
+
409
+ function renderWithStore(ui: React.ReactElement, preloadedState = {}) {
410
+ const store = makeStore();
411
+ return render(<Provider store={store}>{ui}</Provider>);
412
+ }
413
+
414
+ describe('UserProfile', () => {
415
+ it('should render user info', () => {
416
+ renderWithStore(<UserProfile />);
417
+ // ...
418
+ });
419
+ });
420
+ ```
421
+
422
+ ## Best Practices
423
+
424
+ - Use typed hooks (`useAppDispatch`, `useAppSelector`)
425
+ - Keep slices focused on a single domain
426
+ - Use RTK Query for server state
427
+ - Use selectors for derived state
428
+ - Don't duplicate Server Component data in Redux
429
+ - Use Redux DevTools for debugging
@@ -0,0 +1,299 @@
1
+ ---
2
+ paths:
3
+ - "**/*store*.ts"
4
+ - "**/*store*.tsx"
5
+ - "**/stores/**/*.ts"
6
+ ---
7
+
8
+ # Zustand State Management
9
+
10
+ Use Zustand for lightweight client-side state management.
11
+
12
+ ## When to Use
13
+
14
+ - Small to medium projects
15
+ - Simple state logic
16
+ - When you want minimal boilerplate
17
+ - Quick prototyping
18
+
19
+ ## Store Structure
20
+
21
+ ```
22
+ libs/
23
+ [domain]/
24
+ data-access/
25
+ stores/
26
+ user-store.ts
27
+ cart-store.ts
28
+ index.ts
29
+ ```
30
+
31
+ ## Creating a Store
32
+
33
+ ### Basic Store
34
+
35
+ ```typescript
36
+ // stores/user-store.ts
37
+ import { create } from 'zustand';
38
+
39
+ interface User {
40
+ id: string;
41
+ name: string;
42
+ email: string;
43
+ }
44
+
45
+ interface UserState {
46
+ // State
47
+ user: User | null;
48
+ isLoading: boolean;
49
+ error: string | null;
50
+
51
+ // Actions
52
+ setUser: (user: User | null) => void;
53
+ fetchUser: (id: string) => Promise<void>;
54
+ logout: () => void;
55
+ }
56
+
57
+ export const useUserStore = create<UserState>((set) => ({
58
+ // Initial state
59
+ user: null,
60
+ isLoading: false,
61
+ error: null,
62
+
63
+ // Actions
64
+ setUser: (user) => set({ user }),
65
+
66
+ fetchUser: async (id) => {
67
+ set({ isLoading: true, error: null });
68
+ try {
69
+ const response = await fetch(`/api/users/${id}`);
70
+ const user = await response.json();
71
+ set({ user, isLoading: false });
72
+ } catch (error) {
73
+ set({ error: 'Failed to fetch user', isLoading: false });
74
+ }
75
+ },
76
+
77
+ logout: () => set({ user: null }),
78
+ }));
79
+ ```
80
+
81
+ ### Store with Slices (Large Stores)
82
+
83
+ ```typescript
84
+ // stores/app-store.ts
85
+ import { create } from 'zustand';
86
+
87
+ // User slice
88
+ interface UserSlice {
89
+ user: User | null;
90
+ setUser: (user: User | null) => void;
91
+ }
92
+
93
+ const createUserSlice = (set: any): UserSlice => ({
94
+ user: null,
95
+ setUser: (user) => set({ user }),
96
+ });
97
+
98
+ // Cart slice
99
+ interface CartSlice {
100
+ items: CartItem[];
101
+ addItem: (item: CartItem) => void;
102
+ removeItem: (id: string) => void;
103
+ clearCart: () => void;
104
+ }
105
+
106
+ const createCartSlice = (set: any): CartSlice => ({
107
+ items: [],
108
+ addItem: (item) => set((state: any) => ({
109
+ items: [...state.items, item]
110
+ })),
111
+ removeItem: (id) => set((state: any) => ({
112
+ items: state.items.filter((item: CartItem) => item.id !== id)
113
+ })),
114
+ clearCart: () => set({ items: [] }),
115
+ });
116
+
117
+ // Combined store
118
+ type AppStore = UserSlice & CartSlice;
119
+
120
+ export const useAppStore = create<AppStore>((...args) => ({
121
+ ...createUserSlice(...args),
122
+ ...createCartSlice(...args),
123
+ }));
124
+ ```
125
+
126
+ ### Store with Persistence
127
+
128
+ ```typescript
129
+ import { create } from 'zustand';
130
+ import { persist } from 'zustand/middleware';
131
+
132
+ interface ThemeState {
133
+ theme: 'light' | 'dark';
134
+ toggleTheme: () => void;
135
+ }
136
+
137
+ export const useThemeStore = create<ThemeState>()(
138
+ persist(
139
+ (set) => ({
140
+ theme: 'light',
141
+ toggleTheme: () => set((state) => ({
142
+ theme: state.theme === 'light' ? 'dark' : 'light'
143
+ })),
144
+ }),
145
+ {
146
+ name: 'theme-storage', // localStorage key
147
+ }
148
+ )
149
+ );
150
+ ```
151
+
152
+ ### Store with Immer (Complex Updates)
153
+
154
+ ```typescript
155
+ import { create } from 'zustand';
156
+ import { immer } from 'zustand/middleware/immer';
157
+
158
+ interface TodoState {
159
+ todos: Todo[];
160
+ addTodo: (text: string) => void;
161
+ toggleTodo: (id: string) => void;
162
+ updateTodo: (id: string, text: string) => void;
163
+ }
164
+
165
+ export const useTodoStore = create<TodoState>()(
166
+ immer((set) => ({
167
+ todos: [],
168
+
169
+ addTodo: (text) => set((state) => {
170
+ state.todos.push({ id: Date.now().toString(), text, done: false });
171
+ }),
172
+
173
+ toggleTodo: (id) => set((state) => {
174
+ const todo = state.todos.find((todo) => todo.id === id);
175
+ if (todo) {
176
+ todo.done = !todo.done;
177
+ }
178
+ }),
179
+
180
+ updateTodo: (id, text) => set((state) => {
181
+ const todo = state.todos.find((todo) => todo.id === id);
182
+ if (todo) {
183
+ todo.text = text;
184
+ }
185
+ }),
186
+ }))
187
+ );
188
+ ```
189
+
190
+ ## Using in Components
191
+
192
+ ### Basic Usage
193
+
194
+ ```tsx
195
+ 'use client';
196
+
197
+ import { useUserStore } from '@/stores/user-store';
198
+
199
+ export function UserProfile() {
200
+ const { user, isLoading, error, fetchUser, logout } = useUserStore();
201
+
202
+ if (isLoading) return <div>Loading...</div>;
203
+ if (error) return <div>Error: {error}</div>;
204
+ if (!user) return <div>Not logged in</div>;
205
+
206
+ return (
207
+ <div>
208
+ <h1>{user.name}</h1>
209
+ <p>{user.email}</p>
210
+ <button onClick={logout}>Logout</button>
211
+ </div>
212
+ );
213
+ }
214
+ ```
215
+
216
+ ### Selecting State (Performance)
217
+
218
+ ```tsx
219
+ 'use client';
220
+
221
+ import { useUserStore } from '@/stores/user-store';
222
+
223
+ export function UserName() {
224
+ // Only re-render when user.name changes
225
+ const userName = useUserStore((state) => state.user?.name);
226
+
227
+ return <span>{userName}</span>;
228
+ }
229
+ ```
230
+
231
+ ### Shallow Comparison for Objects
232
+
233
+ ```tsx
234
+ 'use client';
235
+
236
+ import { useShallow } from 'zustand/react/shallow';
237
+ import { useUserStore } from '@/stores/user-store';
238
+
239
+ export function UserInfo() {
240
+ // Prevent re-render if object reference changes but content is same
241
+ const { name, email } = useUserStore(
242
+ useShallow((state) => ({
243
+ name: state.user?.name,
244
+ email: state.user?.email
245
+ }))
246
+ );
247
+
248
+ return (
249
+ <div>
250
+ <p>{name}</p>
251
+ <p>{email}</p>
252
+ </div>
253
+ );
254
+ }
255
+ ```
256
+
257
+ ## Testing
258
+
259
+ ```typescript
260
+ import { act, renderHook } from '@testing-library/react';
261
+ import { useUserStore } from './user-store';
262
+
263
+ describe('useUserStore', () => {
264
+ beforeEach(() => {
265
+ // Reset store before each test
266
+ useUserStore.setState({ user: null, isLoading: false, error: null });
267
+ });
268
+
269
+ it('should set user', () => {
270
+ const { result } = renderHook(() => useUserStore());
271
+
272
+ act(() => {
273
+ result.current.setUser({ id: '1', name: 'John', email: 'john@test.com' });
274
+ });
275
+
276
+ expect(result.current.user?.name).toBe('John');
277
+ });
278
+
279
+ it('should logout', () => {
280
+ useUserStore.setState({ user: { id: '1', name: 'John', email: 'john@test.com' } });
281
+
282
+ const { result } = renderHook(() => useUserStore());
283
+
284
+ act(() => {
285
+ result.current.logout();
286
+ });
287
+
288
+ expect(result.current.user).toBeNull();
289
+ });
290
+ });
291
+ ```
292
+
293
+ ## Best Practices
294
+
295
+ - Keep stores small and focused
296
+ - Use selectors for performance
297
+ - Don't put Server Component data in stores
298
+ - Use `immer` middleware for complex nested updates
299
+ - Use `persist` middleware for data that should survive page refresh