@umituz/react-native-splash 1.6.3 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/lib/__tests__/mocks/expoLinearGradient.js +7 -0
- package/lib/__tests__/mocks/reactNative.js +16 -0
- package/lib/__tests__/setup.ts +57 -0
- package/lib/__tests__/utils/testUtils.tsx +86 -0
- package/lib/domain/entities/SplashOptions.ts +74 -0
- package/lib/index.ts +31 -0
- package/lib/presentation/components/SplashDecorations.tsx +56 -0
- package/lib/presentation/components/SplashErrorBoundary.tsx +63 -0
- package/lib/presentation/components/SplashLoading.tsx +74 -0
- package/lib/presentation/components/SplashLogo.tsx +80 -0
- package/lib/presentation/components/SplashScreen.tsx +175 -0
- package/lib/presentation/components/SplashTypography.tsx +72 -0
- package/lib/presentation/hooks/useSplash.ts +70 -0
- package/lib/presentation/utils/splashGradient.utils.ts +47 -0
- package/lib/types/expo-linear-gradient.d.ts +12 -0
- package/package.json +18 -5
- package/src/__tests__/SplashScreen.test.tsx +161 -0
- package/src/__tests__/accessibility/Accessibility.test.tsx +264 -0
- package/src/__tests__/basic/Basic.test.tsx +106 -0
- package/src/__tests__/basic/Simple.test.tsx +32 -0
- package/src/__tests__/edge-cases/EdgeCases.test.tsx +446 -0
- package/src/__tests__/integration/SplashScreen.integration.test.tsx +200 -0
- package/src/__tests__/mocks/expoLinearGradient.js +7 -0
- package/src/__tests__/mocks/reactNative.js +16 -0
- package/src/__tests__/performance/Performance.test.tsx +297 -0
- package/src/__tests__/setup.ts +57 -0
- package/src/__tests__/useSplash.test.tsx +123 -0
- package/src/__tests__/utils/testUtils.tsx +86 -0
- package/src/__tests__/visual/VisualRegression.test.tsx +338 -0
- package/src/domain/entities/SplashOptions.ts +7 -0
- package/src/index.ts +2 -0
- package/src/presentation/components/SplashDecorations.tsx +13 -5
- package/src/presentation/components/SplashErrorBoundary.tsx +63 -0
- package/src/presentation/components/SplashLoading.tsx +7 -5
- package/src/presentation/components/SplashLogo.tsx +4 -2
- package/src/presentation/components/SplashScreen.tsx +50 -30
- package/src/presentation/components/SplashTypography.tsx +6 -4
- package/src/presentation/hooks/useSplash.ts +70 -0
- package/src/presentation/utils/splashGradient.utils.ts +0 -0
- package/src/types/expo-linear-gradient.d.ts +12 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge Case Tests - Error Scenarios and Edge Cases
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent, act, createErrorComponent } from '../utils/testUtils';
|
|
7
|
+
import { SplashScreen } from '../../presentation/components/SplashScreen';
|
|
8
|
+
import { useSplash } from '../../presentation/hooks/useSplash';
|
|
9
|
+
|
|
10
|
+
describe('Edge Case Tests', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.useFakeTimers();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.useRealTimers();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Empty and Null Props', () => {
|
|
20
|
+
it('handles empty string props', () => {
|
|
21
|
+
const props = {
|
|
22
|
+
appName: '',
|
|
23
|
+
tagline: '',
|
|
24
|
+
loadingText: '',
|
|
25
|
+
visible: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
29
|
+
|
|
30
|
+
// Should render without crashing
|
|
31
|
+
expect(container).toBeTruthy();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('handles null props', () => {
|
|
35
|
+
const props = {
|
|
36
|
+
appName: null,
|
|
37
|
+
tagline: null,
|
|
38
|
+
loadingText: null,
|
|
39
|
+
visible: true,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
43
|
+
|
|
44
|
+
// Should render without crashing
|
|
45
|
+
expect(container).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('handles undefined props', () => {
|
|
49
|
+
const props = {
|
|
50
|
+
appName: undefined,
|
|
51
|
+
tagline: undefined,
|
|
52
|
+
loadingText: undefined,
|
|
53
|
+
visible: true,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
57
|
+
|
|
58
|
+
// Should render without crashing
|
|
59
|
+
expect(container).toBeTruthy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles missing props object', () => {
|
|
63
|
+
const { container } = render(<SplashScreen />);
|
|
64
|
+
|
|
65
|
+
// Should render with defaults
|
|
66
|
+
expect(container).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('Invalid Props', () => {
|
|
71
|
+
it('handles invalid gradient colors', () => {
|
|
72
|
+
const props = {
|
|
73
|
+
appName: 'Test App',
|
|
74
|
+
gradientColors: ['invalid-color', 'another-invalid'],
|
|
75
|
+
visible: true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
79
|
+
|
|
80
|
+
// Should render without crashing
|
|
81
|
+
expect(container).toBeTruthy();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('handles empty gradient colors array', () => {
|
|
85
|
+
const props = {
|
|
86
|
+
appName: 'Test App',
|
|
87
|
+
gradientColors: [],
|
|
88
|
+
visible: true,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
92
|
+
|
|
93
|
+
// Should render without crashing
|
|
94
|
+
expect(container).toBeTruthy();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('handles single color in gradient', () => {
|
|
98
|
+
const props = {
|
|
99
|
+
appName: 'Test App',
|
|
100
|
+
gradientColors: ['#FF0000'],
|
|
101
|
+
visible: true,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
105
|
+
|
|
106
|
+
// Should render without crashing
|
|
107
|
+
expect(container).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles negative minimum display time', () => {
|
|
111
|
+
const props = {
|
|
112
|
+
appName: 'Test App',
|
|
113
|
+
minimumDisplayTime: -1000,
|
|
114
|
+
visible: true,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
118
|
+
|
|
119
|
+
// Should render without crashing
|
|
120
|
+
expect(container).toBeTruthy();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('handles very large minimum display time', () => {
|
|
124
|
+
const props = {
|
|
125
|
+
appName: 'Test App',
|
|
126
|
+
minimumDisplayTime: Number.MAX_SAFE_INTEGER,
|
|
127
|
+
visible: true,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
131
|
+
|
|
132
|
+
// Should render without crashing
|
|
133
|
+
expect(container).toBeTruthy();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('Error Scenarios', () => {
|
|
138
|
+
it('handles renderLogo function throwing error', () => {
|
|
139
|
+
const props = {
|
|
140
|
+
appName: 'Test App',
|
|
141
|
+
renderLogo: createErrorComponent('Logo error'),
|
|
142
|
+
visible: true,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
146
|
+
|
|
147
|
+
// Should show error boundary fallback
|
|
148
|
+
expect(getByText('Something went wrong')).toBeTruthy();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('handles renderContent function throwing error', () => {
|
|
152
|
+
const props = {
|
|
153
|
+
appName: 'Test App',
|
|
154
|
+
renderContent: createErrorComponent('Content error'),
|
|
155
|
+
visible: true,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
159
|
+
|
|
160
|
+
// Should show error boundary fallback
|
|
161
|
+
expect(getByText('Something went wrong')).toBeTruthy();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('handles renderFooter function throwing error', () => {
|
|
165
|
+
const props = {
|
|
166
|
+
appName: 'Test App',
|
|
167
|
+
renderFooter: createErrorComponent('Footer error'),
|
|
168
|
+
visible: true,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
172
|
+
|
|
173
|
+
// Should show error boundary fallback
|
|
174
|
+
expect(getByText('Something went wrong')).toBeTruthy();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('handles onReady callback throwing error', () => {
|
|
178
|
+
const props = {
|
|
179
|
+
appName: 'Test App',
|
|
180
|
+
onReady: () => {
|
|
181
|
+
throw new Error('Callback error');
|
|
182
|
+
},
|
|
183
|
+
visible: true,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
187
|
+
|
|
188
|
+
act(() => {
|
|
189
|
+
jest.advanceTimersByTime(1500);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Should still render without crashing
|
|
193
|
+
expect(getByText('Test App')).toBeTruthy();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('handles async onReady callback rejection', async () => {
|
|
197
|
+
const props = {
|
|
198
|
+
appName: 'Test App',
|
|
199
|
+
onReady: async () => {
|
|
200
|
+
throw new Error('Async callback error');
|
|
201
|
+
},
|
|
202
|
+
visible: true,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
206
|
+
|
|
207
|
+
await act(async () => {
|
|
208
|
+
jest.advanceTimersByTime(1500);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Should still render without crashing
|
|
212
|
+
expect(getByText('Test App')).toBeTruthy();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('useSplash Hook Edge Cases', () => {
|
|
217
|
+
it('handles multiple markReady calls', () => {
|
|
218
|
+
const onReady = jest.fn();
|
|
219
|
+
const TestComponent = () => {
|
|
220
|
+
const { markReady } = useSplash({ onReady });
|
|
221
|
+
|
|
222
|
+
React.useEffect(() => {
|
|
223
|
+
markReady();
|
|
224
|
+
markReady();
|
|
225
|
+
markReady();
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
return <div>Test</div>;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
render(<TestComponent />);
|
|
232
|
+
|
|
233
|
+
// Should only call onReady once
|
|
234
|
+
expect(onReady).toHaveBeenCalledTimes(1);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('handles markReady after unmount', () => {
|
|
238
|
+
const onReady = jest.fn();
|
|
239
|
+
const TestComponent = () => {
|
|
240
|
+
const { markReady } = useSplash({ onReady });
|
|
241
|
+
|
|
242
|
+
React.useEffect(() => {
|
|
243
|
+
const timer = setTimeout(markReady, 1000);
|
|
244
|
+
return () => clearTimeout(timer);
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
return <div>Test</div>;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const { unmount } = render(<TestComponent />);
|
|
251
|
+
|
|
252
|
+
// Unmount before timer completes
|
|
253
|
+
unmount();
|
|
254
|
+
|
|
255
|
+
act(() => {
|
|
256
|
+
jest.advanceTimersByTime(1000);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Should not call onReady after unmount
|
|
260
|
+
expect(onReady).not.toHaveBeenCalled();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('handles zero minimum display time', () => {
|
|
264
|
+
const TestComponent = () => {
|
|
265
|
+
const { isVisible, markReady } = useSplash({
|
|
266
|
+
minimumDisplayTime: 0,
|
|
267
|
+
autoHide: true,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
React.useEffect(() => {
|
|
271
|
+
markReady();
|
|
272
|
+
}, []);
|
|
273
|
+
|
|
274
|
+
return isVisible ? <div>Visible</div> : <div>Hidden</div>;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const { getByText } = render(<TestComponent />);
|
|
278
|
+
|
|
279
|
+
// Should hide immediately
|
|
280
|
+
expect(getByText('Hidden')).toBeTruthy();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('Memory and Performance Edge Cases', () => {
|
|
285
|
+
it('handles very long app names', () => {
|
|
286
|
+
const longName = 'A'.repeat(1000);
|
|
287
|
+
const props = {
|
|
288
|
+
appName: longName,
|
|
289
|
+
visible: true,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
293
|
+
|
|
294
|
+
expect(getByText(longName)).toBeTruthy();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('handles very long taglines', () => {
|
|
298
|
+
const longTagline = 'B'.repeat(2000);
|
|
299
|
+
const props = {
|
|
300
|
+
appName: 'Test App',
|
|
301
|
+
tagline: longTagline,
|
|
302
|
+
visible: true,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
306
|
+
|
|
307
|
+
expect(getByText(longTagline)).toBeTruthy();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('handles many gradient colors', () => {
|
|
311
|
+
const manyColors = Array.from({ length: 100 }, (_, i) =>
|
|
312
|
+
`#${i.toString(16).padStart(6, '0')}`
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const props = {
|
|
316
|
+
appName: 'Test App',
|
|
317
|
+
gradientColors: manyColors,
|
|
318
|
+
visible: true,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
322
|
+
|
|
323
|
+
expect(container).toBeTruthy();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('Component Lifecycle Edge Cases', () => {
|
|
328
|
+
it('handles rapid prop changes', () => {
|
|
329
|
+
const { rerender } = render(
|
|
330
|
+
<SplashScreen appName="App 1" visible={true} />
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// Rapidly change props
|
|
334
|
+
for (let i = 2; i <= 100; i++) {
|
|
335
|
+
rerender(<SplashScreen appName={`App ${i}`} visible={i % 2 === 0} />);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Should not crash
|
|
339
|
+
expect(true).toBe(true);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('handles visibility toggle during timer', () => {
|
|
343
|
+
const onReady = jest.fn();
|
|
344
|
+
const { rerender } = render(
|
|
345
|
+
<SplashScreen
|
|
346
|
+
appName="Test App"
|
|
347
|
+
visible={true}
|
|
348
|
+
minimumDisplayTime={2000}
|
|
349
|
+
onReady={onReady}
|
|
350
|
+
/>
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Hide before timer completes
|
|
354
|
+
rerender(<SplashScreen appName="Test App" visible={false} />);
|
|
355
|
+
|
|
356
|
+
act(() => {
|
|
357
|
+
jest.advanceTimersByTime(2000);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Should not call onReady when hidden
|
|
361
|
+
expect(onReady).not.toHaveBeenCalled();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('handles component unmount during timer', () => {
|
|
365
|
+
const onReady = jest.fn();
|
|
366
|
+
const { unmount } = render(
|
|
367
|
+
<SplashScreen
|
|
368
|
+
appName="Test App"
|
|
369
|
+
visible={true}
|
|
370
|
+
minimumDisplayTime={2000}
|
|
371
|
+
onReady={onReady}
|
|
372
|
+
/>
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Unmount before timer completes
|
|
376
|
+
unmount();
|
|
377
|
+
|
|
378
|
+
act(() => {
|
|
379
|
+
jest.advanceTimersByTime(2000);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Should not call onReady after unmount
|
|
383
|
+
expect(onReady).not.toHaveBeenCalled();
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('Localization Edge Cases', () => {
|
|
388
|
+
it('handles missing localization keys', () => {
|
|
389
|
+
const props = {
|
|
390
|
+
appName: undefined,
|
|
391
|
+
tagline: undefined,
|
|
392
|
+
loadingText: undefined,
|
|
393
|
+
visible: true,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
397
|
+
|
|
398
|
+
// Should render with empty fallbacks
|
|
399
|
+
expect(container).toBeTruthy();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('handles special characters in text', () => {
|
|
403
|
+
const props = {
|
|
404
|
+
appName: 'App with émojis 🎉 and spéci@l ch@rs!',
|
|
405
|
+
tagline: 'Tàglîne wïth äccénts & spëcial chars',
|
|
406
|
+
visible: true,
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
410
|
+
|
|
411
|
+
expect(getByText('App with émojis 🎉 and spéci@l ch@rs!')).toBeTruthy();
|
|
412
|
+
expect(getByText('Tàglîne wïth äccénts & spëcial chars')).toBeTruthy();
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe('Theme Edge Cases', () => {
|
|
417
|
+
it('handles invalid color values', () => {
|
|
418
|
+
const props = {
|
|
419
|
+
appName: 'Test App',
|
|
420
|
+
textColor: 'not-a-color',
|
|
421
|
+
iconColor: 'also-not-a-color',
|
|
422
|
+
decorationColor: 'definitely-not-a-color',
|
|
423
|
+
visible: true,
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
427
|
+
|
|
428
|
+
// Should render without crashing
|
|
429
|
+
expect(container).toBeTruthy();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('handles transparent colors', () => {
|
|
433
|
+
const props = {
|
|
434
|
+
appName: 'Test App',
|
|
435
|
+
textColor: 'transparent',
|
|
436
|
+
backgroundColor: 'transparent',
|
|
437
|
+
visible: true,
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const { container } = render(<SplashScreen {...props} />);
|
|
441
|
+
|
|
442
|
+
// Should render without crashing
|
|
443
|
+
expect(container).toBeTruthy();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests - Complete Splash Screen Flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent, act } from '../utils/testUtils';
|
|
7
|
+
import { SplashScreen } from '../../presentation/components/SplashScreen';
|
|
8
|
+
import { useSplash } from '../../presentation/hooks/useSplash';
|
|
9
|
+
|
|
10
|
+
describe('SplashScreen Integration Tests', () => {
|
|
11
|
+
const defaultProps = {
|
|
12
|
+
appName: 'Integration Test App',
|
|
13
|
+
tagline: 'Testing complete flow',
|
|
14
|
+
visible: true,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.useFakeTimers();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.useRealTimers();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('completes full splash screen lifecycle', async () => {
|
|
26
|
+
const onReady = jest.fn();
|
|
27
|
+
const props = { ...defaultProps, onReady };
|
|
28
|
+
|
|
29
|
+
const { getByText, queryByText, rerender } = render(<SplashScreen {...props} />);
|
|
30
|
+
|
|
31
|
+
// Initial render
|
|
32
|
+
expect(getByText('Integration Test App')).toBeTruthy();
|
|
33
|
+
expect(getByText('Testing complete flow')).toBeTruthy();
|
|
34
|
+
expect(getByText('Loading...')).toBeTruthy();
|
|
35
|
+
|
|
36
|
+
// Wait for minimum display time
|
|
37
|
+
act(() => {
|
|
38
|
+
jest.advanceTimersByTime(1500);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(onReady).toHaveBeenCalledTimes(1);
|
|
42
|
+
|
|
43
|
+
// Hide splash screen
|
|
44
|
+
rerender(<SplashScreen {...props} visible={false} />);
|
|
45
|
+
|
|
46
|
+
expect(queryByText('Integration Test App')).toBeFalsy();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('handles custom render functions throughout lifecycle', () => {
|
|
50
|
+
const CustomLogo = () => <div testID="custom-logo">Custom Logo</div>;
|
|
51
|
+
const CustomContent = () => <div testID="custom-content">Custom Content</div>;
|
|
52
|
+
const CustomFooter = () => <div testID="custom-footer">Custom Footer</div>;
|
|
53
|
+
|
|
54
|
+
const props = {
|
|
55
|
+
...defaultProps,
|
|
56
|
+
renderLogo: () => <CustomLogo />,
|
|
57
|
+
renderContent: () => <CustomContent />,
|
|
58
|
+
renderFooter: () => <CustomFooter />,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
62
|
+
|
|
63
|
+
expect(getByTestId('custom-logo')).toBeTruthy();
|
|
64
|
+
expect(getByTestId('custom-content')).toBeTruthy();
|
|
65
|
+
expect(getByTestId('custom-footer')).toBeTruthy();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('integrates with useSplash hook properly', () => {
|
|
69
|
+
const TestComponent = () => {
|
|
70
|
+
const { isVisible, markReady } = useSplash({
|
|
71
|
+
minimumDisplayTime: 1000,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<SplashScreen
|
|
76
|
+
{...defaultProps}
|
|
77
|
+
visible={isVisible}
|
|
78
|
+
onReady={markReady}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const { getByText, queryByText } = render(<TestComponent />);
|
|
84
|
+
|
|
85
|
+
expect(getByText('Integration Test App')).toBeTruthy();
|
|
86
|
+
|
|
87
|
+
act(() => {
|
|
88
|
+
jest.advanceTimersByTime(1000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Should auto-hide after markReady is called
|
|
92
|
+
act(() => {
|
|
93
|
+
jest.advanceTimersByTime(1000);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(queryByText('Integration Test App')).toBeFalsy();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('handles gradient and color theming integration', () => {
|
|
100
|
+
const props = {
|
|
101
|
+
...defaultProps,
|
|
102
|
+
gradientColors: ['#FF0000', '#00FF00', '#0000FF'],
|
|
103
|
+
textColor: '#FFFFFF',
|
|
104
|
+
iconColor: '#FFFF00',
|
|
105
|
+
decorationColor: 'rgba(255, 255, 255, 0.1)',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const { getByTestId } = render(<SplashScreen {...props} />);
|
|
109
|
+
|
|
110
|
+
expect(getByTestId('linear-gradient')).toBeTruthy();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('manages error states across components', () => {
|
|
114
|
+
const ErrorComponent = () => {
|
|
115
|
+
throw new Error('Test error');
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const props = {
|
|
119
|
+
...defaultProps,
|
|
120
|
+
renderLogo: () => <ErrorComponent />,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
124
|
+
|
|
125
|
+
// Should show error boundary fallback
|
|
126
|
+
expect(getByText('Something went wrong')).toBeTruthy();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles responsive design with safe areas', () => {
|
|
130
|
+
const props = {
|
|
131
|
+
...defaultProps,
|
|
132
|
+
showLoading: true,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
136
|
+
|
|
137
|
+
// Should render with proper spacing for safe areas
|
|
138
|
+
expect(getByText('Integration Test App')).toBeTruthy();
|
|
139
|
+
expect(getByText('Loading...')).toBeTruthy();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('maintains performance during complex scenarios', async () => {
|
|
143
|
+
const ComplexComponent = () => {
|
|
144
|
+
const [count, setCount] = React.useState(0);
|
|
145
|
+
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
const interval = setInterval(() => {
|
|
148
|
+
setCount(c => c + 1);
|
|
149
|
+
}, 100);
|
|
150
|
+
|
|
151
|
+
return () => clearInterval(interval);
|
|
152
|
+
}, []);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<SplashScreen
|
|
156
|
+
{...defaultProps}
|
|
157
|
+
renderContent={() => <div>Count: {count}</div>}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const { measureRenderTime } = require('../utils/testUtils');
|
|
163
|
+
const renderTime = await measureRenderTime(<ComplexComponent />);
|
|
164
|
+
|
|
165
|
+
// Should render within reasonable time
|
|
166
|
+
expect(renderTime).toBeLessThan(100); // 100ms
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('handles localization integration', () => {
|
|
170
|
+
const props = {
|
|
171
|
+
appName: undefined, // Will use localization
|
|
172
|
+
tagline: undefined, // Will use localization
|
|
173
|
+
loadingText: undefined, // Will use localization
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const { getByText } = render(<SplashScreen {...props} />);
|
|
177
|
+
|
|
178
|
+
// Should show fallback text from localization mock
|
|
179
|
+
expect(getByText('')).toBeTruthy(); // Empty fallback
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('manages memory correctly during mount/unmount cycles', () => {
|
|
183
|
+
const { trackMemoryUsage } = require('../utils/testUtils');
|
|
184
|
+
const memoryTracker = trackMemoryUsage();
|
|
185
|
+
|
|
186
|
+
const { unmount } = render(<SplashScreen {...defaultProps} />);
|
|
187
|
+
|
|
188
|
+
// Force garbage collection if available
|
|
189
|
+
if (global.gc) {
|
|
190
|
+
global.gc();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const memoryIncrease = memoryTracker.getMemoryIncrease();
|
|
194
|
+
|
|
195
|
+
// Memory increase should be minimal
|
|
196
|
+
expect(memoryIncrease).toBeLessThan(1024 * 1024); // 1MB
|
|
197
|
+
|
|
198
|
+
unmount();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Mocks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const React = require('react');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
View: ({ children, testID, style }) => React.createElement('View', { testID, style }, children),
|
|
9
|
+
Text: ({ children, testID, style }) => React.createElement('Text', { testID, style }, children),
|
|
10
|
+
StyleSheet: {
|
|
11
|
+
create: jest.fn((styles) => styles),
|
|
12
|
+
flatten: jest.fn((style) => style)
|
|
13
|
+
},
|
|
14
|
+
Dimensions: { get: jest.fn(() => ({ width: 375, height: 667 })) },
|
|
15
|
+
LinearGradient: ({ children, testID, style }) => React.createElement('LinearGradient', { testID, style }, children),
|
|
16
|
+
};
|