create-fluxstack 1.8.1 → 1.8.3
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/.env.example +19 -0
- package/README.md +6 -3
- package/app/client/SIMPLIFICATION.md +140 -0
- package/app/client/frontend-only.ts +1 -1
- package/app/client/src/App.tsx +148 -283
- package/app/client/src/index.css +5 -20
- package/app/client/src/lib/eden-api.ts +53 -220
- package/app/client/src/main.tsx +2 -3
- package/app/server/controllers/users.controller.ts +57 -31
- package/app/server/index.ts +5 -2
- package/app/server/live/register-components.ts +18 -7
- package/app/server/routes/env-test.ts +53 -2
- package/app/server/routes/index.ts +1 -8
- package/app/server/routes/users.routes.ts +192 -91
- package/config/fluxstack.config.ts +2 -2
- package/config/plugins.config.ts +22 -1
- package/core/build/flux-plugins-generator.ts +5 -5
- package/core/build/live-components-generator.ts +15 -12
- package/core/cli/command-registry.ts +4 -14
- package/core/cli/commands/plugin-deps.ts +8 -8
- package/core/cli/generators/component.ts +3 -3
- package/core/cli/generators/controller.ts +4 -4
- package/core/cli/generators/index.ts +8 -8
- package/core/cli/generators/interactive.ts +4 -4
- package/core/cli/generators/plugin.ts +3 -3
- package/core/cli/generators/prompts.ts +1 -1
- package/core/cli/generators/route.ts +27 -11
- package/core/cli/generators/service.ts +5 -5
- package/core/cli/generators/template-engine.ts +1 -1
- package/core/cli/generators/types.ts +1 -1
- package/core/cli/index.ts +158 -193
- package/core/cli/plugin-discovery.ts +3 -3
- package/core/client/hooks/index.ts +2 -2
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +1 -1
- package/core/client/hooks/useChunkedUpload.ts +1 -1
- package/core/client/hooks/useHybridLiveComponent.ts +1 -1
- package/core/client/hooks/useWebSocket.ts +1 -1
- package/core/config/env.ts +1 -1
- package/core/config/runtime-config.ts +5 -5
- package/core/config/schema.ts +9 -0
- package/core/framework/server.ts +30 -15
- package/core/framework/types.ts +2 -2
- package/core/live/ComponentRegistry.ts +1 -1
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1 -1
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +65 -161
- package/core/plugins/built-in/static/index.ts +18 -47
- package/core/plugins/built-in/swagger/index.ts +301 -231
- package/core/plugins/built-in/vite/index.ts +74 -109
- package/core/plugins/config.ts +2 -2
- package/core/plugins/dependency-manager.ts +2 -2
- package/core/plugins/discovery.ts +1 -1
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/manager.ts +19 -4
- package/core/plugins/module-resolver.ts +1 -1
- package/core/plugins/registry.ts +3 -3
- package/core/plugins/types.ts +147 -5
- package/core/server/framework.ts +2 -2
- package/core/server/live/ComponentRegistry.ts +9 -26
- package/core/server/live/FileUploadManager.ts +1 -1
- package/core/server/live/auto-generated-components.ts +26 -0
- package/core/server/live/websocket-plugin.ts +211 -19
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +4 -4
- package/core/server/plugins/database.ts +1 -2
- package/core/server/plugins/static-files-plugin.ts +259 -231
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +4 -4
- package/core/server/standalone.ts +16 -1
- package/core/testing/index.ts +1 -1
- package/core/testing/setup.ts +1 -1
- package/core/utils/logger/startup-banner.ts +7 -33
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +68 -25
- package/package.json +2 -2
- package/plugins/crypto-auth/index.ts +52 -47
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/helpers.ts +16 -1
- package/vitest.config.ts +11 -2
- package/app/client/src/App.css +0 -883
- package/app/client/src/components/ErrorBoundary.tsx +0 -107
- package/app/client/src/components/ErrorDisplay.css +0 -365
- package/app/client/src/components/ErrorDisplay.tsx +0 -258
- package/app/client/src/components/FluxStackConfig.tsx +0 -1321
- package/app/client/src/components/HybridLiveCounter.tsx +0 -140
- package/app/client/src/components/LiveClock.tsx +0 -286
- package/app/client/src/components/MainLayout.tsx +0 -388
- package/app/client/src/components/SidebarNavigation.tsx +0 -391
- package/app/client/src/components/StateDemo.tsx +0 -178
- package/app/client/src/components/SystemMonitor.tsx +0 -1044
- package/app/client/src/components/UserProfile.tsx +0 -809
- package/app/client/src/hooks/useAuth.ts +0 -39
- package/app/client/src/hooks/useNotifications.ts +0 -56
- package/app/client/src/lib/errors.ts +0 -340
- package/app/client/src/lib/hooks/useErrorHandler.ts +0 -258
- package/app/client/src/lib/index.ts +0 -45
- package/app/client/src/pages/ApiDocs.tsx +0 -182
- package/app/client/src/pages/CryptoAuthPage.tsx +0 -394
- package/app/client/src/pages/Demo.tsx +0 -174
- package/app/client/src/pages/HybridLive.tsx +0 -263
- package/app/client/src/pages/Overview.tsx +0 -155
- package/app/client/src/store/README.md +0 -43
- package/app/client/src/store/index.ts +0 -16
- package/app/client/src/store/slices/uiSlice.ts +0 -151
- package/app/client/src/store/slices/userSlice.ts +0 -161
- package/app/client/src/test/README.md +0 -257
- package/app/client/src/test/setup.ts +0 -70
- package/app/client/src/test/types.ts +0 -12
- package/app/server/live/CounterComponent.ts +0 -191
- package/app/server/live/FluxStackConfig.ts +0 -534
- package/app/server/live/SidebarNavigation.ts +0 -157
- package/app/server/live/SystemMonitor.ts +0 -595
- package/app/server/live/SystemMonitorIntegration.ts +0 -151
- package/app/server/live/UserProfileComponent.ts +0 -141
- package/app/server/middleware/auth.ts +0 -136
- package/app/server/middleware/errorHandling.ts +0 -252
- package/app/server/middleware/index.ts +0 -10
- package/app/server/middleware/rateLimit.ts +0 -193
- package/app/server/middleware/requestLogging.ts +0 -215
- package/app/server/middleware/validation.ts +0 -270
- package/app/server/routes/config.ts +0 -145
- package/app/server/routes/crypto-auth-demo.routes.ts +0 -167
- package/app/server/routes/example-with-crypto-auth.routes.ts +0 -235
- package/app/server/routes/exemplo-posts.routes.ts +0 -161
- package/app/server/routes/upload.ts +0 -92
- package/app/server/services/NotificationService.ts +0 -302
- package/app/server/services/UserService.ts +0 -222
- package/app/server/services/index.ts +0 -46
- package/app/server/types/index.ts +0 -1
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI Store with Zustand
|
|
3
|
-
* Manages global UI state like modals, notifications, theme, etc.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { create } from 'zustand'
|
|
7
|
-
import { persist } from 'zustand/middleware'
|
|
8
|
-
|
|
9
|
-
export interface Notification {
|
|
10
|
-
id: string
|
|
11
|
-
type: 'success' | 'error' | 'warning' | 'info'
|
|
12
|
-
title: string
|
|
13
|
-
message: string
|
|
14
|
-
duration?: number
|
|
15
|
-
timestamp: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface Modal {
|
|
19
|
-
id: string
|
|
20
|
-
component: string
|
|
21
|
-
props?: Record<string, any>
|
|
22
|
-
closable?: boolean
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface UIState {
|
|
26
|
-
// State
|
|
27
|
-
theme: 'light' | 'dark' | 'system'
|
|
28
|
-
sidebarOpen: boolean
|
|
29
|
-
notifications: Notification[]
|
|
30
|
-
modals: Modal[]
|
|
31
|
-
loading: {
|
|
32
|
-
global: boolean
|
|
33
|
-
operations: Record<string, boolean>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Actions
|
|
37
|
-
setTheme: (theme: 'light' | 'dark' | 'system') => void
|
|
38
|
-
toggleSidebar: () => void
|
|
39
|
-
setSidebarOpen: (open: boolean) => void
|
|
40
|
-
|
|
41
|
-
// Notifications
|
|
42
|
-
addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => void
|
|
43
|
-
removeNotification: (id: string) => void
|
|
44
|
-
clearNotifications: () => void
|
|
45
|
-
|
|
46
|
-
// Modals
|
|
47
|
-
openModal: (modal: Omit<Modal, 'id'>) => void
|
|
48
|
-
closeModal: (id: string) => void
|
|
49
|
-
closeAllModals: () => void
|
|
50
|
-
|
|
51
|
-
// Loading
|
|
52
|
-
setGlobalLoading: (loading: boolean) => void
|
|
53
|
-
setOperationLoading: (operation: string, loading: boolean) => void
|
|
54
|
-
clearOperationLoading: (operation: string) => void
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const useUIStore = create<UIState>()(
|
|
58
|
-
persist(
|
|
59
|
-
(set, get) => ({
|
|
60
|
-
// Initial state
|
|
61
|
-
theme: 'system',
|
|
62
|
-
sidebarOpen: true,
|
|
63
|
-
notifications: [],
|
|
64
|
-
modals: [],
|
|
65
|
-
loading: {
|
|
66
|
-
global: false,
|
|
67
|
-
operations: {}
|
|
68
|
-
},
|
|
69
|
-
|
|
70
|
-
// Theme actions
|
|
71
|
-
setTheme: (theme) => set({ theme }),
|
|
72
|
-
|
|
73
|
-
// Sidebar actions
|
|
74
|
-
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
|
75
|
-
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
|
76
|
-
|
|
77
|
-
// Notification actions
|
|
78
|
-
addNotification: (notification) => {
|
|
79
|
-
const newNotification: Notification = {
|
|
80
|
-
...notification,
|
|
81
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
82
|
-
timestamp: Date.now()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
set((state) => ({
|
|
86
|
-
notifications: [...state.notifications, newNotification]
|
|
87
|
-
}))
|
|
88
|
-
|
|
89
|
-
// Auto-remove notification after duration
|
|
90
|
-
if (notification.duration !== 0) {
|
|
91
|
-
setTimeout(() => {
|
|
92
|
-
get().removeNotification(newNotification.id)
|
|
93
|
-
}, notification.duration || 5000)
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
removeNotification: (id) => set((state) => ({
|
|
98
|
-
notifications: state.notifications.filter(n => n.id !== id)
|
|
99
|
-
})),
|
|
100
|
-
|
|
101
|
-
clearNotifications: () => set({ notifications: [] }),
|
|
102
|
-
|
|
103
|
-
// Modal actions
|
|
104
|
-
openModal: (modal) => {
|
|
105
|
-
const newModal: Modal = {
|
|
106
|
-
...modal,
|
|
107
|
-
id: Math.random().toString(36).substr(2, 9)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
set((state) => ({
|
|
111
|
-
modals: [...state.modals, newModal]
|
|
112
|
-
}))
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
closeModal: (id) => set((state) => ({
|
|
116
|
-
modals: state.modals.filter(m => m.id !== id)
|
|
117
|
-
})),
|
|
118
|
-
|
|
119
|
-
closeAllModals: () => set({ modals: [] }),
|
|
120
|
-
|
|
121
|
-
// Loading actions
|
|
122
|
-
setGlobalLoading: (loading) => set((state) => ({
|
|
123
|
-
loading: { ...state.loading, global: loading }
|
|
124
|
-
})),
|
|
125
|
-
|
|
126
|
-
setOperationLoading: (operation, loading) => set((state) => ({
|
|
127
|
-
loading: {
|
|
128
|
-
...state.loading,
|
|
129
|
-
operations: {
|
|
130
|
-
...state.loading.operations,
|
|
131
|
-
[operation]: loading
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
})),
|
|
135
|
-
|
|
136
|
-
clearOperationLoading: (operation) => set((state) => {
|
|
137
|
-
const { [operation]: _, ...operations } = state.loading.operations
|
|
138
|
-
return {
|
|
139
|
-
loading: { ...state.loading, operations }
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
}),
|
|
143
|
-
{
|
|
144
|
-
name: 'ui-store',
|
|
145
|
-
partialize: (state) => ({
|
|
146
|
-
theme: state.theme,
|
|
147
|
-
sidebarOpen: state.sidebarOpen
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
)
|
|
151
|
-
)
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Store
|
|
3
|
-
* App-specific user store using FluxStack core
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Temporary direct implementation until module resolution is fixed
|
|
7
|
-
import { create } from 'zustand'
|
|
8
|
-
import { persist, createJSONStorage } from 'zustand/middleware'
|
|
9
|
-
|
|
10
|
-
export interface BaseUser {
|
|
11
|
-
id: string
|
|
12
|
-
email: string
|
|
13
|
-
name: string
|
|
14
|
-
role: 'admin' | 'user'
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Type aliases for compatibility
|
|
18
|
-
export type User = BaseUser
|
|
19
|
-
|
|
20
|
-
export interface LoginCredentials {
|
|
21
|
-
email: string
|
|
22
|
-
password: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface RegisterData {
|
|
26
|
-
email: string
|
|
27
|
-
password: string
|
|
28
|
-
name: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface BaseUserStore {
|
|
32
|
-
currentUser: BaseUser | null
|
|
33
|
-
isAuthenticated: boolean
|
|
34
|
-
isLoading: boolean
|
|
35
|
-
error: string | null
|
|
36
|
-
login: (credentials: { email: string; password: string }) => Promise<void>
|
|
37
|
-
register: (data: { email: string; password: string; name: string }) => Promise<void>
|
|
38
|
-
logout: () => void
|
|
39
|
-
updateProfile: (data: Partial<BaseUser>) => Promise<void>
|
|
40
|
-
clearError: () => void
|
|
41
|
-
setLoading: (loading: boolean) => void
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Create user store using Zustand directly (temporary)
|
|
45
|
-
export const useUserStore = create<BaseUserStore>()(
|
|
46
|
-
persist(
|
|
47
|
-
(set, get) => ({
|
|
48
|
-
currentUser: null,
|
|
49
|
-
isAuthenticated: false,
|
|
50
|
-
isLoading: false,
|
|
51
|
-
error: null,
|
|
52
|
-
|
|
53
|
-
login: async (credentials) => {
|
|
54
|
-
set({ isLoading: true, error: null })
|
|
55
|
-
try {
|
|
56
|
-
const response = await fetch('/api/auth/login', {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers: { 'Content-Type': 'application/json' },
|
|
59
|
-
body: JSON.stringify(credentials)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
const error = await response.json()
|
|
64
|
-
throw new Error(error.message || 'Login failed')
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const { user } = await response.json()
|
|
68
|
-
set({
|
|
69
|
-
currentUser: user,
|
|
70
|
-
isAuthenticated: true,
|
|
71
|
-
isLoading: false
|
|
72
|
-
})
|
|
73
|
-
} catch (error) {
|
|
74
|
-
set({
|
|
75
|
-
error: error instanceof Error ? error.message : 'Login failed',
|
|
76
|
-
isLoading: false
|
|
77
|
-
})
|
|
78
|
-
throw error
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
register: async (data) => {
|
|
83
|
-
set({ isLoading: true, error: null })
|
|
84
|
-
try {
|
|
85
|
-
const response = await fetch('/api/auth/register', {
|
|
86
|
-
method: 'POST',
|
|
87
|
-
headers: { 'Content-Type': 'application/json' },
|
|
88
|
-
body: JSON.stringify(data)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
const error = await response.json()
|
|
93
|
-
throw new Error(error.message || 'Registration failed')
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const { user } = await response.json()
|
|
97
|
-
set({
|
|
98
|
-
currentUser: user,
|
|
99
|
-
isAuthenticated: true,
|
|
100
|
-
isLoading: false
|
|
101
|
-
})
|
|
102
|
-
} catch (error) {
|
|
103
|
-
set({
|
|
104
|
-
error: error instanceof Error ? error.message : 'Registration failed',
|
|
105
|
-
isLoading: false
|
|
106
|
-
})
|
|
107
|
-
throw error
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
logout: () => {
|
|
112
|
-
fetch('/api/auth/logout', { method: 'POST' }).catch(console.error)
|
|
113
|
-
set({
|
|
114
|
-
currentUser: null,
|
|
115
|
-
isAuthenticated: false,
|
|
116
|
-
error: null
|
|
117
|
-
})
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
updateProfile: async (data) => {
|
|
121
|
-
const { currentUser } = get()
|
|
122
|
-
if (!currentUser) {
|
|
123
|
-
throw new Error('No user logged in')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
set({ isLoading: true, error: null })
|
|
127
|
-
try {
|
|
128
|
-
const response = await fetch('/api/user/profile', {
|
|
129
|
-
method: 'PUT',
|
|
130
|
-
headers: { 'Content-Type': 'application/json' },
|
|
131
|
-
body: JSON.stringify(data)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
if (!response.ok) {
|
|
135
|
-
const error = await response.json()
|
|
136
|
-
throw new Error(error.message || 'Profile update failed')
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const { user } = await response.json()
|
|
140
|
-
set({
|
|
141
|
-
currentUser: user,
|
|
142
|
-
isLoading: false
|
|
143
|
-
})
|
|
144
|
-
} catch (error) {
|
|
145
|
-
set({
|
|
146
|
-
error: error instanceof Error ? error.message : 'Profile update failed',
|
|
147
|
-
isLoading: false
|
|
148
|
-
})
|
|
149
|
-
throw error
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
clearError: () => set({ error: null }),
|
|
154
|
-
setLoading: (loading) => set({ isLoading: loading })
|
|
155
|
-
}),
|
|
156
|
-
{
|
|
157
|
-
name: 'user-store',
|
|
158
|
-
storage: createJSONStorage(() => localStorage)
|
|
159
|
-
}
|
|
160
|
-
)
|
|
161
|
-
)
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
# FluxStack Testing Guide
|
|
2
|
-
|
|
3
|
-
Este guia explica como testar o sistema de estado baseado em Zustand no FluxStack.
|
|
4
|
-
|
|
5
|
-
## 🧪 **Estrutura de Testes**
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
app/client/src/
|
|
9
|
-
├── store/
|
|
10
|
-
│ ├── __tests__/
|
|
11
|
-
│ │ ├── userSlice.test.ts # Testes do store de usuário
|
|
12
|
-
│ │ └── uiSlice.test.ts # Testes do store de UI
|
|
13
|
-
├── hooks/
|
|
14
|
-
│ ├── __tests__/
|
|
15
|
-
│ │ ├── useAuth.test.ts # Testes do hook de auth
|
|
16
|
-
│ │ └── useNotifications.test.ts # Testes do hook de notificações
|
|
17
|
-
├── components/
|
|
18
|
-
│ └── __tests__/
|
|
19
|
-
│ └── StateDemo.test.tsx # Testes de integração
|
|
20
|
-
└── test/
|
|
21
|
-
├── setup.ts # Configuração global
|
|
22
|
-
├── types.ts # Tipos para testes
|
|
23
|
-
└── README.md # Este arquivo
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## 🔧 **Configuração**
|
|
27
|
-
|
|
28
|
-
### Dependências necessárias:
|
|
29
|
-
|
|
30
|
-
```json
|
|
31
|
-
{
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@testing-library/jest-dom": "^6.1.4",
|
|
34
|
-
"@testing-library/react": "^13.4.0",
|
|
35
|
-
"@testing-library/user-event": "^14.5.1",
|
|
36
|
-
"jsdom": "^22.1.0",
|
|
37
|
-
"vitest": "^0.34.6"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Configuração do Vitest:
|
|
43
|
-
|
|
44
|
-
O arquivo `vitest.config.ts` está configurado para:
|
|
45
|
-
- Usar jsdom como ambiente
|
|
46
|
-
- Configurar aliases para imports
|
|
47
|
-
- Incluir setup global
|
|
48
|
-
- Configurar coverage
|
|
49
|
-
|
|
50
|
-
## 🧪 **Testando Stores Zustand**
|
|
51
|
-
|
|
52
|
-
### Exemplo básico:
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
import { renderHook, act } from '@testing-library/react'
|
|
56
|
-
import { useUserStore } from '../slices/userSlice'
|
|
57
|
-
|
|
58
|
-
describe('useUserStore', () => {
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
// Reset store state
|
|
61
|
-
useUserStore.setState({
|
|
62
|
-
currentUser: null,
|
|
63
|
-
isAuthenticated: false,
|
|
64
|
-
isLoading: false,
|
|
65
|
-
error: null
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('should login successfully', async () => {
|
|
70
|
-
const { result } = renderHook(() => useUserStore())
|
|
71
|
-
|
|
72
|
-
await act(async () => {
|
|
73
|
-
await result.current.login({
|
|
74
|
-
email: 'test@example.com',
|
|
75
|
-
password: 'password'
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
expect(result.current.isAuthenticated).toBe(true)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Padrões importantes:
|
|
85
|
-
|
|
86
|
-
1. **Reset do estado**: Sempre resetar o store antes de cada teste
|
|
87
|
-
2. **act()**: Usar `act()` para mudanças de estado assíncronas
|
|
88
|
-
3. **Mock de APIs**: Mockar fetch para testes isolados
|
|
89
|
-
|
|
90
|
-
## 🎯 **Testando Hooks Utilitários**
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
import { renderHook } from '@testing-library/react'
|
|
94
|
-
import { useAuth } from '../useAuth'
|
|
95
|
-
|
|
96
|
-
describe('useAuth', () => {
|
|
97
|
-
it('should detect admin user', () => {
|
|
98
|
-
// Set admin user in store
|
|
99
|
-
useUserStore.setState({
|
|
100
|
-
currentUser: { role: 'admin', /* ... */ },
|
|
101
|
-
isAuthenticated: true
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const { result } = renderHook(() => useAuth())
|
|
105
|
-
|
|
106
|
-
expect(result.current.isAdmin).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## 🧩 **Testando Componentes com Estado**
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
115
|
-
import { MyComponent } from '../MyComponent'
|
|
116
|
-
import { useUIStore } from '../../store/slices/uiSlice'
|
|
117
|
-
|
|
118
|
-
describe('MyComponent', () => {
|
|
119
|
-
beforeEach(() => {
|
|
120
|
-
// Reset stores
|
|
121
|
-
useUIStore.setState({ /* initial state */ })
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('should update theme when button clicked', () => {
|
|
125
|
-
render(<MyComponent />)
|
|
126
|
-
|
|
127
|
-
fireEvent.click(screen.getByText('Dark Theme'))
|
|
128
|
-
|
|
129
|
-
expect(screen.getByText('Current theme: dark')).toBeInTheDocument()
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## 🔄 **Mocking APIs**
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
// Mock fetch globally
|
|
138
|
-
global.fetch = vi.fn()
|
|
139
|
-
|
|
140
|
-
beforeEach(() => {
|
|
141
|
-
vi.resetAllMocks()
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('should handle API success', async () => {
|
|
145
|
-
// Mock successful response
|
|
146
|
-
;(global.fetch as any).mockResolvedValueOnce({
|
|
147
|
-
ok: true,
|
|
148
|
-
json: async () => ({ user: mockUser })
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// Test your component/hook
|
|
152
|
-
})
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## 📊 **Coverage**
|
|
156
|
-
|
|
157
|
-
Execute testes com coverage:
|
|
158
|
-
|
|
159
|
-
```bash
|
|
160
|
-
npm run test:coverage
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Isso gerará um relatório em `coverage/index.html`.
|
|
164
|
-
|
|
165
|
-
## 🎨 **Boas Práticas**
|
|
166
|
-
|
|
167
|
-
### 1. **Isolamento de Testes**
|
|
168
|
-
- Sempre resetar stores antes de cada teste
|
|
169
|
-
- Mockar APIs externas
|
|
170
|
-
- Não depender de ordem de execução
|
|
171
|
-
|
|
172
|
-
### 2. **Testes Descritivos**
|
|
173
|
-
```typescript
|
|
174
|
-
// ❌ Ruim
|
|
175
|
-
it('should work', () => { /* ... */ })
|
|
176
|
-
|
|
177
|
-
// ✅ Bom
|
|
178
|
-
it('should login successfully with valid credentials', () => { /* ... */ })
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### 3. **Arrange-Act-Assert**
|
|
182
|
-
```typescript
|
|
183
|
-
it('should add notification', () => {
|
|
184
|
-
// Arrange
|
|
185
|
-
const { result } = renderHook(() => useNotifications())
|
|
186
|
-
|
|
187
|
-
// Act
|
|
188
|
-
act(() => {
|
|
189
|
-
result.current.success('Title', 'Message')
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
// Assert
|
|
193
|
-
expect(result.current.notifications).toHaveLength(1)
|
|
194
|
-
})
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### 4. **Testar Comportamentos, não Implementação**
|
|
198
|
-
```typescript
|
|
199
|
-
// ❌ Ruim - testa implementação
|
|
200
|
-
expect(mockSetState).toHaveBeenCalledWith({ loading: true })
|
|
201
|
-
|
|
202
|
-
// ✅ Bom - testa comportamento
|
|
203
|
-
expect(result.current.isLoading).toBe(true)
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## 🚀 **Executando Testes**
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
# Todos os testes
|
|
210
|
-
npm run test:run
|
|
211
|
-
|
|
212
|
-
# Modo watch
|
|
213
|
-
npm run test:watch
|
|
214
|
-
|
|
215
|
-
# Com UI
|
|
216
|
-
npm run test:ui
|
|
217
|
-
|
|
218
|
-
# Apenas stores
|
|
219
|
-
npm run test:client -- store
|
|
220
|
-
|
|
221
|
-
# Apenas hooks
|
|
222
|
-
npm run test:client -- hooks
|
|
223
|
-
|
|
224
|
-
# Apenas componentes
|
|
225
|
-
npm run test:client -- components
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## 🐛 **Debugging**
|
|
229
|
-
|
|
230
|
-
### Console logs em testes:
|
|
231
|
-
```typescript
|
|
232
|
-
it('should debug state', () => {
|
|
233
|
-
const { result } = renderHook(() => useUserStore())
|
|
234
|
-
|
|
235
|
-
console.log('Current state:', result.current)
|
|
236
|
-
|
|
237
|
-
// Seu teste aqui
|
|
238
|
-
})
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Usando screen.debug():
|
|
242
|
-
```typescript
|
|
243
|
-
it('should render correctly', () => {
|
|
244
|
-
render(<MyComponent />)
|
|
245
|
-
|
|
246
|
-
screen.debug() // Mostra o DOM atual
|
|
247
|
-
|
|
248
|
-
// Seu teste aqui
|
|
249
|
-
})
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
## 📝 **Exemplos Completos**
|
|
253
|
-
|
|
254
|
-
Veja os arquivos de teste existentes para exemplos completos:
|
|
255
|
-
- `store/__tests__/userSlice.test.ts` - Testes de store
|
|
256
|
-
- `hooks/__tests__/useAuth.test.ts` - Testes de hooks
|
|
257
|
-
- `components/__tests__/StateDemo.test.tsx` - Testes de integração
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Setup
|
|
3
|
-
* Global test configuration and mocks
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { expect, afterEach, vi } from 'vitest'
|
|
7
|
-
import { cleanup } from '@testing-library/react'
|
|
8
|
-
import * as matchers from '@testing-library/jest-dom/matchers'
|
|
9
|
-
|
|
10
|
-
// Extend Vitest's expect with jest-dom matchers
|
|
11
|
-
expect.extend(matchers)
|
|
12
|
-
|
|
13
|
-
// Mock fetch with proper typing
|
|
14
|
-
global.fetch = vi.fn() as any
|
|
15
|
-
|
|
16
|
-
// Cleanup after each test case (e.g. clearing jsdom)
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
cleanup()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// Mock window.matchMedia
|
|
22
|
-
Object.defineProperty(window, 'matchMedia', {
|
|
23
|
-
writable: true,
|
|
24
|
-
value: vi.fn().mockImplementation(query => ({
|
|
25
|
-
matches: false,
|
|
26
|
-
media: query,
|
|
27
|
-
onchange: null,
|
|
28
|
-
addListener: vi.fn(), // deprecated
|
|
29
|
-
removeListener: vi.fn(), // deprecated
|
|
30
|
-
addEventListener: vi.fn(),
|
|
31
|
-
removeEventListener: vi.fn(),
|
|
32
|
-
dispatchEvent: vi.fn(),
|
|
33
|
-
})),
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// Mock window.ResizeObserver
|
|
37
|
-
global.ResizeObserver = vi.fn().mockImplementation(() => ({
|
|
38
|
-
observe: vi.fn(),
|
|
39
|
-
unobserve: vi.fn(),
|
|
40
|
-
disconnect: vi.fn(),
|
|
41
|
-
}))
|
|
42
|
-
|
|
43
|
-
// Mock localStorage
|
|
44
|
-
const localStorageMock = {
|
|
45
|
-
getItem: vi.fn(),
|
|
46
|
-
setItem: vi.fn(),
|
|
47
|
-
removeItem: vi.fn(),
|
|
48
|
-
clear: vi.fn(),
|
|
49
|
-
}
|
|
50
|
-
vi.stubGlobal('localStorage', localStorageMock)
|
|
51
|
-
|
|
52
|
-
// Mock sessionStorage
|
|
53
|
-
const sessionStorageMock = {
|
|
54
|
-
getItem: vi.fn(),
|
|
55
|
-
setItem: vi.fn(),
|
|
56
|
-
removeItem: vi.fn(),
|
|
57
|
-
clear: vi.fn(),
|
|
58
|
-
}
|
|
59
|
-
vi.stubGlobal('sessionStorage', sessionStorageMock)
|
|
60
|
-
|
|
61
|
-
// Mock console methods to reduce noise in tests
|
|
62
|
-
global.console = {
|
|
63
|
-
...console,
|
|
64
|
-
// Uncomment to ignore console logs in tests
|
|
65
|
-
// log: vi.fn(),
|
|
66
|
-
// debug: vi.fn(),
|
|
67
|
-
// info: vi.fn(),
|
|
68
|
-
// warn: vi.fn(),
|
|
69
|
-
// error: vi.fn(),
|
|
70
|
-
}
|