@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,362 +1,385 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Performance tests for VirtualizedList component
|
|
3
|
-
* Tests render times and memory usage with large datasets.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { render } from "@testing-library/react-native";
|
|
8
|
-
import { View, Text } from "react-native";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Generate test data
|
|
33
|
-
*/
|
|
34
|
-
function generateTestData(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Performance tests for VirtualizedList component
|
|
3
|
+
* Tests render times and memory usage with large datasets.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { render } from "@testing-library/react-native";
|
|
8
|
+
import { View, Text } from "react-native";
|
|
9
|
+
|
|
10
|
+
import { VirtualizedList } from "@/components/ui/VirtualizedList";
|
|
11
|
+
|
|
12
|
+
// Mock FlashList since it requires native modules
|
|
13
|
+
jest.mock("@shopify/flash-list", () => {
|
|
14
|
+
const { FlatList } = require("react-native");
|
|
15
|
+
return {
|
|
16
|
+
FlashList: FlatList,
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Mock useTheme
|
|
21
|
+
jest.mock("@/hooks/useTheme", () => ({
|
|
22
|
+
useTheme: () => ({
|
|
23
|
+
isDark: false,
|
|
24
|
+
mode: "light",
|
|
25
|
+
isLoaded: true,
|
|
26
|
+
setMode: jest.fn(),
|
|
27
|
+
toggleTheme: jest.fn(),
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generate test data
|
|
33
|
+
*/
|
|
34
|
+
function generateTestData(
|
|
35
|
+
count: number
|
|
36
|
+
): { id: string; title: string; value: number }[] {
|
|
37
|
+
return Array.from({ length: count }, (_, index) => ({
|
|
38
|
+
id: `item-${index}`,
|
|
39
|
+
title: `Item ${index}`,
|
|
40
|
+
value: Math.random() * 1000,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Simple render item component
|
|
46
|
+
*/
|
|
47
|
+
function TestItem({
|
|
48
|
+
item,
|
|
49
|
+
}: {
|
|
50
|
+
item: { id: string; title: string; value: number };
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<View testID={`item-${item.id}`} style={{ height: 50, padding: 10 }}>
|
|
54
|
+
<Text>{item.title}</Text>
|
|
55
|
+
<Text>{item.value.toFixed(2)}</Text>
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Performance thresholds (in milliseconds)
|
|
62
|
+
*/
|
|
63
|
+
const PERFORMANCE_THRESHOLDS = {
|
|
64
|
+
SMALL_LIST_RENDER: 100, // 100 items
|
|
65
|
+
MEDIUM_LIST_RENDER: 200, // 1000 items
|
|
66
|
+
LARGE_LIST_RENDER: 500, // 10000 items
|
|
67
|
+
INITIAL_RENDER: 50, // Initial render without data
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
describe("VirtualizedList Performance", () => {
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
jest.clearAllMocks();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Render Time", () => {
|
|
76
|
+
it("renders empty list within threshold", () => {
|
|
77
|
+
const startTime = performance.now();
|
|
78
|
+
|
|
79
|
+
render(
|
|
80
|
+
<VirtualizedList
|
|
81
|
+
data={[]}
|
|
82
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
83
|
+
keyExtractor={(item) => item.id}
|
|
84
|
+
estimatedItemSize={50}
|
|
85
|
+
emptyMessage="No items"
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const renderTime = performance.now() - startTime;
|
|
90
|
+
|
|
91
|
+
expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.INITIAL_RENDER);
|
|
92
|
+
console.log(`Empty list render time: ${renderTime.toFixed(2)}ms`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("renders small list (100 items) within threshold", () => {
|
|
96
|
+
const data = generateTestData(100);
|
|
97
|
+
const startTime = performance.now();
|
|
98
|
+
|
|
99
|
+
render(
|
|
100
|
+
<VirtualizedList
|
|
101
|
+
data={data}
|
|
102
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
103
|
+
keyExtractor={(item) => item.id}
|
|
104
|
+
estimatedItemSize={50}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const renderTime = performance.now() - startTime;
|
|
109
|
+
|
|
110
|
+
expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.SMALL_LIST_RENDER);
|
|
111
|
+
console.log(
|
|
112
|
+
`Small list (100 items) render time: ${renderTime.toFixed(2)}ms`
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("renders medium list (1000 items) within threshold", () => {
|
|
117
|
+
const data = generateTestData(1000);
|
|
118
|
+
const startTime = performance.now();
|
|
119
|
+
|
|
120
|
+
render(
|
|
121
|
+
<VirtualizedList
|
|
122
|
+
data={data}
|
|
123
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
124
|
+
keyExtractor={(item) => item.id}
|
|
125
|
+
estimatedItemSize={50}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const renderTime = performance.now() - startTime;
|
|
130
|
+
|
|
131
|
+
expect(renderTime).toBeLessThan(
|
|
132
|
+
PERFORMANCE_THRESHOLDS.MEDIUM_LIST_RENDER
|
|
133
|
+
);
|
|
134
|
+
console.log(
|
|
135
|
+
`Medium list (1000 items) render time: ${renderTime.toFixed(2)}ms`
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("renders large list (10000 items) within threshold", () => {
|
|
140
|
+
const data = generateTestData(10000);
|
|
141
|
+
const startTime = performance.now();
|
|
142
|
+
|
|
143
|
+
render(
|
|
144
|
+
<VirtualizedList
|
|
145
|
+
data={data}
|
|
146
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
147
|
+
keyExtractor={(item) => item.id}
|
|
148
|
+
estimatedItemSize={50}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const renderTime = performance.now() - startTime;
|
|
153
|
+
|
|
154
|
+
expect(renderTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LARGE_LIST_RENDER);
|
|
155
|
+
console.log(
|
|
156
|
+
`Large list (10000 items) render time: ${renderTime.toFixed(2)}ms`
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("Re-render Performance", () => {
|
|
162
|
+
it("handles data updates efficiently", () => {
|
|
163
|
+
const initialData = generateTestData(500);
|
|
164
|
+
|
|
165
|
+
const { rerender } = render(
|
|
166
|
+
<VirtualizedList
|
|
167
|
+
data={initialData}
|
|
168
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
169
|
+
keyExtractor={(item) => item.id}
|
|
170
|
+
estimatedItemSize={50}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Measure re-render with new data
|
|
175
|
+
const newData = generateTestData(500);
|
|
176
|
+
const startTime = performance.now();
|
|
177
|
+
|
|
178
|
+
rerender(
|
|
179
|
+
<VirtualizedList
|
|
180
|
+
data={newData}
|
|
181
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
182
|
+
keyExtractor={(item) => item.id}
|
|
183
|
+
estimatedItemSize={50}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const rerenderTime = performance.now() - startTime;
|
|
188
|
+
|
|
189
|
+
// Re-render should be fast since only visible items are rendered
|
|
190
|
+
expect(rerenderTime).toBeLessThan(100);
|
|
191
|
+
console.log(`Re-render time (500 items): ${rerenderTime.toFixed(2)}ms`);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("handles appending items efficiently", () => {
|
|
195
|
+
const initialData = generateTestData(100);
|
|
196
|
+
|
|
197
|
+
const { rerender } = render(
|
|
198
|
+
<VirtualizedList
|
|
199
|
+
data={initialData}
|
|
200
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
201
|
+
keyExtractor={(item) => item.id}
|
|
202
|
+
estimatedItemSize={50}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Append 100 more items
|
|
207
|
+
const appendedData = [
|
|
208
|
+
...initialData,
|
|
209
|
+
...generateTestData(100).map((item, i) => ({
|
|
210
|
+
...item,
|
|
211
|
+
id: `appended-${i}`,
|
|
212
|
+
})),
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const startTime = performance.now();
|
|
216
|
+
|
|
217
|
+
rerender(
|
|
218
|
+
<VirtualizedList
|
|
219
|
+
data={appendedData}
|
|
220
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
221
|
+
keyExtractor={(item) => item.id}
|
|
222
|
+
estimatedItemSize={50}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const appendTime = performance.now() - startTime;
|
|
227
|
+
|
|
228
|
+
expect(appendTime).toBeLessThan(50);
|
|
229
|
+
console.log(`Append time (100 items): ${appendTime.toFixed(2)}ms`);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("Memory Efficiency", () => {
|
|
234
|
+
it("maintains stable render count with static data", () => {
|
|
235
|
+
let renderCount = 0;
|
|
236
|
+
|
|
237
|
+
const CountingItem = ({
|
|
238
|
+
item,
|
|
239
|
+
}: {
|
|
240
|
+
item: { id: string; title: string; value: number };
|
|
241
|
+
}) => {
|
|
242
|
+
renderCount++;
|
|
243
|
+
return <TestItem item={item} />;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const data = generateTestData(100);
|
|
247
|
+
|
|
248
|
+
const { rerender } = render(
|
|
249
|
+
<VirtualizedList
|
|
250
|
+
data={data}
|
|
251
|
+
renderItem={({ item }) => <CountingItem item={item} />}
|
|
252
|
+
keyExtractor={(item) => item.id}
|
|
253
|
+
estimatedItemSize={50}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const initialRenderCount = renderCount;
|
|
258
|
+
|
|
259
|
+
// Re-render with same data
|
|
260
|
+
rerender(
|
|
261
|
+
<VirtualizedList
|
|
262
|
+
data={data}
|
|
263
|
+
renderItem={({ item }) => <CountingItem item={item} />}
|
|
264
|
+
keyExtractor={(item) => item.id}
|
|
265
|
+
estimatedItemSize={50}
|
|
266
|
+
/>
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// With proper memoization, render count should not double
|
|
270
|
+
// Allow some flexibility for visible items being re-rendered
|
|
271
|
+
expect(renderCount).toBeLessThan(initialRenderCount * 2);
|
|
272
|
+
console.log(
|
|
273
|
+
`Initial renders: ${initialRenderCount}, After re-render: ${renderCount}`
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("Props Validation", () => {
|
|
279
|
+
it("handles all size datasets without crashing", () => {
|
|
280
|
+
const sizes = [0, 1, 10, 100, 1000, 5000];
|
|
281
|
+
|
|
282
|
+
for (const size of sizes) {
|
|
283
|
+
const data = generateTestData(size);
|
|
284
|
+
|
|
285
|
+
expect(() => {
|
|
286
|
+
render(
|
|
287
|
+
<VirtualizedList
|
|
288
|
+
data={data}
|
|
289
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
290
|
+
keyExtractor={(item) => item.id}
|
|
291
|
+
estimatedItemSize={50}
|
|
292
|
+
/>
|
|
293
|
+
);
|
|
294
|
+
}).not.toThrow();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("handles undefined/null items gracefully", () => {
|
|
299
|
+
const data = [
|
|
300
|
+
{ id: "1", title: "Item 1", value: 100 },
|
|
301
|
+
{ id: "2", title: "Item 2", value: 200 },
|
|
302
|
+
];
|
|
303
|
+
|
|
304
|
+
expect(() => {
|
|
305
|
+
render(
|
|
306
|
+
<VirtualizedList
|
|
307
|
+
data={data}
|
|
308
|
+
renderItem={({ item }) => (item ? <TestItem item={item} /> : null)}
|
|
309
|
+
keyExtractor={(item) => item?.id || "unknown"}
|
|
310
|
+
estimatedItemSize={50}
|
|
311
|
+
/>
|
|
312
|
+
);
|
|
313
|
+
}).not.toThrow();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("Performance Metrics Summary", () => {
|
|
319
|
+
it("logs performance summary", () => {
|
|
320
|
+
const metrics = {
|
|
321
|
+
emptyList: 0,
|
|
322
|
+
smallList: 0,
|
|
323
|
+
mediumList: 0,
|
|
324
|
+
largeList: 0,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Empty list
|
|
328
|
+
let start = performance.now();
|
|
329
|
+
render(
|
|
330
|
+
<VirtualizedList
|
|
331
|
+
data={[]}
|
|
332
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
333
|
+
keyExtractor={(item) => item.id}
|
|
334
|
+
estimatedItemSize={50}
|
|
335
|
+
/>
|
|
336
|
+
);
|
|
337
|
+
metrics.emptyList = performance.now() - start;
|
|
338
|
+
|
|
339
|
+
// Small list
|
|
340
|
+
start = performance.now();
|
|
341
|
+
render(
|
|
342
|
+
<VirtualizedList
|
|
343
|
+
data={generateTestData(100)}
|
|
344
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
345
|
+
keyExtractor={(item) => item.id}
|
|
346
|
+
estimatedItemSize={50}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
metrics.smallList = performance.now() - start;
|
|
350
|
+
|
|
351
|
+
// Medium list
|
|
352
|
+
start = performance.now();
|
|
353
|
+
render(
|
|
354
|
+
<VirtualizedList
|
|
355
|
+
data={generateTestData(1000)}
|
|
356
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
357
|
+
keyExtractor={(item) => item.id}
|
|
358
|
+
estimatedItemSize={50}
|
|
359
|
+
/>
|
|
360
|
+
);
|
|
361
|
+
metrics.mediumList = performance.now() - start;
|
|
362
|
+
|
|
363
|
+
// Large list
|
|
364
|
+
start = performance.now();
|
|
365
|
+
render(
|
|
366
|
+
<VirtualizedList
|
|
367
|
+
data={generateTestData(10000)}
|
|
368
|
+
renderItem={({ item }) => <TestItem item={item} />}
|
|
369
|
+
keyExtractor={(item) => item.id}
|
|
370
|
+
estimatedItemSize={50}
|
|
371
|
+
/>
|
|
372
|
+
);
|
|
373
|
+
metrics.largeList = performance.now() - start;
|
|
374
|
+
|
|
375
|
+
console.log("\n=== VirtualizedList Performance Summary ===");
|
|
376
|
+
console.log(`Empty list: ${metrics.emptyList.toFixed(2)}ms`);
|
|
377
|
+
console.log(`Small (100): ${metrics.smallList.toFixed(2)}ms`);
|
|
378
|
+
console.log(`Medium (1000): ${metrics.mediumList.toFixed(2)}ms`);
|
|
379
|
+
console.log(`Large (10000): ${metrics.largeList.toFixed(2)}ms`);
|
|
380
|
+
console.log("==========================================\n");
|
|
381
|
+
|
|
382
|
+
// This test always passes, it's just for logging
|
|
383
|
+
expect(true).toBe(true);
|
|
384
|
+
});
|
|
385
|
+
});
|