@umituz/react-native-splash 1.6.4 → 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 +43 -26
- 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,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Tests - Memory Leak Prevention
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, act, trackMemoryUsage } from '../utils/testUtils';
|
|
7
|
+
import { SplashScreen } from '../../presentation/components/SplashScreen';
|
|
8
|
+
import { useSplash } from '../../presentation/hooks/useSplash';
|
|
9
|
+
|
|
10
|
+
describe('Performance Tests', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.useFakeTimers();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.useRealTimers();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Memory Leak Prevention', () => {
|
|
20
|
+
it('prevents memory leaks with timer cleanup', () => {
|
|
21
|
+
const { trackMemoryUsage } = require('../utils/testUtils');
|
|
22
|
+
const memoryTracker = trackMemoryUsage();
|
|
23
|
+
|
|
24
|
+
const components = [];
|
|
25
|
+
|
|
26
|
+
// Create and destroy many components
|
|
27
|
+
for (let i = 0; i < 100; i++) {
|
|
28
|
+
const { unmount } = render(
|
|
29
|
+
<SplashScreen
|
|
30
|
+
appName={`App ${i}`}
|
|
31
|
+
visible={true}
|
|
32
|
+
minimumDisplayTime={1000}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
components.push(unmount);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Unmount all components
|
|
39
|
+
components.forEach(unmount => unmount());
|
|
40
|
+
|
|
41
|
+
// Force garbage collection if available
|
|
42
|
+
if (global.gc) {
|
|
43
|
+
global.gc();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const memoryIncrease = memoryTracker.getMemoryIncrease();
|
|
47
|
+
|
|
48
|
+
// Memory increase should be minimal after cleanup
|
|
49
|
+
expect(memoryIncrease).toBeLessThan(5 * 1024 * 1024); // 5MB
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('cleans up useSplash timers properly', () => {
|
|
53
|
+
const TestComponent = () => {
|
|
54
|
+
const { isVisible, markReady } = useSplash({
|
|
55
|
+
minimumDisplayTime: 5000,
|
|
56
|
+
autoHide: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<SplashScreen
|
|
61
|
+
appName="Test App"
|
|
62
|
+
visible={isVisible}
|
|
63
|
+
onReady={markReady}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const { unmount } = render(<TestComponent />);
|
|
69
|
+
|
|
70
|
+
// Component should be unmounted before timer completes
|
|
71
|
+
unmount();
|
|
72
|
+
|
|
73
|
+
// Should not cause memory leaks or timer issues
|
|
74
|
+
expect(true).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('handles rapid mount/unmount cycles', () => {
|
|
78
|
+
const { trackMemoryUsage } = require('../utils/testUtils');
|
|
79
|
+
const memoryTracker = trackMemoryUsage();
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < 50; i++) {
|
|
82
|
+
const { unmount } = render(
|
|
83
|
+
<SplashScreen
|
|
84
|
+
appName="Rapid Test"
|
|
85
|
+
visible={true}
|
|
86
|
+
minimumDisplayTime={100}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Immediately unmount
|
|
91
|
+
unmount();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (global.gc) {
|
|
95
|
+
global.gc();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const memoryIncrease = memoryTracker.getMemoryIncrease();
|
|
99
|
+
expect(memoryIncrease).toBeLessThan(2 * 1024 * 1024); // 2MB
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('Render Performance', () => {
|
|
104
|
+
it('renders quickly with basic props', async () => {
|
|
105
|
+
const { measureRenderTime } = require('../utils/testUtils');
|
|
106
|
+
|
|
107
|
+
const renderTime = await measureRenderTime(
|
|
108
|
+
<SplashScreen
|
|
109
|
+
appName="Performance Test"
|
|
110
|
+
visible={true}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(renderTime).toBeLessThan(50); // 50ms
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('renders efficiently with complex props', async () => {
|
|
118
|
+
const { measureRenderTime } = require('../utils/testUtils');
|
|
119
|
+
|
|
120
|
+
const ComplexSplash = () => (
|
|
121
|
+
<SplashScreen
|
|
122
|
+
appName="Complex App"
|
|
123
|
+
tagline="Complex tagline with lots of text"
|
|
124
|
+
gradientColors={['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF']}
|
|
125
|
+
textColor="#FFFFFF"
|
|
126
|
+
iconColor="#FFFF00"
|
|
127
|
+
decorationColor="rgba(255, 255, 255, 0.1)"
|
|
128
|
+
showLoading={true}
|
|
129
|
+
minimumDisplayTime={2000}
|
|
130
|
+
renderLogo={() => <div>Complex Logo</div>}
|
|
131
|
+
renderContent={() => <div>Complex Content</div>}
|
|
132
|
+
renderFooter={() => <div>Complex Footer</div>}
|
|
133
|
+
visible={true}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const renderTime = await measureRenderTime(<ComplexSplash />);
|
|
138
|
+
|
|
139
|
+
expect(renderTime).toBeLessThan(100); // 100ms
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('handles prop updates efficiently', async () => {
|
|
143
|
+
const { measureRenderTime } = require('../utils/testUtils');
|
|
144
|
+
|
|
145
|
+
let props = {
|
|
146
|
+
appName: "Initial App",
|
|
147
|
+
visible: true,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const initialRenderTime = await measureRenderTime(
|
|
151
|
+
<SplashScreen {...props} />
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Update props
|
|
155
|
+
props = {
|
|
156
|
+
...props,
|
|
157
|
+
appName: "Updated App",
|
|
158
|
+
textColor: "#FF0000",
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const updateRenderTime = await measureRenderTime(
|
|
162
|
+
<SplashScreen {...props} />
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(initialRenderTime).toBeLessThan(50);
|
|
166
|
+
expect(updateRenderTime).toBeLessThan(30); // Updates should be faster
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('Timer Performance', () => {
|
|
171
|
+
it('handles multiple timers efficiently', () => {
|
|
172
|
+
const onReady = jest.fn();
|
|
173
|
+
|
|
174
|
+
const { unmount } = render(
|
|
175
|
+
<SplashScreen
|
|
176
|
+
appName="Timer Test"
|
|
177
|
+
visible={true}
|
|
178
|
+
minimumDisplayTime={1000}
|
|
179
|
+
onReady={onReady}
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Advance timers partially
|
|
184
|
+
act(() => {
|
|
185
|
+
jest.advanceTimersByTime(500);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(onReady).not.toHaveBeenCalled();
|
|
189
|
+
|
|
190
|
+
// Complete timers
|
|
191
|
+
act(() => {
|
|
192
|
+
jest.advanceTimersByTime(500);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(onReady).toHaveBeenCalledTimes(1);
|
|
196
|
+
|
|
197
|
+
unmount();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('prevents timer conflicts', () => {
|
|
201
|
+
const onReady1 = jest.fn();
|
|
202
|
+
const onReady2 = jest.fn();
|
|
203
|
+
|
|
204
|
+
const { unmount: unmount1 } = render(
|
|
205
|
+
<SplashScreen
|
|
206
|
+
appName="Timer Test 1"
|
|
207
|
+
visible={true}
|
|
208
|
+
minimumDisplayTime={1000}
|
|
209
|
+
onReady={onReady1}
|
|
210
|
+
/>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const { unmount: unmount2 } = render(
|
|
214
|
+
<SplashScreen
|
|
215
|
+
appName="Timer Test 2"
|
|
216
|
+
visible={true}
|
|
217
|
+
minimumDisplayTime={1500}
|
|
218
|
+
onReady={onReady2}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
act(() => {
|
|
223
|
+
jest.advanceTimersByTime(1000);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(onReady1).toHaveBeenCalledTimes(1);
|
|
227
|
+
expect(onReady2).not.toHaveBeenCalled();
|
|
228
|
+
|
|
229
|
+
act(() => {
|
|
230
|
+
jest.advanceTimersByTime(500);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(onReady2).toHaveBeenCalledTimes(1);
|
|
234
|
+
|
|
235
|
+
unmount1();
|
|
236
|
+
unmount2();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('Memory Usage Patterns', () => {
|
|
241
|
+
it('maintains stable memory usage during extended operation', () => {
|
|
242
|
+
const { trackMemoryUsage } = require('../utils/testUtils');
|
|
243
|
+
const memoryTracker = trackMemoryUsage();
|
|
244
|
+
|
|
245
|
+
let currentProps = {
|
|
246
|
+
appName: "Base App",
|
|
247
|
+
visible: true,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const { rerender } = render(<SplashScreen {...currentProps} />);
|
|
251
|
+
|
|
252
|
+
// Simulate extended operation with prop changes
|
|
253
|
+
for (let i = 0; i < 100; i++) {
|
|
254
|
+
currentProps = {
|
|
255
|
+
...currentProps,
|
|
256
|
+
appName: `App ${i}`,
|
|
257
|
+
tagline: i % 2 === 0 ? `Tagline ${i}` : undefined,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
rerender(<SplashScreen {...currentProps} />);
|
|
261
|
+
|
|
262
|
+
if (i % 10 === 0 && global.gc) {
|
|
263
|
+
global.gc();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const memoryIncrease = memoryTracker.getMemoryIncrease();
|
|
268
|
+
expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024); // 10MB
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('handles large prop objects efficiently', () => {
|
|
272
|
+
const largeGradientColors = Array.from({ length: 100 }, (_, i) =>
|
|
273
|
+
`#${i.toString(16).padStart(6, '0')}`
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const { trackMemoryUsage } = require('../utils/testUtils');
|
|
277
|
+
const memoryTracker = trackMemoryUsage();
|
|
278
|
+
|
|
279
|
+
const { unmount } = render(
|
|
280
|
+
<SplashScreen
|
|
281
|
+
appName="Large Props Test"
|
|
282
|
+
gradientColors={largeGradientColors}
|
|
283
|
+
visible={true}
|
|
284
|
+
/>
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
unmount();
|
|
288
|
+
|
|
289
|
+
if (global.gc) {
|
|
290
|
+
global.gc();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const memoryIncrease = memoryTracker.getMemoryIncrease();
|
|
294
|
+
expect(memoryIncrease).toBeLessThan(1 * 1024 * 1024); // 1MB
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Test Setup
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Mock React Native before anything else
|
|
6
|
+
jest.mock('react-native', () => {
|
|
7
|
+
const React = require('react');
|
|
8
|
+
return {
|
|
9
|
+
View: ({ children, testID, style }) => React.createElement('View', { testID, style }, children),
|
|
10
|
+
Text: ({ children, testID, style }) => React.createElement('Text', { testID, style }, children),
|
|
11
|
+
StyleSheet: {
|
|
12
|
+
create: jest.fn((styles) => styles),
|
|
13
|
+
flatten: jest.fn((style) => style)
|
|
14
|
+
},
|
|
15
|
+
Dimensions: { get: jest.fn(() => ({ width: 375, height: 667 })) },
|
|
16
|
+
LinearGradient: ({ children, testID, style }) => React.createElement('LinearGradient', { testID, style }, children),
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Mock expo-linear-gradient
|
|
21
|
+
jest.mock('expo-linear-gradient', () => {
|
|
22
|
+
const React = require('react');
|
|
23
|
+
return {
|
|
24
|
+
LinearGradient: ({ children, testID, style }) => React.createElement('LinearGradient', { testID, style }, children),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Mock design system atoms
|
|
29
|
+
jest.mock('@umituz/react-native-design-system-atoms', () => {
|
|
30
|
+
const React = require('react');
|
|
31
|
+
return {
|
|
32
|
+
AtomicIcon: ({ name, testID }) => React.createElement('AtomicIcon', { testID: testID || `icon-${name}`, name }),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Mock design system theme
|
|
37
|
+
jest.mock('@umituz/react-native-design-system-theme', () => ({
|
|
38
|
+
useAppDesignTokens: () => ({
|
|
39
|
+
colors: { primary: '#007AFF' },
|
|
40
|
+
spacing: { xl: 24, md: 16, xxxl: 48 },
|
|
41
|
+
}),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Mock localization
|
|
45
|
+
jest.mock('@umituz/react-native-localization', () => ({
|
|
46
|
+
useLocalization: () => ({
|
|
47
|
+
t: (key: string, fallback: string) => fallback || key,
|
|
48
|
+
}),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Mock safe area context
|
|
52
|
+
jest.mock('react-native-safe-area-context', () => ({
|
|
53
|
+
useSafeAreaInsets: () => ({ top: 44, bottom: 34, left: 0, right: 0 }),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Global test utilities
|
|
57
|
+
global.__DEV__ = true;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use Splash Hook Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { renderHook, act } from "@testing-library/react-hooks";
|
|
6
|
+
import { useSplash } from "../presentation/hooks/useSplash";
|
|
7
|
+
|
|
8
|
+
describe("useSplash", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.useFakeTimers();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
jest.useRealTimers();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("initializes with visible state", () => {
|
|
18
|
+
const { result } = renderHook(() => useSplash());
|
|
19
|
+
|
|
20
|
+
expect(result.current.isVisible).toBe(true);
|
|
21
|
+
expect(result.current.isReady).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("marks as ready and calls onReady", async () => {
|
|
25
|
+
const onReady = jest.fn();
|
|
26
|
+
const { result } = renderHook(() => useSplash({ onReady }));
|
|
27
|
+
|
|
28
|
+
await act(async () => {
|
|
29
|
+
await result.current.markReady();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(result.current.isReady).toBe(true);
|
|
33
|
+
expect(onReady).toHaveBeenCalledTimes(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("auto-hides after minimum display time", async () => {
|
|
37
|
+
const { result } = renderHook(() => useSplash({ minimumDisplayTime: 1000 }));
|
|
38
|
+
|
|
39
|
+
await act(async () => {
|
|
40
|
+
await result.current.markReady();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result.current.isVisible).toBe(true);
|
|
44
|
+
|
|
45
|
+
act(() => {
|
|
46
|
+
jest.advanceTimersByTime(1000);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(result.current.isVisible).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("does not auto-hide when autoHide is false", async () => {
|
|
53
|
+
const { result } = renderHook(() => useSplash({ autoHide: false }));
|
|
54
|
+
|
|
55
|
+
await act(async () => {
|
|
56
|
+
await result.current.markReady();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
act(() => {
|
|
60
|
+
jest.advanceTimersByTime(2000);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(result.current.isVisible).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("can manually hide and show", () => {
|
|
67
|
+
const { result } = renderHook(() => useSplash());
|
|
68
|
+
|
|
69
|
+
act(() => {
|
|
70
|
+
result.current.hide();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.current.isVisible).toBe(false);
|
|
74
|
+
|
|
75
|
+
act(() => {
|
|
76
|
+
result.current.show();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result.current.isVisible).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("prevents multiple ready calls", async () => {
|
|
83
|
+
const onReady = jest.fn();
|
|
84
|
+
const { result } = renderHook(() => useSplash({ onReady }));
|
|
85
|
+
|
|
86
|
+
await act(async () => {
|
|
87
|
+
await result.current.markReady();
|
|
88
|
+
await result.current.markReady();
|
|
89
|
+
await result.current.markReady();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(onReady).toHaveBeenCalledTimes(1);
|
|
93
|
+
expect(result.current.isReady).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("handles onReady errors gracefully", async () => {
|
|
97
|
+
const consoleError = jest.spyOn(console, "error").mockImplementation();
|
|
98
|
+
const onReady = jest.fn().mockRejectedValue(new Error("Test error"));
|
|
99
|
+
const { result } = renderHook(() => useSplash({ onReady }));
|
|
100
|
+
|
|
101
|
+
await act(async () => {
|
|
102
|
+
await result.current.markReady();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(result.current.isReady).toBe(true);
|
|
106
|
+
expect(consoleError).toHaveBeenCalled();
|
|
107
|
+
|
|
108
|
+
consoleError.mockRestore();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("cleans up timer on unmount", () => {
|
|
112
|
+
const { result, unmount } = renderHook(() => useSplash());
|
|
113
|
+
|
|
114
|
+
act(() => {
|
|
115
|
+
result.current.markReady();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
unmount();
|
|
119
|
+
|
|
120
|
+
// Should not throw error about timer cleanup
|
|
121
|
+
expect(true).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Utilities and Mocks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, RenderOptions } from '@testing-library/react-native';
|
|
7
|
+
import { View } from 'react-native';
|
|
8
|
+
|
|
9
|
+
// Custom render function with providers
|
|
10
|
+
const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
|
|
11
|
+
return <View>{children}</View>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const customRender = (
|
|
15
|
+
ui: React.ReactElement,
|
|
16
|
+
options?: Omit<RenderOptions, 'wrapper'>
|
|
17
|
+
) => render(ui, { wrapper: AllTheProviders, ...options });
|
|
18
|
+
|
|
19
|
+
// Mock timers utilities
|
|
20
|
+
export const advanceTimersByTime = (ms: number) => {
|
|
21
|
+
jest.advanceTimersByTime(ms);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const runAllTimers = () => {
|
|
25
|
+
jest.runAllTimers();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Mock data generators
|
|
29
|
+
export const createMockSplashProps = (overrides = {}) => ({
|
|
30
|
+
appName: 'Test App',
|
|
31
|
+
tagline: 'Test Tagline',
|
|
32
|
+
visible: true,
|
|
33
|
+
showLoading: true,
|
|
34
|
+
minimumDisplayTime: 1500,
|
|
35
|
+
...overrides,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const createMockDesignTokens = (overrides = {}) => ({
|
|
39
|
+
colors: { primary: '#007AFF' },
|
|
40
|
+
spacing: { xl: 24, md: 16, xxxl: 48 },
|
|
41
|
+
...overrides,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Performance testing utilities
|
|
45
|
+
export const measureRenderTime = async (component: React.ReactElement) => {
|
|
46
|
+
const start = performance.now();
|
|
47
|
+
customRender(component);
|
|
48
|
+
const end = performance.now();
|
|
49
|
+
return end - start;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Memory leak testing utilities
|
|
53
|
+
export const trackMemoryUsage = () => {
|
|
54
|
+
const initialMemory = (performance as any).memory?.usedJSHeapSize || 0;
|
|
55
|
+
return {
|
|
56
|
+
getMemoryIncrease: () => {
|
|
57
|
+
const currentMemory = (performance as any).memory?.usedJSHeapSize || 0;
|
|
58
|
+
return currentMemory - initialMemory;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Accessibility testing utilities
|
|
64
|
+
export const checkAccessibility = (container: any) => {
|
|
65
|
+
const accessibilityElements = container.findAllByProps({
|
|
66
|
+
accessible: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
hasAccessibleElements: accessibilityElements.length > 0,
|
|
71
|
+
accessibleElementCount: accessibilityElements.length,
|
|
72
|
+
elements: accessibilityElements,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Error boundary testing utilities
|
|
77
|
+
export const createErrorComponent = (errorMessage: string) => {
|
|
78
|
+
const ThrowError = () => {
|
|
79
|
+
throw new Error(errorMessage);
|
|
80
|
+
};
|
|
81
|
+
return ThrowError;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Re-export testing library
|
|
85
|
+
export * from '@testing-library/react-native';
|
|
86
|
+
export { customRender as render };
|