@croacroa/react-native-template 2.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/.eslintrc.js +8 -0
- package/.github/workflows/ci.yml +187 -187
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/.github/workflows/npm-publish.yml +57 -0
- package/CHANGELOG.md +195 -106
- package/CONTRIBUTING.md +377 -377
- package/LICENSE +21 -21
- package/README.md +446 -402
- package/__tests__/accessibility/components.test.tsx +285 -0
- package/__tests__/components/Button.test.tsx +2 -4
- package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
- package/__tests__/components/snapshots.test.tsx +131 -131
- package/__tests__/helpers/a11y.ts +54 -0
- package/__tests__/hooks/useAnalytics.test.ts +100 -0
- package/__tests__/hooks/useAnimations.test.ts +70 -0
- package/__tests__/hooks/useAuth.test.tsx +71 -28
- package/__tests__/hooks/useMedia.test.ts +318 -0
- package/__tests__/hooks/usePayments.test.tsx +307 -0
- package/__tests__/hooks/usePermission.test.ts +230 -0
- package/__tests__/hooks/useWebSocket.test.ts +329 -0
- package/__tests__/integration/auth-api.test.tsx +224 -227
- package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
- package/__tests__/services/api.test.ts +24 -6
- package/app/(auth)/home.tsx +11 -9
- package/app/(auth)/profile.tsx +8 -6
- package/app/(auth)/settings.tsx +11 -9
- package/app/(public)/forgot-password.tsx +25 -15
- package/app/(public)/login.tsx +48 -12
- package/app/(public)/onboarding.tsx +5 -5
- package/app/(public)/register.tsx +24 -15
- package/app/_layout.tsx +6 -3
- package/app.config.ts +27 -2
- package/assets/images/.gitkeep +7 -7
- package/assets/images/adaptive-icon.png +0 -0
- package/assets/images/favicon.png +0 -0
- package/assets/images/icon.png +0 -0
- package/assets/images/notification-icon.png +0 -0
- package/assets/images/splash.png +0 -0
- package/components/ErrorBoundary.tsx +73 -28
- package/components/auth/SocialLoginButtons.tsx +168 -0
- package/components/forms/FormInput.tsx +5 -3
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/AnalyticsProvider.tsx +67 -0
- package/components/providers/SuspenseBoundary.tsx +359 -357
- package/components/providers/index.ts +24 -21
- package/components/ui/AnimatedButton.tsx +1 -9
- package/components/ui/AnimatedList.tsx +98 -0
- package/components/ui/AnimatedScreen.tsx +89 -0
- package/components/ui/Avatar.tsx +319 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Button.tsx +11 -3
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/FeatureGate.tsx +57 -0
- package/components/ui/ForceUpdateScreen.tsx +108 -0
- package/components/ui/ImagePickerButton.tsx +180 -0
- package/components/ui/Input.stories.tsx +2 -10
- package/components/ui/Input.tsx +2 -10
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Paywall.tsx +253 -0
- package/components/ui/PermissionGate.tsx +155 -0
- package/components/ui/PurchaseButton.tsx +84 -0
- package/components/ui/Select.tsx +240 -240
- package/components/ui/Skeleton.tsx +3 -1
- package/components/ui/Toast.tsx +427 -418
- package/components/ui/UploadProgress.tsx +189 -0
- package/components/ui/VirtualizedList.tsx +288 -285
- package/components/ui/index.ts +28 -30
- package/constants/config.ts +135 -97
- package/docs/adr/001-state-management.md +79 -79
- package/docs/adr/002-styling-approach.md +130 -130
- package/docs/adr/003-data-fetching.md +155 -155
- package/docs/adr/004-auth-adapter-pattern.md +144 -144
- package/docs/adr/README.md +78 -78
- package/docs/guides/analytics-posthog.md +121 -0
- package/docs/guides/auth-supabase.md +162 -0
- package/docs/guides/feature-flags-launchdarkly.md +150 -0
- package/docs/guides/payments-revenuecat.md +169 -0
- package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
- package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
- package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
- package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
- package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
- package/eas.json +2 -1
- package/hooks/index.ts +70 -40
- package/hooks/useAnimatedEntry.ts +204 -0
- package/hooks/useApi.ts +5 -4
- package/hooks/useAuth.tsx +7 -3
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useChannel.ts +111 -0
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useExperiment.ts +36 -0
- package/hooks/useFeatureFlag.ts +59 -0
- package/hooks/useForceUpdate.ts +91 -0
- package/hooks/useImagePicker.ts +281 -375
- package/hooks/useInAppReview.ts +64 -0
- package/hooks/useMFA.ts +509 -499
- package/hooks/useParallax.ts +142 -0
- package/hooks/usePerformance.ts +434 -434
- package/hooks/usePermission.ts +190 -0
- package/hooks/usePresence.ts +129 -0
- package/hooks/useProducts.ts +36 -0
- package/hooks/usePurchase.ts +103 -0
- package/hooks/useRateLimit.ts +70 -0
- package/hooks/useSubscription.ts +49 -0
- package/hooks/useTrackEvent.ts +52 -0
- package/hooks/useTrackScreen.ts +40 -0
- package/hooks/useUpdates.ts +358 -358
- package/hooks/useUpload.ts +165 -0
- package/hooks/useWebSocket.ts +111 -0
- package/i18n/index.ts +197 -194
- package/i18n/locales/ar.json +170 -101
- package/i18n/locales/de.json +170 -101
- package/i18n/locales/en.json +170 -101
- package/i18n/locales/es.json +170 -101
- package/i18n/locales/fr.json +170 -101
- package/jest.config.js +1 -1
- package/maestro/README.md +113 -113
- package/maestro/config.yaml +35 -35
- package/maestro/flows/login.yaml +62 -62
- package/maestro/flows/mfa-login.yaml +92 -92
- package/maestro/flows/mfa-setup.yaml +86 -86
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -101
- package/maestro/flows/offline-sync.yaml +128 -128
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +188 -176
- package/scripts/generate-placeholders.js +38 -0
- package/services/analytics/adapters/console.ts +50 -0
- package/services/analytics/analytics-adapter.ts +94 -0
- package/services/analytics/types.ts +73 -0
- package/services/analytics.ts +428 -428
- package/services/api.ts +419 -340
- package/services/auth/social/apple.ts +110 -0
- package/services/auth/social/google.ts +159 -0
- package/services/auth/social/social-auth.ts +100 -0
- package/services/auth/social/types.ts +80 -0
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +652 -626
- package/services/feature-flags/adapters/mock.ts +108 -0
- package/services/feature-flags/feature-flag-adapter.ts +174 -0
- package/services/feature-flags/types.ts +79 -0
- package/services/force-update.ts +140 -0
- package/services/index.ts +116 -54
- package/services/media/compression.ts +91 -0
- package/services/media/media-picker.ts +151 -0
- package/services/media/media-upload.ts +160 -0
- package/services/payments/adapters/mock.ts +159 -0
- package/services/payments/payment-adapter.ts +118 -0
- package/services/payments/types.ts +131 -0
- package/services/permissions/permission-manager.ts +284 -0
- package/services/permissions/types.ts +104 -0
- package/services/realtime/types.ts +100 -0
- package/services/realtime/websocket-manager.ts +441 -0
- package/services/security.ts +289 -286
- package/services/sentry.ts +4 -4
- package/stores/appStore.ts +9 -0
- package/stores/notificationStore.ts +3 -1
- package/tailwind.config.js +47 -47
- package/tsconfig.json +37 -13
- package/types/user.ts +1 -1
- package/utils/accessibility.ts +446 -446
- package/utils/animations/presets.ts +182 -0
- package/utils/animations/transitions.ts +62 -0
- package/utils/index.ts +63 -52
- package/utils/toast.ts +9 -2
- package/utils/validation.ts +4 -1
- package/utils/withAccessibility.tsx +272 -272
|
@@ -1,227 +1,224 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Integration tests for authentication and API flow
|
|
3
|
-
* Tests the complete flow from sign-in through authenticated API calls.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { render, waitFor
|
|
8
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
9
|
-
import { AuthProvider, useAuth, getAuthToken } from "@/hooks/useAuth";
|
|
10
|
-
import { api } from "@/services/api";
|
|
11
|
-
|
|
12
|
-
// Mock SecureStore
|
|
13
|
-
const mockSecureStore: Record<string, string> = {};
|
|
14
|
-
jest.mock("expo-secure-store", () => ({
|
|
15
|
-
getItemAsync: jest.fn((key: string) =>
|
|
16
|
-
Promise.resolve(mockSecureStore[key] || null)
|
|
17
|
-
),
|
|
18
|
-
setItemAsync: jest.fn((key: string, value: string) => {
|
|
19
|
-
mockSecureStore[key] = value;
|
|
20
|
-
return Promise.resolve();
|
|
21
|
-
}),
|
|
22
|
-
deleteItemAsync: jest.fn((key: string) => {
|
|
23
|
-
delete mockSecureStore[key];
|
|
24
|
-
return Promise.resolve();
|
|
25
|
-
}),
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
// Mock expo-router
|
|
29
|
-
jest.mock("expo-router", () => ({
|
|
30
|
-
router: {
|
|
31
|
-
replace: jest.fn(),
|
|
32
|
-
push: jest.fn(),
|
|
33
|
-
},
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
// Mock toast
|
|
37
|
-
jest.mock("@/utils/toast", () => ({
|
|
38
|
-
toast: {
|
|
39
|
-
success: jest.fn(),
|
|
40
|
-
error: jest.fn(),
|
|
41
|
-
info: jest.fn(),
|
|
42
|
-
},
|
|
43
|
-
handleApiError: jest.fn(),
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
|
-
// Test component that uses auth
|
|
47
|
-
function TestAuthComponent({
|
|
48
|
-
onAuthState,
|
|
49
|
-
}: {
|
|
50
|
-
onAuthState: (state: {
|
|
51
|
-
user: unknown;
|
|
52
|
-
isAuthenticated: boolean;
|
|
53
|
-
isLoading: boolean;
|
|
54
|
-
}) => void;
|
|
55
|
-
}) {
|
|
56
|
-
const auth = useAuth();
|
|
57
|
-
|
|
58
|
-
React.useEffect(() => {
|
|
59
|
-
onAuthState({
|
|
60
|
-
user: auth.user,
|
|
61
|
-
isAuthenticated: auth.isAuthenticated,
|
|
62
|
-
isLoading: auth.isLoading,
|
|
63
|
-
});
|
|
64
|
-
}, [auth.user, auth.isAuthenticated, auth.isLoading, onAuthState]);
|
|
65
|
-
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Wrapper with providers
|
|
70
|
-
function createWrapper() {
|
|
71
|
-
const queryClient = new QueryClient({
|
|
72
|
-
defaultOptions: {
|
|
73
|
-
queries: {
|
|
74
|
-
retry: false,
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return function Wrapper({ children }: { children: React.ReactNode }) {
|
|
80
|
-
return (
|
|
81
|
-
<QueryClientProvider client={queryClient}>
|
|
82
|
-
<AuthProvider>{children}</AuthProvider>
|
|
83
|
-
</QueryClientProvider>
|
|
84
|
-
);
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
describe("Auth + API Integration", () => {
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
// Clear mock storage
|
|
91
|
-
Object.keys(mockSecureStore).forEach((key) => delete mockSecureStore[key]);
|
|
92
|
-
jest.clearAllMocks();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("Authentication Flow", () => {
|
|
96
|
-
it("starts with loading state then transitions to unauthenticated", async () => {
|
|
97
|
-
const states:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
expect(api).
|
|
221
|
-
expect(typeof api.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Integration tests for authentication and API flow
|
|
3
|
+
* Tests the complete flow from sign-in through authenticated API calls.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { render, waitFor } from "@testing-library/react-native";
|
|
8
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
9
|
+
import { AuthProvider, useAuth, getAuthToken } from "@/hooks/useAuth";
|
|
10
|
+
import { api } from "@/services/api";
|
|
11
|
+
|
|
12
|
+
// Mock SecureStore
|
|
13
|
+
const mockSecureStore: Record<string, string> = {};
|
|
14
|
+
jest.mock("expo-secure-store", () => ({
|
|
15
|
+
getItemAsync: jest.fn((key: string) =>
|
|
16
|
+
Promise.resolve(mockSecureStore[key] || null)
|
|
17
|
+
),
|
|
18
|
+
setItemAsync: jest.fn((key: string, value: string) => {
|
|
19
|
+
mockSecureStore[key] = value;
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
}),
|
|
22
|
+
deleteItemAsync: jest.fn((key: string) => {
|
|
23
|
+
delete mockSecureStore[key];
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock expo-router
|
|
29
|
+
jest.mock("expo-router", () => ({
|
|
30
|
+
router: {
|
|
31
|
+
replace: jest.fn(),
|
|
32
|
+
push: jest.fn(),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// Mock toast
|
|
37
|
+
jest.mock("@/utils/toast", () => ({
|
|
38
|
+
toast: {
|
|
39
|
+
success: jest.fn(),
|
|
40
|
+
error: jest.fn(),
|
|
41
|
+
info: jest.fn(),
|
|
42
|
+
},
|
|
43
|
+
handleApiError: jest.fn(),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Test component that uses auth
|
|
47
|
+
function TestAuthComponent({
|
|
48
|
+
onAuthState,
|
|
49
|
+
}: {
|
|
50
|
+
onAuthState: (state: {
|
|
51
|
+
user: unknown;
|
|
52
|
+
isAuthenticated: boolean;
|
|
53
|
+
isLoading: boolean;
|
|
54
|
+
}) => void;
|
|
55
|
+
}) {
|
|
56
|
+
const auth = useAuth();
|
|
57
|
+
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
onAuthState({
|
|
60
|
+
user: auth.user,
|
|
61
|
+
isAuthenticated: auth.isAuthenticated,
|
|
62
|
+
isLoading: auth.isLoading,
|
|
63
|
+
});
|
|
64
|
+
}, [auth.user, auth.isAuthenticated, auth.isLoading, onAuthState]);
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Wrapper with providers
|
|
70
|
+
function createWrapper() {
|
|
71
|
+
const queryClient = new QueryClient({
|
|
72
|
+
defaultOptions: {
|
|
73
|
+
queries: {
|
|
74
|
+
retry: false,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return function Wrapper({ children }: { children: React.ReactNode }) {
|
|
80
|
+
return (
|
|
81
|
+
<QueryClientProvider client={queryClient}>
|
|
82
|
+
<AuthProvider>{children}</AuthProvider>
|
|
83
|
+
</QueryClientProvider>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
describe("Auth + API Integration", () => {
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
// Clear mock storage
|
|
91
|
+
Object.keys(mockSecureStore).forEach((key) => delete mockSecureStore[key]);
|
|
92
|
+
jest.clearAllMocks();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("Authentication Flow", () => {
|
|
96
|
+
it("starts with loading state then transitions to unauthenticated", async () => {
|
|
97
|
+
const states: { isLoading: boolean; isAuthenticated: boolean }[] = [];
|
|
98
|
+
|
|
99
|
+
render(
|
|
100
|
+
<TestAuthComponent onAuthState={(state) => states.push(state)} />,
|
|
101
|
+
{ wrapper: createWrapper() }
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
await waitFor(() => {
|
|
105
|
+
expect(states.length).toBeGreaterThanOrEqual(2);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// First state should be loading
|
|
109
|
+
expect(states[0].isLoading).toBe(true);
|
|
110
|
+
|
|
111
|
+
// Final state should be not loading and not authenticated
|
|
112
|
+
const finalState = states[states.length - 1];
|
|
113
|
+
expect(finalState.isLoading).toBe(false);
|
|
114
|
+
expect(finalState.isAuthenticated).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("restores session from stored tokens", async () => {
|
|
118
|
+
// Pre-populate storage with valid tokens
|
|
119
|
+
const tokens = {
|
|
120
|
+
accessToken: "stored_access_token",
|
|
121
|
+
refreshToken: "stored_refresh_token",
|
|
122
|
+
expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour from now
|
|
123
|
+
};
|
|
124
|
+
const user = {
|
|
125
|
+
id: "1",
|
|
126
|
+
email: "stored@example.com",
|
|
127
|
+
name: "Stored User",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
mockSecureStore["auth_tokens"] = JSON.stringify(tokens);
|
|
131
|
+
mockSecureStore["auth_user"] = JSON.stringify(user);
|
|
132
|
+
|
|
133
|
+
let finalState: { user: unknown; isAuthenticated: boolean } | null = null;
|
|
134
|
+
|
|
135
|
+
render(
|
|
136
|
+
<TestAuthComponent
|
|
137
|
+
onAuthState={(state) => {
|
|
138
|
+
if (!state.isLoading) {
|
|
139
|
+
finalState = state;
|
|
140
|
+
}
|
|
141
|
+
}}
|
|
142
|
+
/>,
|
|
143
|
+
{ wrapper: createWrapper() }
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
await waitFor(() => {
|
|
147
|
+
expect(finalState).not.toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(finalState?.isAuthenticated).toBe(true);
|
|
151
|
+
expect(finalState?.user).toEqual(user);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("handles expired tokens by refreshing", async () => {
|
|
155
|
+
// Pre-populate storage with expired tokens
|
|
156
|
+
const tokens = {
|
|
157
|
+
accessToken: "expired_access_token",
|
|
158
|
+
refreshToken: "valid_refresh_token",
|
|
159
|
+
expiresAt: Date.now() - 1000, // Expired 1 second ago
|
|
160
|
+
};
|
|
161
|
+
const user = {
|
|
162
|
+
id: "1",
|
|
163
|
+
email: "test@example.com",
|
|
164
|
+
name: "Test User",
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
mockSecureStore["auth_tokens"] = JSON.stringify(tokens);
|
|
168
|
+
mockSecureStore["auth_user"] = JSON.stringify(user);
|
|
169
|
+
|
|
170
|
+
let finalState: { isAuthenticated: boolean } | null = null;
|
|
171
|
+
|
|
172
|
+
render(
|
|
173
|
+
<TestAuthComponent
|
|
174
|
+
onAuthState={(state) => {
|
|
175
|
+
if (!state.isLoading) {
|
|
176
|
+
finalState = state;
|
|
177
|
+
}
|
|
178
|
+
}}
|
|
179
|
+
/>,
|
|
180
|
+
{ wrapper: createWrapper() }
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await waitFor(
|
|
184
|
+
() => {
|
|
185
|
+
expect(finalState).not.toBeNull();
|
|
186
|
+
},
|
|
187
|
+
{ timeout: 3000 }
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Should be authenticated after token refresh (mock implementation always succeeds)
|
|
191
|
+
expect(finalState?.isAuthenticated).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("Token Management", () => {
|
|
196
|
+
it("getAuthToken returns null when no tokens stored", async () => {
|
|
197
|
+
const token = await getAuthToken();
|
|
198
|
+
expect(token).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("getAuthToken returns access token when stored", async () => {
|
|
202
|
+
const tokens = {
|
|
203
|
+
accessToken: "test_access_token",
|
|
204
|
+
refreshToken: "test_refresh_token",
|
|
205
|
+
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
mockSecureStore["auth_tokens"] = JSON.stringify(tokens);
|
|
209
|
+
|
|
210
|
+
const token = await getAuthToken();
|
|
211
|
+
expect(token).toBe("test_access_token");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("API Client Integration", () => {
|
|
216
|
+
it("api client exists and has required methods", () => {
|
|
217
|
+
expect(api).toBeDefined();
|
|
218
|
+
expect(typeof api.get).toBe("function");
|
|
219
|
+
expect(typeof api.post).toBe("function");
|
|
220
|
+
expect(typeof api.patch).toBe("function");
|
|
221
|
+
expect(typeof api.delete).toBe("function");
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|