@umituz/react-native-settings 1.11.4 → 2.4.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.
Files changed (167) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +129 -3
  3. package/lib/__tests__/setup.d.ts +5 -0
  4. package/lib/__tests__/setup.d.ts.map +1 -0
  5. package/lib/__tests__/setup.js +143 -0
  6. package/lib/__tests__/setup.js.map +1 -0
  7. package/lib/domain/repositories/ISettingsRepository.d.ts +51 -0
  8. package/lib/domain/repositories/ISettingsRepository.d.ts.map +1 -0
  9. package/lib/domain/repositories/ISettingsRepository.js +8 -0
  10. package/lib/domain/repositories/ISettingsRepository.js.map +1 -0
  11. package/lib/index.d.ts +35 -0
  12. package/lib/index.d.ts.map +1 -0
  13. package/lib/index.js +32 -0
  14. package/lib/index.js.map +1 -0
  15. package/lib/infrastructure/storage/SettingsStore.d.ts +36 -0
  16. package/lib/infrastructure/storage/SettingsStore.d.ts.map +1 -0
  17. package/lib/infrastructure/storage/SettingsStore.js +144 -0
  18. package/lib/infrastructure/storage/SettingsStore.js.map +1 -0
  19. package/lib/presentation/components/CloudSyncSetting.d.ts +16 -0
  20. package/lib/presentation/components/CloudSyncSetting.d.ts.map +1 -0
  21. package/lib/presentation/components/CloudSyncSetting.js +30 -0
  22. package/lib/presentation/components/CloudSyncSetting.js.map +1 -0
  23. package/lib/presentation/components/DisclaimerCard.d.ts +15 -0
  24. package/lib/presentation/components/DisclaimerCard.d.ts.map +1 -0
  25. package/lib/presentation/components/DisclaimerCard.js +73 -0
  26. package/lib/presentation/components/DisclaimerCard.js.map +1 -0
  27. package/lib/presentation/components/DisclaimerModal.d.ts +13 -0
  28. package/lib/presentation/components/DisclaimerModal.d.ts.map +1 -0
  29. package/lib/presentation/components/DisclaimerModal.js +62 -0
  30. package/lib/presentation/components/DisclaimerModal.js.map +1 -0
  31. package/lib/presentation/components/DisclaimerSetting.d.ts +39 -0
  32. package/lib/presentation/components/DisclaimerSetting.d.ts.map +1 -0
  33. package/lib/presentation/components/DisclaimerSetting.js +59 -0
  34. package/lib/presentation/components/DisclaimerSetting.js.map +1 -0
  35. package/lib/presentation/components/SettingItem.d.ts +45 -0
  36. package/lib/presentation/components/SettingItem.d.ts.map +1 -0
  37. package/lib/presentation/components/SettingItem.js +113 -0
  38. package/lib/presentation/components/SettingItem.js.map +1 -0
  39. package/lib/presentation/components/SettingsErrorBoundary.d.ts +23 -0
  40. package/lib/presentation/components/SettingsErrorBoundary.d.ts.map +1 -0
  41. package/lib/presentation/components/SettingsErrorBoundary.js +73 -0
  42. package/lib/presentation/components/SettingsErrorBoundary.js.map +1 -0
  43. package/lib/presentation/components/SettingsFooter.d.ts +11 -0
  44. package/lib/presentation/components/SettingsFooter.d.ts.map +1 -0
  45. package/lib/presentation/components/SettingsFooter.js +31 -0
  46. package/lib/presentation/components/SettingsFooter.js.map +1 -0
  47. package/lib/presentation/components/SettingsSection.d.ts +13 -0
  48. package/lib/presentation/components/SettingsSection.d.ts.map +1 -0
  49. package/lib/presentation/components/SettingsSection.js +37 -0
  50. package/lib/presentation/components/SettingsSection.js.map +1 -0
  51. package/lib/presentation/components/StorageClearSetting.d.ts +16 -0
  52. package/lib/presentation/components/StorageClearSetting.d.ts.map +1 -0
  53. package/lib/presentation/components/StorageClearSetting.js +21 -0
  54. package/lib/presentation/components/StorageClearSetting.js.map +1 -0
  55. package/lib/presentation/components/UserProfileHeader.d.ts +30 -0
  56. package/lib/presentation/components/UserProfileHeader.d.ts.map +1 -0
  57. package/lib/presentation/components/UserProfileHeader.js +119 -0
  58. package/lib/presentation/components/UserProfileHeader.js.map +1 -0
  59. package/lib/presentation/screens/AppearanceScreen.d.ts +8 -0
  60. package/lib/presentation/screens/AppearanceScreen.d.ts.map +1 -0
  61. package/lib/presentation/screens/AppearanceScreen.js +8 -0
  62. package/lib/presentation/screens/AppearanceScreen.js.map +1 -0
  63. package/lib/presentation/screens/SettingsScreen.d.ts +38 -0
  64. package/lib/presentation/screens/SettingsScreen.d.ts.map +1 -0
  65. package/lib/presentation/screens/SettingsScreen.js +37 -0
  66. package/lib/presentation/screens/SettingsScreen.js.map +1 -0
  67. package/lib/presentation/screens/components/AboutLegalSection.d.ts +15 -0
  68. package/lib/presentation/screens/components/AboutLegalSection.d.ts.map +1 -0
  69. package/lib/presentation/screens/components/AboutLegalSection.js +28 -0
  70. package/lib/presentation/screens/components/AboutLegalSection.js.map +1 -0
  71. package/lib/presentation/screens/components/AppearanceSection.d.ts +12 -0
  72. package/lib/presentation/screens/components/AppearanceSection.d.ts.map +1 -0
  73. package/lib/presentation/screens/components/AppearanceSection.js +21 -0
  74. package/lib/presentation/screens/components/AppearanceSection.js.map +1 -0
  75. package/lib/presentation/screens/components/LanguageSection.d.ts +12 -0
  76. package/lib/presentation/screens/components/LanguageSection.d.ts.map +1 -0
  77. package/lib/presentation/screens/components/LanguageSection.js +26 -0
  78. package/lib/presentation/screens/components/LanguageSection.js.map +1 -0
  79. package/lib/presentation/screens/components/NotificationsSection.d.ts +12 -0
  80. package/lib/presentation/screens/components/NotificationsSection.d.ts.map +1 -0
  81. package/lib/presentation/screens/components/NotificationsSection.js +58 -0
  82. package/lib/presentation/screens/components/NotificationsSection.js.map +1 -0
  83. package/lib/presentation/screens/components/SettingsContent.d.ts +36 -0
  84. package/lib/presentation/screens/components/SettingsContent.d.ts.map +1 -0
  85. package/lib/presentation/screens/components/SettingsContent.js +81 -0
  86. package/lib/presentation/screens/components/SettingsContent.js.map +1 -0
  87. package/lib/presentation/screens/components/SettingsHeader.d.ts +12 -0
  88. package/lib/presentation/screens/components/SettingsHeader.d.ts.map +1 -0
  89. package/lib/presentation/screens/components/SettingsHeader.js +59 -0
  90. package/lib/presentation/screens/components/SettingsHeader.js.map +1 -0
  91. package/lib/presentation/screens/components/index.d.ts +9 -0
  92. package/lib/presentation/screens/components/index.d.ts.map +1 -0
  93. package/lib/presentation/screens/components/index.js +9 -0
  94. package/lib/presentation/screens/components/index.js.map +1 -0
  95. package/lib/presentation/screens/hooks/useFeatureDetection.d.ts +21 -0
  96. package/lib/presentation/screens/hooks/useFeatureDetection.d.ts.map +1 -0
  97. package/lib/presentation/screens/hooks/useFeatureDetection.js +82 -0
  98. package/lib/presentation/screens/hooks/useFeatureDetection.js.map +1 -0
  99. package/lib/presentation/screens/types/CustomSection.d.ts +19 -0
  100. package/lib/presentation/screens/types/CustomSection.d.ts.map +1 -0
  101. package/lib/presentation/screens/types/CustomSection.js +6 -0
  102. package/lib/presentation/screens/types/CustomSection.js.map +1 -0
  103. package/lib/presentation/screens/types/ExtendedConfig.d.ts +68 -0
  104. package/lib/presentation/screens/types/ExtendedConfig.d.ts.map +1 -0
  105. package/lib/presentation/screens/types/ExtendedConfig.js +6 -0
  106. package/lib/presentation/screens/types/ExtendedConfig.js.map +1 -0
  107. package/lib/presentation/screens/types/FeatureConfig.d.ts +95 -0
  108. package/lib/presentation/screens/types/FeatureConfig.d.ts.map +1 -0
  109. package/lib/presentation/screens/types/FeatureConfig.js +6 -0
  110. package/lib/presentation/screens/types/FeatureConfig.js.map +1 -0
  111. package/lib/presentation/screens/types/SettingsConfig.d.ts +97 -0
  112. package/lib/presentation/screens/types/SettingsConfig.d.ts.map +1 -0
  113. package/lib/presentation/screens/types/SettingsConfig.js +6 -0
  114. package/lib/presentation/screens/types/SettingsConfig.js.map +1 -0
  115. package/lib/presentation/screens/types/index.d.ts +10 -0
  116. package/lib/presentation/screens/types/index.d.ts.map +1 -0
  117. package/lib/presentation/screens/types/index.js +6 -0
  118. package/lib/presentation/screens/types/index.js.map +1 -0
  119. package/lib/presentation/screens/utils/normalizeConfig.d.ts +44 -0
  120. package/lib/presentation/screens/utils/normalizeConfig.d.ts.map +1 -0
  121. package/lib/presentation/screens/utils/normalizeConfig.js +38 -0
  122. package/lib/presentation/screens/utils/normalizeConfig.js.map +1 -0
  123. package/package.json +46 -11
  124. package/src/__tests__/integration.test.tsx +371 -0
  125. package/src/__tests__/performance.test.tsx +369 -0
  126. package/src/__tests__/setup.test.tsx +20 -0
  127. package/src/__tests__/setup.ts +157 -0
  128. package/src/domain/repositories/ISettingsRepository.ts +0 -0
  129. package/src/index.ts +9 -1
  130. package/src/infrastructure/storage/SettingsStore.ts +90 -45
  131. package/src/infrastructure/storage/__tests__/SettingsStore.test.tsx +302 -0
  132. package/src/presentation/components/CloudSyncSetting.tsx +11 -17
  133. package/src/presentation/components/DisclaimerCard.tsx +115 -0
  134. package/src/presentation/components/DisclaimerModal.tsx +104 -0
  135. package/src/presentation/components/DisclaimerSetting.tsx +77 -159
  136. package/src/presentation/components/SettingItem.tsx +11 -2
  137. package/src/presentation/components/SettingsErrorBoundary.tsx +126 -0
  138. package/src/presentation/components/SettingsFooter.tsx +0 -0
  139. package/src/presentation/components/SettingsSection.tsx +0 -0
  140. package/src/presentation/components/StorageClearSetting.tsx +13 -8
  141. package/src/presentation/components/UserProfileHeader.tsx +48 -11
  142. package/src/presentation/components/__tests__/CloudSyncSetting.test.tsx +78 -0
  143. package/src/presentation/components/__tests__/DisclaimerCard.test.tsx +208 -0
  144. package/src/presentation/components/__tests__/DisclaimerModal.test.tsx +236 -0
  145. package/src/presentation/components/__tests__/DisclaimerSetting.test.tsx +74 -0
  146. package/src/presentation/components/__tests__/SettingItem.test.tsx +189 -0
  147. package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +186 -0
  148. package/src/presentation/screens/AppearanceScreen.tsx +0 -0
  149. package/src/presentation/screens/SettingsScreen.tsx +29 -159
  150. package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +322 -0
  151. package/src/presentation/screens/components/AboutLegalSection.tsx +14 -5
  152. package/src/presentation/screens/components/AppearanceSection.tsx +1 -1
  153. package/src/presentation/screens/components/LanguageSection.tsx +2 -1
  154. package/src/presentation/screens/components/NotificationsSection.tsx +19 -14
  155. package/src/presentation/screens/components/SettingsContent.tsx +167 -0
  156. package/src/presentation/screens/components/SettingsHeader.tsx +79 -0
  157. package/src/presentation/screens/components/index.ts +0 -0
  158. package/src/presentation/screens/hooks/__tests__/useFeatureDetection.test.tsx +261 -0
  159. package/src/presentation/screens/hooks/useFeatureDetection.ts +15 -5
  160. package/src/presentation/screens/types/CustomSection.ts +20 -0
  161. package/src/presentation/screens/types/ExtendedConfig.ts +68 -0
  162. package/src/presentation/screens/types/FeatureConfig.ts +102 -0
  163. package/src/presentation/screens/types/SettingsConfig.ts +116 -0
  164. package/src/presentation/screens/types/index.ts +20 -0
  165. package/src/presentation/screens/utils/normalizeConfig.ts +2 -1
  166. package/src/presentation/screens/LanguageSelectionScreen.tsx +0 -204
  167. package/src/presentation/screens/types.ts +0 -263
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Tests for SettingItem Component
3
+ */
4
+
5
+ import React from 'react';
6
+ import { render, fireEvent } from '@testing-library/react-native';
7
+ import { SettingItem } from '../SettingItem';
8
+
9
+ // Mock lucide-react-native
10
+ jest.mock('lucide-react-native', () => ({
11
+ Bell: 'Bell',
12
+ Palette: 'Palette',
13
+ ChevronRight: 'ChevronRight',
14
+ }));
15
+
16
+ // Mock dependencies
17
+ jest.mock('@umituz/react-native-design-system-theme', () => ({
18
+ useAppDesignTokens: () => ({
19
+ colors: {
20
+ backgroundPrimary: '#ffffff',
21
+ primary: '#007AFF',
22
+ textPrimary: '#000000',
23
+ textSecondary: '#666666',
24
+ },
25
+ spacing: {
26
+ md: 16,
27
+ },
28
+ }),
29
+ }));
30
+
31
+ describe('SettingItem', () => {
32
+ it('renders correctly with basic props', () => {
33
+ const { getByText, getByTestId } = render(
34
+ <SettingItem
35
+ icon={Bell}
36
+ title="Test Setting"
37
+ testID="test-setting"
38
+ />
39
+ );
40
+
41
+ expect(getByText('Test Setting')).toBeTruthy();
42
+ expect(getByTestId('test-setting')).toBeTruthy();
43
+ });
44
+
45
+ it('displays value when provided', () => {
46
+ const { getByText } = render(
47
+ <SettingItem
48
+ icon={Bell}
49
+ title="Test Setting"
50
+ value="Test Value"
51
+ />
52
+ );
53
+
54
+ expect(getByText('Test Setting')).toBeTruthy();
55
+ expect(getByText('Test Value')).toBeTruthy();
56
+ });
57
+
58
+ it('shows switch when showSwitch is true', () => {
59
+ const { getByTestId, queryByTestId } = render(
60
+ <SettingItem
61
+ icon={Bell}
62
+ title="Test Setting"
63
+ showSwitch={true}
64
+ switchValue={true}
65
+ testID="test-setting"
66
+ />
67
+ );
68
+
69
+ expect(getByTestId('test-setting')).toBeTruthy();
70
+ // Switch should be present, chevron should not
71
+ });
72
+
73
+ it('calls onPress when pressed', () => {
74
+ const mockOnPress = jest.fn();
75
+ const { getByTestId } = render(
76
+ <SettingItem
77
+ icon={Bell}
78
+ title="Test Setting"
79
+ onPress={mockOnPress}
80
+ testID="test-setting"
81
+ />
82
+ );
83
+
84
+ fireEvent.press(getByTestId('test-setting'));
85
+ expect(mockOnPress).toHaveBeenCalledTimes(1);
86
+ });
87
+
88
+ it('calls onSwitchChange when switch is toggled', () => {
89
+ const mockOnSwitchChange = jest.fn();
90
+ const { getByRole } = render(
91
+ <SettingItem
92
+ icon={Bell}
93
+ title="Test Setting"
94
+ showSwitch={true}
95
+ switchValue={false}
96
+ onSwitchChange={mockOnSwitchChange}
97
+ />
98
+ );
99
+
100
+ const switchElement = getByRole('switch');
101
+ fireEvent(switchElement, 'valueChange', true);
102
+ expect(mockOnSwitchChange).toHaveBeenCalledWith(true);
103
+ });
104
+
105
+ it('applies custom colors', () => {
106
+ const { getByText } = render(
107
+ <SettingItem
108
+ icon={Bell}
109
+ title="Test Setting"
110
+ iconColor="#FF0000"
111
+ titleColor="#00FF00"
112
+ />
113
+ );
114
+
115
+ expect(getByText('Test Setting')).toBeTruthy();
116
+ });
117
+
118
+ it('shows disabled state correctly', () => {
119
+ const mockOnPress = jest.fn();
120
+ const { getByTestId } = render(
121
+ <SettingItem
122
+ icon={Bell}
123
+ title="Test Setting"
124
+ disabled={true}
125
+ onPress={mockOnPress}
126
+ testID="test-setting"
127
+ />
128
+ );
129
+
130
+ const pressable = getByTestId('test-setting');
131
+ expect(pressable.props.disabled).toBe(true);
132
+ });
133
+
134
+ it('does not show divider when isLast is true', () => {
135
+ const { queryByTestId } = render(
136
+ <SettingItem
137
+ icon={Bell}
138
+ title="Test Setting"
139
+ isLast={true}
140
+ />
141
+ );
142
+
143
+ // Divider should not be present
144
+ expect(queryByTestId('setting-divider')).toBeNull();
145
+ });
146
+
147
+ it('renders with different icon types', () => {
148
+ const { getByText } = render(
149
+ <SettingItem
150
+ icon={Palette}
151
+ title="Appearance Setting"
152
+ />
153
+ );
154
+
155
+ expect(getByText('Appearance Setting')).toBeTruthy();
156
+ });
157
+
158
+ it('handles long text correctly', () => {
159
+ const longTitle = 'This is a very long title that should be truncated properly';
160
+ const longValue = 'This is a very long value that should be truncated properly and should not break the layout';
161
+
162
+ const { getByText } = render(
163
+ <SettingItem
164
+ icon={Bell}
165
+ title={longTitle}
166
+ value={longValue}
167
+ />
168
+ );
169
+
170
+ expect(getByText(longTitle)).toBeTruthy();
171
+ expect(getByText(longValue)).toBeTruthy();
172
+ });
173
+
174
+ it('applies custom switch colors', () => {
175
+ const { getByRole } = render(
176
+ <SettingItem
177
+ icon={Bell}
178
+ title="Test Setting"
179
+ showSwitch={true}
180
+ switchValue={true}
181
+ switchThumbColor="#FF0000"
182
+ switchTrackColors={{ false: '#CCCCCC', true: '#00FF00' }}
183
+ />
184
+ );
185
+
186
+ const switchElement = getByRole('switch');
187
+ expect(switchElement).toBeTruthy();
188
+ });
189
+ });
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Tests for SettingsErrorBoundary Component
3
+ */
4
+
5
+ import React from 'react';
6
+ import { render, Text } from '@testing-library/react-native';
7
+ import { SettingsErrorBoundary } from '../SettingsErrorBoundary';
8
+
9
+ // Mock dependencies
10
+ jest.mock('@umituz/react-native-design-system-theme', () => ({
11
+ useAppDesignTokens: () => ({
12
+ colors: {
13
+ backgroundPrimary: '#ffffff',
14
+ surface: '#f5f5f5',
15
+ },
16
+ }),
17
+ }));
18
+
19
+ jest.mock('@umituz/react-native-design-system-atoms', () => ({
20
+ AtomicText: ({ children, type, color, style }: any) => (
21
+ <Text style={style} testID={`atomic-text-${type}-${color}`}>
22
+ {children}
23
+ </Text>
24
+ ),
25
+ AtomicIcon: ({ name, color, size, style }: any) => (
26
+ <Text style={style} testID={`atomic-icon-${name}-${color}-${size}`}>
27
+ Icon: {name}
28
+ </Text>
29
+ ),
30
+ }));
31
+
32
+ // Component that throws an error
33
+ const ThrowErrorComponent = () => {
34
+ throw new Error('Test error');
35
+ };
36
+
37
+ describe('SettingsErrorBoundary', () => {
38
+ let consoleError: jest.SpyInstance;
39
+
40
+ beforeEach(() => {
41
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
42
+ });
43
+
44
+ afterEach(() => {
45
+ consoleError.mockRestore();
46
+ });
47
+
48
+ it('renders children when there is no error', () => {
49
+ const { getByText } = render(
50
+ <SettingsErrorBoundary>
51
+ <Text>Normal Content</Text>
52
+ </SettingsErrorBoundary>
53
+ );
54
+
55
+ expect(getByText('Normal Content')).toBeTruthy();
56
+ });
57
+
58
+ it('catches and displays error boundary fallback', () => {
59
+ const { getByTestId, getByText } = render(
60
+ <SettingsErrorBoundary>
61
+ <ThrowErrorComponent />
62
+ </SettingsErrorBoundary>
63
+ );
64
+
65
+ // Should show error fallback
66
+ expect(getByTestId('atomic-icon-AlertTriangle-warning-lg')).toBeTruthy();
67
+ expect(getByTestId('atomic-text-headlineSmall-primary')).toBeTruthy();
68
+ expect(getByTestId('atomic-text-bodyMedium-secondary')).toBeTruthy();
69
+ });
70
+
71
+ it('uses custom fallback when provided', () => {
72
+ const customFallback = <Text testID="custom-fallback">Custom Error</Text>;
73
+
74
+ const { getByTestId, queryByTestId } = render(
75
+ <SettingsErrorBoundary fallback={customFallback}>
76
+ <ThrowErrorComponent />
77
+ </SettingsErrorBoundary>
78
+ );
79
+
80
+ expect(getByTestId('custom-fallback')).toBeTruthy();
81
+ expect(queryByTestId('atomic-icon-AlertTriangle-warning-lg')).toBeNull();
82
+ });
83
+
84
+ it('uses custom fallback title and message', () => {
85
+ const { getByTestId } = render(
86
+ <SettingsErrorBoundary
87
+ fallbackTitle="custom.error.title"
88
+ fallbackMessage="custom.error.message"
89
+ >
90
+ <ThrowErrorComponent />
91
+ </SettingsErrorBoundary>
92
+ );
93
+
94
+ expect(getByTestId('atomic-text-headlineSmall-primary')).toBeTruthy();
95
+ expect(getByTestId('atomic-text-bodyMedium-secondary')).toBeTruthy();
96
+ });
97
+
98
+ it('logs error in development mode', () => {
99
+ const originalDev = __DEV__;
100
+ (global as any).__DEV__ = true;
101
+
102
+ render(
103
+ <SettingsErrorBoundary>
104
+ <ThrowErrorComponent />
105
+ </SettingsErrorBoundary>
106
+ );
107
+
108
+ expect(consoleError).toHaveBeenCalledWith(
109
+ 'Settings Error Boundary caught an error:',
110
+ expect.any(Error),
111
+ expect.objectContaining({
112
+ componentStack: expect.any(String),
113
+ })
114
+ );
115
+
116
+ (global as any).__DEV__ = originalDev;
117
+ });
118
+
119
+ it('does not log error in production mode', () => {
120
+ const originalDev = __DEV__;
121
+ (global as any).__DEV__ = false;
122
+
123
+ render(
124
+ <SettingsErrorBoundary>
125
+ <ThrowErrorComponent />
126
+ </SettingsErrorBoundary>
127
+ );
128
+
129
+ expect(consoleError).not.toHaveBeenCalled();
130
+
131
+ (global as any).__DEV__ = originalDev;
132
+ });
133
+
134
+ it('resets error state when new children are provided', () => {
135
+ const { rerender, getByText, queryByText } = render(
136
+ <SettingsErrorBoundary>
137
+ <ThrowErrorComponent />
138
+ </SettingsErrorBoundary>
139
+ );
140
+
141
+ // Error state should be shown
142
+ expect(queryByText('Normal Content')).toBeNull();
143
+
144
+ // Rerender with normal content
145
+ rerender(
146
+ <SettingsErrorBoundary>
147
+ <Text>Normal Content</Text>
148
+ </SettingsErrorBoundary>
149
+ );
150
+
151
+ // Should show normal content again
152
+ expect(getByText('Normal Content')).toBeTruthy();
153
+ });
154
+
155
+ it('handles different types of errors', () => {
156
+ const StringErrorComponent = () => {
157
+ throw 'String error';
158
+ };
159
+
160
+ const { getByTestId } = render(
161
+ <SettingsErrorBoundary>
162
+ <StringErrorComponent />
163
+ </SettingsErrorBoundary>
164
+ );
165
+
166
+ expect(getByTestId('atomic-icon-AlertTriangle-warning-lg')).toBeTruthy();
167
+ });
168
+
169
+ it('handles async errors in useEffect', async () => {
170
+ const AsyncErrorComponent = () => {
171
+ React.useEffect(() => {
172
+ throw new Error('Async error');
173
+ }, []);
174
+ return <Text>Async Component</Text>;
175
+ };
176
+
177
+ const { getByTestId } = render(
178
+ <SettingsErrorBoundary>
179
+ <AsyncErrorComponent />
180
+ </SettingsErrorBoundary>
181
+ );
182
+
183
+ // Should catch the error
184
+ expect(getByTestId('atomic-icon-AlertTriangle-warning-lg')).toBeTruthy();
185
+ });
186
+ });
File without changes
@@ -4,28 +4,21 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { View, ScrollView, StatusBar, StyleSheet, TouchableOpacity } from "react-native";
8
- import { useSafeAreaInsets } from "react-native-safe-area-context";
7
+ import { View, StatusBar, StyleSheet } from "react-native";
9
8
  import { useNavigation } from "@react-navigation/native";
10
9
  import {
11
10
  useDesignSystemTheme,
12
11
  useAppDesignTokens,
13
12
  } from "@umituz/react-native-design-system-theme";
14
- import { AtomicIcon } from "@umituz/react-native-design-system-atoms";
15
- import { useLocalization } from "@umituz/react-native-localization";
16
- import { SettingsFooter } from "../components/SettingsFooter";
17
- import { UserProfileHeader } from "../components/UserProfileHeader";
18
- import { SettingsSection } from "../components/SettingsSection";
19
- import { AppearanceSection } from "./components/AppearanceSection";
20
- import { LanguageSection } from "./components/LanguageSection";
21
- import { NotificationsSection } from "./components/NotificationsSection";
22
- import { AboutLegalSection } from "./components/AboutLegalSection";
13
+ import { SettingsHeader } from "./components/SettingsHeader";
14
+ import { SettingsContent } from "./components/SettingsContent";
15
+ import { SettingsErrorBoundary } from "../components/SettingsErrorBoundary";
23
16
  import { normalizeSettingsConfig } from "./utils/normalizeConfig";
24
17
  import { useFeatureDetection } from "./hooks/useFeatureDetection";
25
- import type { CustomSettingsSection } from "./types";
18
+ import type { SettingsConfig, CustomSettingsSection } from "./types";
26
19
 
27
20
  export interface SettingsScreenProps {
28
- config?: any;
21
+ config?: SettingsConfig;
29
22
  /** Show user profile header */
30
23
  showUserProfile?: boolean;
31
24
  /** User profile props */
@@ -36,6 +29,8 @@ export interface SettingsScreenProps {
36
29
  avatarUrl?: string;
37
30
  accountSettingsRoute?: string;
38
31
  onPress?: () => void;
32
+ guestDisplayName?: string;
33
+ avatarServiceUrl?: string;
39
34
  };
40
35
  /** Show footer with version */
41
36
  showFooter?: boolean;
@@ -47,6 +42,10 @@ export interface SettingsScreenProps {
47
42
  showCloseButton?: boolean;
48
43
  /** Custom close handler */
49
44
  onClose?: () => void;
45
+ /** Feature detection options */
46
+ featureOptions?: {
47
+ notificationServiceAvailable?: boolean;
48
+ };
50
49
  }
51
50
 
52
51
  export const SettingsScreen: React.FC<SettingsScreenProps> = ({
@@ -58,138 +57,36 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
58
57
  customSections = [],
59
58
  showCloseButton = false,
60
59
  onClose,
60
+ featureOptions,
61
61
  }) => {
62
62
  const navigation = useNavigation();
63
63
  const { themeMode } = useDesignSystemTheme();
64
64
  const tokens = useAppDesignTokens();
65
- const insets = useSafeAreaInsets();
66
- const { t } = useLocalization();
67
65
 
68
66
  const isDark = themeMode === "dark";
69
67
  const colors = tokens.colors;
70
- const spacing = tokens.spacing;
71
68
 
72
69
  const normalizedConfig = normalizeSettingsConfig(config);
73
- const features = useFeatureDetection(normalizedConfig, navigation);
74
-
75
- const hasAnyFeatures =
76
- features.appearance ||
77
- features.language ||
78
- features.notifications ||
79
- features.about ||
80
- features.legal ||
81
- customSections.length > 0;
82
-
83
- const handleClose = () => {
84
- if (onClose) {
85
- onClose();
86
- } else {
87
- navigation.goBack();
88
- }
89
- };
70
+ const features = useFeatureDetection(normalizedConfig, navigation, featureOptions);
90
71
 
91
72
  return (
92
73
  <View style={[styles.container, { backgroundColor: colors.backgroundPrimary }]}>
93
74
  <StatusBar barStyle={isDark ? "light-content" : "dark-content"} />
94
-
95
- {showCloseButton && (
96
- <View
97
- style={[
98
- styles.closeButtonContainer,
99
- {
100
- paddingTop: insets.top + spacing.xs,
101
- paddingRight: spacing.md,
102
- },
103
- ]}
104
- >
105
- <TouchableOpacity
106
- onPress={handleClose}
107
- style={[
108
- styles.closeButton,
109
- {
110
- backgroundColor: colors.surface,
111
- },
112
- ]}
113
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
114
- >
115
- <AtomicIcon name="X" size="lg" color="primary" />
116
- </TouchableOpacity>
117
- </View>
118
- )}
119
-
120
- <ScrollView
121
- style={styles.scrollView}
122
- contentContainerStyle={[
123
- styles.scrollContent,
124
- {
125
- paddingTop: showCloseButton ? spacing.md : insets.top + spacing.md,
126
- paddingBottom: spacing.xxxl + spacing.xl,
127
- paddingHorizontal: 0,
128
- },
129
- ]}
130
- showsVerticalScrollIndicator={false}
131
- >
132
- {showUserProfile && (
133
- <View style={styles.profileContainer}>
134
- <UserProfileHeader
135
- displayName={userProfile?.displayName}
136
- userId={userProfile?.userId}
137
- isGuest={userProfile?.isGuest}
138
- avatarUrl={userProfile?.avatarUrl}
139
- accountSettingsRoute={userProfile?.accountSettingsRoute}
140
- onPress={userProfile?.onPress}
141
- />
142
- </View>
143
- )}
144
-
145
- {features.appearance && (
146
- <AppearanceSection config={normalizedConfig.appearance.config} />
147
- )}
148
-
149
- {features.language && (
150
- <LanguageSection config={normalizedConfig.language.config} />
151
- )}
152
-
153
- {features.notifications && (
154
- <NotificationsSection config={normalizedConfig.notifications.config} />
155
- )}
156
-
157
- {(features.about || features.legal) && (
158
- <AboutLegalSection
159
- showAbout={features.about}
160
- showLegal={features.legal}
161
- aboutConfig={normalizedConfig.about.config}
162
- legalConfig={normalizedConfig.legal.config}
163
- />
164
- )}
165
-
166
- {customSections && customSections.length > 0 && (
167
- <>
168
- {Array.from(customSections)
169
- .sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
170
- .map((section, index) => (
171
- <SettingsSection
172
- key={section.id || `custom-${index}`}
173
- title={section.title}
174
- >
175
- {section.content}
176
- </SettingsSection>
177
- ))}
178
- </>
179
- )}
180
-
181
- {!hasAnyFeatures && (
182
- <View style={styles.emptyContainer}>
183
- <SettingsSection
184
- title={t("settings.noOptionsAvailable") || "No settings available"}
185
- >
186
- <View />
187
- </SettingsSection>
188
- </View>
189
- )}
190
-
191
- {showFooter && <SettingsFooter versionText={footerText} />}
192
- </ScrollView>
75
+
76
+ <SettingsHeader showCloseButton={showCloseButton} onClose={onClose} />
77
+
78
+ <SettingsErrorBoundary>
79
+ <SettingsContent
80
+ normalizedConfig={normalizedConfig}
81
+ features={features}
82
+ showUserProfile={showUserProfile}
83
+ userProfile={userProfile}
84
+ showFooter={showFooter}
85
+ footerText={footerText}
86
+ customSections={customSections}
87
+ showCloseButton={showCloseButton}
88
+ />
89
+ </SettingsErrorBoundary>
193
90
  </View>
194
91
  );
195
92
  };
@@ -198,31 +95,4 @@ const styles = StyleSheet.create({
198
95
  container: {
199
96
  flex: 1,
200
97
  },
201
- closeButtonContainer: {
202
- position: "absolute",
203
- top: 0,
204
- right: 0,
205
- zIndex: 10,
206
- alignItems: "flex-end",
207
- },
208
- closeButton: {
209
- width: 44,
210
- height: 44,
211
- borderRadius: 22,
212
- justifyContent: "center",
213
- alignItems: "center",
214
- },
215
- scrollView: {
216
- flex: 1,
217
- },
218
- scrollContent: {
219
- flexGrow: 1,
220
- },
221
- profileContainer: {
222
- marginBottom: 32,
223
- paddingHorizontal: 0,
224
- },
225
- emptyContainer: {
226
- paddingVertical: 24,
227
- },
228
98
  });