@umituz/react-native-settings 4.17.14 → 4.17.16

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.
Files changed (103) hide show
  1. package/package.json +16 -15
  2. package/src/domains/about/__tests__/integration.test.tsx +328 -0
  3. package/src/domains/about/__tests__/types.d.ts +5 -0
  4. package/src/domains/about/domain/entities/AppInfo.ts +74 -0
  5. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +93 -0
  6. package/src/domains/about/domain/repositories/IAboutRepository.ts +22 -0
  7. package/src/domains/about/index.ts +10 -0
  8. package/src/domains/about/infrastructure/repositories/AboutRepository.ts +68 -0
  9. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +153 -0
  10. package/src/domains/about/presentation/components/AboutContent.tsx +104 -0
  11. package/src/domains/about/presentation/components/AboutHeader.tsx +79 -0
  12. package/src/domains/about/presentation/components/AboutSection.tsx +134 -0
  13. package/src/domains/about/presentation/components/AboutSettingItem.tsx +208 -0
  14. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +178 -0
  15. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +293 -0
  16. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +201 -0
  17. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +71 -0
  18. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +229 -0
  19. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +240 -0
  20. package/src/domains/about/presentation/hooks/useAboutInfo.ts +262 -0
  21. package/src/domains/about/presentation/screens/AboutScreen.tsx +195 -0
  22. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +199 -0
  23. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +366 -0
  24. package/src/domains/about/types/global.d.ts +15 -0
  25. package/src/domains/about/utils/__tests__/index.test.ts +408 -0
  26. package/src/domains/about/utils/index.ts +160 -0
  27. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +195 -0
  28. package/src/domains/appearance/__tests__/hooks/index.test.tsx +232 -0
  29. package/src/domains/appearance/__tests__/integration/index.test.tsx +207 -0
  30. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +299 -0
  31. package/src/domains/appearance/__tests__/setup.ts +96 -0
  32. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +175 -0
  33. package/src/domains/appearance/data/colorPalettes.ts +94 -0
  34. package/src/domains/appearance/hooks/index.ts +6 -0
  35. package/src/domains/appearance/hooks/useAppearance.ts +61 -0
  36. package/src/domains/appearance/hooks/useAppearanceActions.ts +144 -0
  37. package/src/domains/appearance/index.ts +7 -0
  38. package/src/domains/appearance/infrastructure/services/appearanceService.ts +301 -0
  39. package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +79 -0
  40. package/src/domains/appearance/infrastructure/services/validation.ts +91 -0
  41. package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +120 -0
  42. package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +132 -0
  43. package/src/domains/appearance/presentation/components/AppearanceHeader.tsx +67 -0
  44. package/src/domains/appearance/presentation/components/AppearancePreview.tsx +141 -0
  45. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +139 -0
  46. package/src/domains/appearance/presentation/components/ColorPicker.tsx +113 -0
  47. package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +186 -0
  48. package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +110 -0
  49. package/src/domains/appearance/presentation/components/ThemeOption.tsx +138 -0
  50. package/src/domains/appearance/presentation/components/index.ts +6 -0
  51. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +226 -0
  52. package/src/domains/appearance/presentation/screens/index.ts +2 -0
  53. package/src/domains/appearance/types/index.ts +54 -0
  54. package/src/domains/faqs/domain/entities/FAQEntity.ts +16 -0
  55. package/src/domains/faqs/domain/services/FAQSearchService.ts +36 -0
  56. package/src/domains/faqs/domain/services/index.ts +1 -0
  57. package/src/domains/faqs/index.ts +7 -0
  58. package/src/domains/faqs/presentation/components/FAQCategory.tsx +71 -0
  59. package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +75 -0
  60. package/src/domains/faqs/presentation/components/FAQItem.tsx +103 -0
  61. package/src/domains/faqs/presentation/components/FAQSearchBar.tsx +70 -0
  62. package/src/domains/faqs/presentation/components/FAQSection.tsx +50 -0
  63. package/src/domains/faqs/presentation/components/index.ts +18 -0
  64. package/src/domains/faqs/presentation/hooks/index.ts +6 -0
  65. package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +51 -0
  66. package/src/domains/faqs/presentation/hooks/useFAQSearch.ts +33 -0
  67. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +129 -0
  68. package/src/domains/faqs/presentation/screens/index.ts +2 -0
  69. package/src/domains/feedback/domain/entities/FeedbackEntity.ts +92 -0
  70. package/src/domains/feedback/domain/repositories/IFeedbackRepository.ts +28 -0
  71. package/src/domains/feedback/index.ts +6 -0
  72. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +189 -0
  73. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +111 -0
  74. package/src/domains/feedback/presentation/components/SupportSection.tsx +160 -0
  75. package/src/domains/feedback/presentation/hooks/useDeleteFeedback.ts +25 -0
  76. package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +59 -0
  77. package/src/domains/feedback/presentation/hooks/useSubmitFeedback.ts +55 -0
  78. package/src/domains/feedback/presentation/hooks/useUserFeedback.ts +29 -0
  79. package/src/domains/legal/__tests__/ContentValidationService.test.ts +195 -0
  80. package/src/domains/legal/__tests__/StyleCacheService.test.ts +110 -0
  81. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +71 -0
  82. package/src/domains/legal/__tests__/setup.ts +82 -0
  83. package/src/domains/legal/domain/entities/LegalConfig.ts +26 -0
  84. package/src/domains/legal/domain/services/ContentValidationService.ts +89 -0
  85. package/src/domains/legal/domain/services/StyleCacheService.ts +97 -0
  86. package/src/domains/legal/domain/services/UrlHandlerService.ts +128 -0
  87. package/src/domains/legal/index.ts +8 -0
  88. package/src/domains/legal/presentation/components/LegalItem.tsx +177 -0
  89. package/src/domains/legal/presentation/components/LegalLinks.tsx +154 -0
  90. package/src/domains/legal/presentation/components/LegalSection.tsx +134 -0
  91. package/src/domains/legal/presentation/screens/LegalScreen.tsx +237 -0
  92. package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +214 -0
  93. package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +214 -0
  94. package/src/index.ts +19 -0
  95. package/src/presentation/components/DevSettingsSection.tsx +2 -2
  96. package/src/presentation/components/SettingItem.tsx +2 -2
  97. package/src/presentation/components/SettingsErrorBoundary.tsx +2 -2
  98. package/src/presentation/components/SettingsFooter.tsx +2 -2
  99. package/src/presentation/components/SettingsSection.tsx +2 -2
  100. package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
  101. package/src/presentation/screens/SettingsScreen.tsx +2 -2
  102. package/src/presentation/screens/components/SettingsContent.tsx +2 -2
  103. package/src/presentation/screens/components/SettingsHeader.tsx +2 -2
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Hook for managing About information
3
+ * Provides reactive state management for About data
4
+ * Optimized for performance and memory safety
5
+ */
6
+ import { useState, useEffect, useCallback, useRef } from 'react';
7
+ import { AppInfo, AboutConfig } from '../../domain/entities/AppInfo';
8
+ import { AboutRepository } from '../../infrastructure/repositories/AboutRepository';
9
+
10
+ export interface UseAboutInfoOptions {
11
+ /** Initial configuration */
12
+ initialConfig?: AboutConfig;
13
+ /** Auto-initialize on mount */
14
+ autoInit?: boolean;
15
+ }
16
+
17
+ export interface UseAboutInfoReturn {
18
+ /** Current app info */
19
+ appInfo: AppInfo | null;
20
+ /** Loading state */
21
+ loading: boolean;
22
+ /** Error state */
23
+ error: string | null;
24
+ /** Initialize with config */
25
+ initialize: (config: AboutConfig) => Promise<void>;
26
+ /** Update with new config */
27
+ update: (config: AboutConfig) => Promise<void>;
28
+ /** Update app info */
29
+ updateAppInfo: (updates: Partial<AppInfo>) => Promise<void>;
30
+ /** Refresh current app info */
31
+ refresh: () => Promise<void>;
32
+ /** Reset to initial state */
33
+ reset: () => void;
34
+ }
35
+
36
+ export const useAboutInfo = (
37
+ options: UseAboutInfoOptions = {}
38
+ ): UseAboutInfoReturn => {
39
+ const { initialConfig, autoInit } = options;
40
+ const [repository] = useState(() => new AboutRepository());
41
+ const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
42
+ const [loading, setLoading] = useState(false);
43
+ const [error, setError] = useState<string | null>(null);
44
+
45
+ // Prevent infinite loops and memory leaks
46
+ const isInitializedRef = useRef(false);
47
+ const isMountedRef = useRef(true);
48
+
49
+ const initialize = useCallback(async (config: AboutConfig, force = false) => {
50
+ // Prevent multiple initializations unless forced
51
+ if (isInitializedRef.current && !force) {
52
+ return;
53
+ }
54
+
55
+ // Check if component is still mounted
56
+ if (!isMountedRef.current) {
57
+ return;
58
+ }
59
+
60
+ setLoading(true);
61
+ setError(null);
62
+
63
+ try {
64
+ const defaultAppInfo: AppInfo = {
65
+ name: config.appInfo?.name || '',
66
+ version: config.appInfo?.version || '1.0.0',
67
+ description: config.appInfo?.description,
68
+ developer: config.appInfo?.developer,
69
+ contactEmail: config.appInfo?.contactEmail,
70
+ websiteUrl: config.appInfo?.websiteUrl,
71
+ websiteDisplay: config.appInfo?.websiteDisplay,
72
+ moreAppsUrl: config.appInfo?.moreAppsUrl,
73
+ };
74
+
75
+ await repository.saveAppInfo(defaultAppInfo);
76
+
77
+ // Only update state if component is still mounted
78
+ if (isMountedRef.current) {
79
+ setAppInfo(defaultAppInfo);
80
+ isInitializedRef.current = true;
81
+ }
82
+ } catch (err) {
83
+ if (!isMountedRef.current) {
84
+ return;
85
+ }
86
+
87
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
88
+ setError(errorMessage);
89
+ } finally {
90
+ // Only update loading state if component is still mounted
91
+ if (isMountedRef.current) {
92
+ setLoading(false);
93
+ }
94
+ }
95
+ }, [repository]);
96
+
97
+ const update = useCallback(async (config: AboutConfig) => {
98
+ if (!isMountedRef.current) {
99
+ return;
100
+ }
101
+
102
+ setLoading(true);
103
+ setError(null);
104
+
105
+ try {
106
+ const updatedAppInfo: AppInfo = {
107
+ name: config.appInfo?.name || '',
108
+ version: config.appInfo?.version || '1.0.0',
109
+ description: config.appInfo?.description,
110
+ developer: config.appInfo?.developer,
111
+ contactEmail: config.appInfo?.contactEmail,
112
+ websiteUrl: config.appInfo?.websiteUrl,
113
+ websiteDisplay: config.appInfo?.websiteDisplay,
114
+ moreAppsUrl: config.appInfo?.moreAppsUrl,
115
+ };
116
+
117
+ await repository.saveAppInfo(updatedAppInfo);
118
+
119
+ // Only update state if component is still mounted
120
+ if (isMountedRef.current) {
121
+ setAppInfo(updatedAppInfo);
122
+ }
123
+ } catch (err) {
124
+ if (!isMountedRef.current) {
125
+ return;
126
+ }
127
+
128
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
129
+ setError(errorMessage);
130
+ } finally {
131
+ // Only update loading state if component is still mounted
132
+ if (isMountedRef.current) {
133
+ setLoading(false);
134
+ }
135
+ }
136
+ }, [repository]);
137
+
138
+ const updateAppInfo = useCallback(async (updates: Partial<AppInfo>) => {
139
+ if (!appInfo || !isMountedRef.current) {
140
+ if (isMountedRef.current) {
141
+ setError('App info not initialized');
142
+ }
143
+ return;
144
+ }
145
+
146
+ setLoading(true);
147
+ setError(null);
148
+
149
+ try {
150
+ const updatedInfo = await repository.updateAppInfo(updates);
151
+
152
+ // Only update state if component is still mounted
153
+ if (isMountedRef.current) {
154
+ setAppInfo(updatedInfo);
155
+ }
156
+ } catch (err) {
157
+ if (!isMountedRef.current) {
158
+ return;
159
+ }
160
+
161
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
162
+ setError(errorMessage);
163
+ } finally {
164
+ // Only update loading state if component is still mounted
165
+ if (isMountedRef.current) {
166
+ setLoading(false);
167
+ }
168
+ }
169
+ }, [repository, appInfo]);
170
+
171
+ const refresh = useCallback(async () => {
172
+ if (!isMountedRef.current || !appInfo) {
173
+ return;
174
+ }
175
+
176
+ setLoading(true);
177
+ setError(null);
178
+
179
+ try {
180
+ const refreshedInfo = await repository.getAppInfo();
181
+
182
+ // Only update state if component is still mounted
183
+ if (isMountedRef.current) {
184
+ setAppInfo(refreshedInfo);
185
+ }
186
+ } catch (err) {
187
+ if (!isMountedRef.current) {
188
+ return;
189
+ }
190
+
191
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
192
+ setError(errorMessage);
193
+ } finally {
194
+ // Only update loading state if component is still mounted
195
+ if (isMountedRef.current) {
196
+ setLoading(false);
197
+ }
198
+ }
199
+ }, [repository, appInfo]);
200
+
201
+ const reset = useCallback(() => {
202
+ if (!isMountedRef.current) {
203
+ return;
204
+ }
205
+
206
+ setAppInfo(null);
207
+ setError(null);
208
+ setLoading(false);
209
+ isInitializedRef.current = false;
210
+ }, []);
211
+
212
+ // Cleanup on unmount to prevent memory leaks
213
+ useEffect(() => {
214
+ return () => {
215
+ isMountedRef.current = false;
216
+
217
+ // Cleanup repository if it has destroy method
218
+ if (repository && typeof repository.destroy === 'function') {
219
+ repository.destroy();
220
+ }
221
+ };
222
+ }, [repository]);
223
+
224
+
225
+
226
+ // Set initial config when provided (if autoInit is not explicitly false)
227
+ useEffect(() => {
228
+ if (initialConfig && autoInit !== false && isMountedRef.current && !isInitializedRef.current) {
229
+ const defaultAppInfo: AppInfo = {
230
+ name: initialConfig.appInfo?.name || '',
231
+ version: initialConfig.appInfo?.version || '1.0.0',
232
+ description: initialConfig.appInfo?.description,
233
+ developer: initialConfig.appInfo?.developer,
234
+ contactEmail: initialConfig.appInfo?.contactEmail,
235
+ websiteUrl: initialConfig.appInfo?.websiteUrl,
236
+ websiteDisplay: initialConfig.appInfo?.websiteDisplay,
237
+ moreAppsUrl: initialConfig.appInfo?.moreAppsUrl,
238
+ };
239
+
240
+ setAppInfo(defaultAppInfo);
241
+ isInitializedRef.current = true;
242
+ }
243
+ }, [initialConfig, autoInit]);
244
+
245
+ // Auto-initialize with dependency optimization
246
+ useEffect(() => {
247
+ if (autoInit === true && initialConfig && isMountedRef.current) {
248
+ initialize(initialConfig, true);
249
+ }
250
+ }, [autoInit, initialConfig, initialize]);
251
+
252
+ return {
253
+ appInfo,
254
+ loading,
255
+ error,
256
+ initialize,
257
+ update,
258
+ updateAppInfo,
259
+ refresh,
260
+ reset,
261
+ };
262
+ };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * About Screen Component
3
+ * Main screen component for displaying app information
4
+ * Fully configurable and generic
5
+ * Optimized for performance and memory safety
6
+ */
7
+ import React, { useMemo, useCallback } from 'react';
8
+ import {
9
+ View,
10
+ StyleSheet,
11
+ ScrollView,
12
+ ViewStyle,
13
+ TextStyle,
14
+ } from 'react-native';
15
+ import { AboutHeader } from '../components/AboutHeader';
16
+ import { AboutContent } from '../components/AboutContent';
17
+ import { AboutConfig } from '../../domain/entities/AppInfo';
18
+
19
+ export interface AboutScreenProps {
20
+ /** Configuration for the about screen */
21
+ config: AboutConfig;
22
+ /** Custom container style */
23
+ containerStyle?: ViewStyle;
24
+ /** Custom header style */
25
+ headerStyle?: ViewStyle;
26
+ /** Custom title style */
27
+ titleStyle?: TextStyle;
28
+ /** Custom version style */
29
+ versionStyle?: TextStyle;
30
+ /** Show app header with name and version */
31
+ showHeader?: boolean;
32
+ /** Custom header component */
33
+ headerComponent?: React.ReactNode;
34
+ /** Custom footer component */
35
+ footerComponent?: React.ReactNode;
36
+ /** Test ID for E2E testing */
37
+ testID?: string;
38
+ }
39
+
40
+ import { useAboutInfo } from '../hooks/useAboutInfo';
41
+ import { useResponsiveDesignTokens, AtomicText } from '@umituz/react-native-design-system';
42
+
43
+ export const AboutScreen: React.FC<AboutScreenProps> = ({
44
+ config,
45
+ containerStyle,
46
+ headerStyle,
47
+ titleStyle,
48
+ versionStyle,
49
+ showHeader = true,
50
+ headerComponent,
51
+ footerComponent,
52
+ testID,
53
+ }) => {
54
+ const tokens = useResponsiveDesignTokens();
55
+ const colors = tokens.colors;
56
+
57
+ const { appInfo, loading, error } = useAboutInfo({
58
+ autoInit: true,
59
+ initialConfig: config,
60
+ });
61
+
62
+ // Memoize header rendering to prevent unnecessary re-renders
63
+ const renderHeader = useCallback(() => {
64
+ if (headerComponent) {
65
+ return headerComponent;
66
+ }
67
+
68
+ if (!showHeader || !appInfo) {
69
+ return null;
70
+ }
71
+
72
+ return (
73
+ <AboutHeader
74
+ appInfo={appInfo}
75
+ containerStyle={headerStyle}
76
+ titleStyle={titleStyle}
77
+ versionStyle={versionStyle}
78
+ versionPrefix={config.texts?.versionPrefix}
79
+ />
80
+ );
81
+ }, [headerComponent, showHeader, appInfo, headerStyle, titleStyle, versionStyle, config.texts?.versionPrefix]);
82
+
83
+ // Memoize footer rendering
84
+ const renderFooter = useCallback(() => {
85
+ if (!footerComponent) {
86
+ return null;
87
+ }
88
+
89
+ return (
90
+ <View style={[styles.footer, { borderTopColor: colors.border }]}>
91
+ {footerComponent}
92
+ </View>
93
+ );
94
+ }, [footerComponent, colors.border]);
95
+
96
+ // Memoize content rendering
97
+ const renderContent = useCallback(() => {
98
+ if (!appInfo) {
99
+ return null;
100
+ }
101
+
102
+ return (
103
+ <AboutContent
104
+ appInfo={appInfo}
105
+ config={config}
106
+ />
107
+ );
108
+ }, [appInfo, config]);
109
+
110
+ // Memoize container style to prevent unnecessary re-renders
111
+ const containerStyles = useMemo(() => {
112
+ return [
113
+ styles.container,
114
+ { backgroundColor: colors.backgroundPrimary },
115
+ containerStyle
116
+ ];
117
+ }, [containerStyle, colors.backgroundPrimary]);
118
+
119
+ const texts = config.texts || {};
120
+
121
+ if (loading) {
122
+ return (
123
+ <View style={containerStyles} testID={testID}>
124
+ <AtomicText
125
+ type="bodyMedium"
126
+ color="textSecondary"
127
+ style={styles.loadingText}
128
+ >
129
+ {texts.loading}
130
+ </AtomicText>
131
+ </View>
132
+ );
133
+ }
134
+
135
+ if (error) {
136
+ return (
137
+ <View style={containerStyles} testID={testID}>
138
+ <AtomicText
139
+ type="bodyMedium"
140
+ color="error"
141
+ style={styles.errorText}
142
+ >
143
+ {texts.errorPrefix} {error}
144
+ </AtomicText>
145
+ </View>
146
+ );
147
+ }
148
+
149
+ if (!appInfo) {
150
+ return (
151
+ <View style={containerStyles} testID={testID}>
152
+ <AtomicText
153
+ type="bodyMedium"
154
+ color="textSecondary"
155
+ style={styles.errorText}
156
+ >
157
+ {texts.noInfo}
158
+ </AtomicText>
159
+ </View>
160
+ );
161
+ }
162
+
163
+ return (
164
+ <ScrollView
165
+ style={containerStyles}
166
+ testID={testID}
167
+ contentContainerStyle={{ paddingBottom: 32 }}
168
+ >
169
+ {renderHeader()}
170
+ {renderContent()}
171
+ {renderFooter()}
172
+ </ScrollView>
173
+ );
174
+ };
175
+
176
+ const styles = StyleSheet.create({
177
+ container: {
178
+ flex: 1,
179
+ },
180
+ footer: {
181
+ paddingVertical: 16,
182
+ paddingHorizontal: 16,
183
+ borderTopWidth: 1,
184
+ },
185
+ loadingText: {
186
+ textAlign: 'center',
187
+ fontSize: 16,
188
+ marginTop: 20,
189
+ },
190
+ errorText: {
191
+ textAlign: 'center',
192
+ fontSize: 16,
193
+ marginTop: 20,
194
+ },
195
+ });
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Simple test for AboutScreen component
3
+ */
4
+ import React from 'react';
5
+ import { AboutScreen } from '../AboutScreen';
6
+ import { AppInfo, AboutConfig } from '../../domain/entities/AppInfo';
7
+
8
+ describe('AboutScreen', () => {
9
+ const mockAppInfo: AppInfo = {
10
+ name: 'Test App',
11
+ version: '1.0.0',
12
+ description: 'Test Description',
13
+ developer: 'Test Developer',
14
+ contactEmail: 'test@example.com',
15
+ websiteUrl: 'https://example.com',
16
+ websiteDisplay: 'example.com',
17
+ moreAppsUrl: 'https://apps.example.com',
18
+ privacyPolicyUrl: 'https://example.com/privacy',
19
+ termsOfServiceUrl: 'https://example.com/terms',
20
+ };
21
+
22
+ const mockConfig: AboutConfig = {
23
+ appInfo: mockAppInfo,
24
+ actions: {
25
+ onEmailPress: jest.fn(),
26
+ onWebsitePress: jest.fn(),
27
+ onPrivacyPress: jest.fn(),
28
+ onTermsPress: jest.fn(),
29
+ onMoreAppsPress: jest.fn(),
30
+ },
31
+ };
32
+
33
+ it('should render without crashing', () => {
34
+ const element = React.createElement(AboutScreen, { config: mockConfig });
35
+ expect(element).toBeTruthy();
36
+ });
37
+
38
+ it('should render app info after loading', () => {
39
+ const element = React.createElement(AboutScreen, { config: mockConfig });
40
+ expect(element).toBeTruthy();
41
+ expect(mockConfig.appInfo.name).toBe('Test App');
42
+ });
43
+
44
+ it('should render error state when initialization fails', () => {
45
+ const invalidConfig = null as unknown;
46
+
47
+ const element = React.createElement(AboutScreen, { config: invalidConfig });
48
+ expect(element).toBeTruthy();
49
+ });
50
+
51
+ it('should render no app info message when app info is null', () => {
52
+ const emptyConfig: AboutConfig = {
53
+ appInfo: null as unknown,
54
+ actions: {},
55
+ };
56
+
57
+ const element = React.createElement(AboutScreen, { config: emptyConfig });
58
+ expect(element).toBeTruthy();
59
+ });
60
+
61
+ it('should not render header when showHeader is false', () => {
62
+ const element = React.createElement(AboutScreen, { config: mockConfig, showHeader: false });
63
+ expect(element).toBeTruthy();
64
+ });
65
+
66
+ it('should render custom header component when provided', () => {
67
+ const CustomHeader = () => React.createElement('div', { testID: 'custom-header' }, 'Custom Header');
68
+
69
+ const element = React.createElement(AboutScreen, {
70
+ config: mockConfig,
71
+ headerComponent: React.createElement(CustomHeader)
72
+ });
73
+ expect(element).toBeTruthy();
74
+ });
75
+
76
+ it('should render custom footer component when provided', () => {
77
+ const CustomFooter = () => React.createElement('div', { testID: 'custom-footer' }, 'Custom Footer');
78
+
79
+ const element = React.createElement(AboutScreen, {
80
+ config: mockConfig,
81
+ footerComponent: React.createElement(CustomFooter)
82
+ });
83
+ expect(element).toBeTruthy();
84
+ });
85
+
86
+ it('should apply custom container style', () => {
87
+ const customStyle = { backgroundColor: 'red' };
88
+
89
+ const element = React.createElement(AboutScreen, {
90
+ config: mockConfig,
91
+ containerStyle: customStyle,
92
+ testID: 'screen'
93
+ });
94
+ expect(element).toBeTruthy();
95
+ });
96
+
97
+ it('should apply custom header style', () => {
98
+ const customStyle = { backgroundColor: 'blue' };
99
+
100
+ const element = React.createElement(AboutScreen, {
101
+ config: mockConfig,
102
+ headerStyle: customStyle
103
+ });
104
+ expect(element).toBeTruthy();
105
+ });
106
+
107
+ it('should apply custom title style', () => {
108
+ const customStyle = { color: 'green' } as React.CSSProperties;
109
+
110
+ const element = React.createElement(AboutScreen, {
111
+ config: mockConfig,
112
+ titleStyle: customStyle
113
+ });
114
+ expect(element).toBeTruthy();
115
+ });
116
+
117
+ it('should apply custom version style', () => {
118
+ const customStyle = { color: 'purple' };
119
+
120
+ const element = React.createElement(AboutScreen, {
121
+ config: mockConfig,
122
+ versionStyle: customStyle
123
+ });
124
+ expect(element).toBeTruthy();
125
+ });
126
+
127
+ it('should handle empty config', () => {
128
+ const emptyConfig: AboutConfig = {
129
+ appInfo: {
130
+ name: '',
131
+ version: '',
132
+ description: '',
133
+ developer: '',
134
+ contactEmail: '',
135
+ websiteUrl: '',
136
+ websiteDisplay: '',
137
+ moreAppsUrl: '',
138
+ privacyPolicyUrl: '',
139
+ termsOfServiceUrl: '',
140
+ },
141
+ actions: {},
142
+ };
143
+
144
+ const element = React.createElement(AboutScreen, { config: emptyConfig });
145
+ expect(element).toBeTruthy();
146
+ });
147
+
148
+ it('should handle config with only required fields', () => {
149
+ const minimalConfig: AboutConfig = {
150
+ appInfo: {
151
+ name: 'Minimal App',
152
+ version: '1.0.0',
153
+ description: '',
154
+ developer: '',
155
+ contactEmail: '',
156
+ websiteUrl: '',
157
+ websiteDisplay: '',
158
+ moreAppsUrl: '',
159
+ privacyPolicyUrl: '',
160
+ termsOfServiceUrl: '',
161
+ },
162
+ actions: {},
163
+ };
164
+
165
+ const element = React.createElement(AboutScreen, { config: minimalConfig });
166
+ expect(element).toBeTruthy();
167
+ expect(minimalConfig.appInfo.name).toBe('Minimal App');
168
+ });
169
+
170
+ it('should handle special characters in text', () => {
171
+ const configWithSpecialChars: AboutConfig = {
172
+ appInfo: {
173
+ ...mockAppInfo,
174
+ name: 'App & Special <Chars>',
175
+ description: 'Description with "quotes" and \'apostrophes\'',
176
+ },
177
+ actions: {},
178
+ };
179
+
180
+ const element = React.createElement(AboutScreen, { config: configWithSpecialChars });
181
+ expect(element).toBeTruthy();
182
+ expect(configWithSpecialChars.appInfo.name).toBe('App & Special <Chars>');
183
+ });
184
+
185
+ it('should handle very long text', () => {
186
+ const configWithLongText: AboutConfig = {
187
+ appInfo: {
188
+ ...mockAppInfo,
189
+ name: 'A'.repeat(100),
190
+ description: 'B'.repeat(500),
191
+ },
192
+ actions: {},
193
+ };
194
+
195
+ const element = React.createElement(AboutScreen, { config: configWithLongText });
196
+ expect(element).toBeTruthy();
197
+ expect(configWithLongText.appInfo.name.length).toBe(100);
198
+ });
199
+ });