@umituz/react-native-settings 4.20.62 → 4.21.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.
Files changed (69) hide show
  1. package/package.json +6 -61
  2. package/src/domains/gamification/components/AchievementCard.tsx +142 -0
  3. package/src/domains/gamification/components/AchievementItem.tsx +182 -0
  4. package/src/domains/gamification/components/AchievementToast.tsx +122 -0
  5. package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +84 -0
  6. package/src/domains/gamification/components/GamificationScreen/Header.tsx +29 -0
  7. package/src/domains/gamification/components/GamificationScreen/StatsGrid.tsx +51 -0
  8. package/src/domains/gamification/components/GamificationScreen/index.tsx +111 -0
  9. package/src/domains/gamification/components/GamificationScreen/styles.ts +43 -0
  10. package/src/domains/gamification/components/GamificationScreen/types.ts +77 -0
  11. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +4 -4
  12. package/src/domains/gamification/components/GamificationSettingsItem.tsx +1 -1
  13. package/src/domains/gamification/components/LevelProgress.tsx +129 -0
  14. package/src/domains/gamification/components/PointsBadge.tsx +60 -0
  15. package/src/domains/gamification/components/StatsCard.tsx +89 -0
  16. package/src/domains/gamification/components/StreakDisplay.tsx +119 -0
  17. package/src/domains/gamification/components/index.ts +13 -0
  18. package/src/domains/gamification/examples/gamification.config.example.ts +1 -1
  19. package/src/domains/gamification/hooks/useGamification.ts +91 -0
  20. package/src/domains/gamification/index.ts +46 -19
  21. package/src/domains/gamification/store/gamificationStore.ts +162 -0
  22. package/src/domains/gamification/types/index.ts +95 -23
  23. package/src/domains/gamification/types/settings.ts +28 -0
  24. package/src/domains/gamification/utils/calculations.ts +85 -0
  25. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -51
  26. package/.github/ISSUE_TEMPLATE/documentation.md +0 -52
  27. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -63
  28. package/.github/PULL_REQUEST_TEMPLATE.md +0 -84
  29. package/AI_AGENT_GUIDELINES.md +0 -367
  30. package/ARCHITECTURE.md +0 -246
  31. package/CHANGELOG.md +0 -67
  32. package/CODE_OF_CONDUCT.md +0 -75
  33. package/CONTRIBUTING.md +0 -107
  34. package/DOCUMENTATION_MIGRATION.md +0 -319
  35. package/DOCUMENTATION_TEMPLATE.md +0 -155
  36. package/SECURITY.md +0 -98
  37. package/SETTINGS_SCREEN_GUIDE.md +0 -185
  38. package/TESTING.md +0 -358
  39. package/src/__tests__/integration.test.tsx +0 -371
  40. package/src/__tests__/performance.test.tsx +0 -369
  41. package/src/__tests__/setup.test.tsx +0 -20
  42. package/src/__tests__/setup.ts +0 -154
  43. package/src/domains/about/__tests__/integration.test.tsx +0 -328
  44. package/src/domains/about/__tests__/types.d.ts +0 -5
  45. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +0 -93
  46. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +0 -153
  47. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +0 -178
  48. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +0 -293
  49. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +0 -201
  50. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +0 -71
  51. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +0 -229
  52. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +0 -240
  53. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +0 -199
  54. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +0 -366
  55. package/src/domains/about/utils/__tests__/index.test.ts +0 -408
  56. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +0 -195
  57. package/src/domains/appearance/__tests__/hooks/index.test.tsx +0 -232
  58. package/src/domains/appearance/__tests__/integration/index.test.tsx +0 -207
  59. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +0 -299
  60. package/src/domains/appearance/__tests__/setup.ts +0 -88
  61. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +0 -175
  62. package/src/domains/cloud-sync/presentation/components/__tests__/CloudSyncSetting.test.tsx +0 -78
  63. package/src/domains/legal/__tests__/ContentValidationService.test.ts +0 -195
  64. package/src/domains/legal/__tests__/StyleCacheService.test.ts +0 -110
  65. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +0 -71
  66. package/src/domains/legal/__tests__/setup.ts +0 -82
  67. package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +0 -186
  68. package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +0 -322
  69. package/src/presentation/screens/hooks/__tests__/useFeatureDetection.test.tsx +0 -261
@@ -1,195 +0,0 @@
1
- /**
2
- * Tests for ContentValidationService
3
- */
4
-
5
- import { ContentValidationService } from '../domain/services/ContentValidationService';
6
-
7
- describe('ContentValidationService', () => {
8
- describe('validateScreenContent', () => {
9
- const originalWarn = console.warn;
10
- const mockWarn = jest.fn();
11
-
12
- beforeEach(() => {
13
- mockWarn.mockClear();
14
- console.warn = mockWarn;
15
- });
16
-
17
- afterEach(() => {
18
- console.warn = originalWarn;
19
- });
20
-
21
- it('should warn when neither content nor url is provided', () => {
22
- ContentValidationService.validateScreenContent(
23
- undefined,
24
- undefined,
25
- 'Test Title',
26
- 'View Online',
27
- 'Open',
28
- 'TestScreen'
29
- );
30
-
31
- expect(mockWarn).toHaveBeenCalledWith(
32
- 'TestScreen: Either content or url must be provided'
33
- );
34
- });
35
-
36
- it('should warn when title is not provided', () => {
37
- ContentValidationService.validateScreenContent(
38
- 'Some content',
39
- undefined,
40
- undefined,
41
- 'View Online',
42
- 'Open',
43
- 'TestScreen'
44
- );
45
-
46
- expect(mockWarn).toHaveBeenCalledWith(
47
- 'TestScreen: title is required'
48
- );
49
- });
50
-
51
- it('should warn when url is provided but viewOnlineText is missing', () => {
52
- ContentValidationService.validateScreenContent(
53
- undefined,
54
- 'https://example.com',
55
- 'Test Title',
56
- undefined,
57
- 'Open',
58
- 'TestScreen'
59
- );
60
-
61
- expect(mockWarn).toHaveBeenCalledWith(
62
- 'TestScreen: viewOnlineText is required when url is provided'
63
- );
64
- });
65
-
66
- it('should warn when url is provided but openText is missing', () => {
67
- ContentValidationService.validateScreenContent(
68
- undefined,
69
- 'https://example.com',
70
- 'Test Title',
71
- 'View Online',
72
- undefined,
73
- 'TestScreen'
74
- );
75
-
76
- expect(mockWarn).toHaveBeenCalledWith(
77
- 'TestScreen: openText is required when url is provided'
78
- );
79
- });
80
-
81
- it('should not warn when all required props are provided', () => {
82
- ContentValidationService.validateScreenContent(
83
- 'Some content',
84
- undefined,
85
- 'Test Title',
86
- 'View Online',
87
- 'Open',
88
- 'TestScreen'
89
- );
90
-
91
- expect(mockWarn).not.toHaveBeenCalled();
92
- });
93
- });
94
-
95
- describe('validateLegalLinks', () => {
96
- const originalWarn = console.warn;
97
- const mockWarn = jest.fn();
98
-
99
- beforeEach(() => {
100
- mockWarn.mockClear();
101
- console.warn = mockWarn;
102
- });
103
-
104
- afterEach(() => {
105
- console.warn = originalWarn;
106
- });
107
-
108
- it('should warn when privacyPolicyUrl is provided but privacyText is missing', () => {
109
- ContentValidationService.validateLegalLinks(
110
- 'https://privacy.com',
111
- undefined,
112
- undefined,
113
- undefined,
114
- undefined,
115
- undefined
116
- );
117
-
118
- expect(mockWarn).toHaveBeenCalledWith(
119
- 'LegalLinks: privacyText is required when privacyPolicyUrl is provided'
120
- );
121
- });
122
-
123
- it('should warn when termsOfServiceUrl is provided but termsText is missing', () => {
124
- ContentValidationService.validateLegalLinks(
125
- undefined,
126
- 'https://terms.com',
127
- undefined,
128
- undefined,
129
- undefined,
130
- undefined
131
- );
132
-
133
- expect(mockWarn).toHaveBeenCalledWith(
134
- 'LegalLinks: termsText is required when termsOfServiceUrl is provided'
135
- );
136
- });
137
-
138
- it('should not warn when onPrivacyPress is provided instead of privacyText', () => {
139
- const onPrivacyPress = jest.fn();
140
-
141
- ContentValidationService.validateLegalLinks(
142
- 'https://privacy.com',
143
- undefined,
144
- undefined,
145
- undefined,
146
- onPrivacyPress,
147
- undefined
148
- );
149
-
150
- expect(mockWarn).not.toHaveBeenCalled();
151
- });
152
- });
153
-
154
- describe('hasValidContent', () => {
155
- it('should return true when content is provided', () => {
156
- expect(ContentValidationService.hasValidContent('Some content')).toBe(true);
157
- });
158
-
159
- it('should return true when url is provided', () => {
160
- expect(ContentValidationService.hasValidContent(undefined, 'https://example.com')).toBe(true);
161
- });
162
-
163
- it('should return false when neither content nor url is provided', () => {
164
- expect(ContentValidationService.hasValidContent()).toBe(false);
165
- });
166
- });
167
-
168
- describe('shouldShowUrlSection', () => {
169
- it('should return true when url is provided', () => {
170
- expect(ContentValidationService.shouldShowUrlSection('https://example.com')).toBe(true);
171
- });
172
-
173
- it('should return true when onUrlPress is provided', () => {
174
- expect(ContentValidationService.shouldShowUrlSection(undefined, jest.fn())).toBe(true);
175
- });
176
-
177
- it('should return false when neither url nor onUrlPress is provided', () => {
178
- expect(ContentValidationService.shouldShowUrlSection()).toBe(false);
179
- });
180
- });
181
-
182
- describe('shouldShowLegalItem', () => {
183
- it('should return true when both onPress and title are provided', () => {
184
- expect(ContentValidationService.shouldShowLegalItem(jest.fn(), 'Title')).toBe(true);
185
- });
186
-
187
- it('should return false when onPress is missing', () => {
188
- expect(ContentValidationService.shouldShowLegalItem(undefined, 'Title')).toBe(false);
189
- });
190
-
191
- it('should return false when title is missing', () => {
192
- expect(ContentValidationService.shouldShowLegalItem(jest.fn())).toBe(false);
193
- });
194
- });
195
- });
@@ -1,110 +0,0 @@
1
- /**
2
- * Tests for StyleCacheService
3
- */
4
-
5
- import { StyleCacheService } from '../domain/services/StyleCacheService';
6
-
7
- describe('StyleCacheService', () => {
8
- beforeEach(() => {
9
- StyleCacheService.clearAllCaches();
10
- });
11
-
12
- describe('getCachedStyles', () => {
13
- it('should cache and return styles', () => {
14
- const styleFactory = () => ({ test: 'style' });
15
- const result1 = StyleCacheService.getCachedStyles('test', 'key1', styleFactory);
16
- const result2 = StyleCacheService.getCachedStyles('test', 'key1', styleFactory);
17
-
18
- expect(result1).toEqual({ test: 'style' });
19
- expect(result1).toBe(result2); // Should be the same reference
20
- });
21
-
22
- it('should create new styles for different keys', () => {
23
- const styleFactory = () => ({ test: 'style' });
24
- const result1 = StyleCacheService.getCachedStyles('test', 'key1', styleFactory);
25
- const result2 = StyleCacheService.getCachedStyles('test', 'key2', styleFactory);
26
-
27
- expect(result1).toEqual(result2);
28
- expect(result1).not.toBe(result2); // Should be different references
29
- });
30
-
31
- it('should limit cache size', () => {
32
- const styleFactory = (index: number) => ({ test: `style${index}` });
33
- const maxSize = 3;
34
-
35
- // Fill cache beyond max size
36
- for (let i = 0; i < maxSize + 2; i++) {
37
- StyleCacheService.getCachedStyles('test', `key${i}`, () => styleFactory(i), maxSize);
38
- }
39
-
40
- // Cache should be at most maxSize + 1 (since cleanup happens after adding)
41
- expect(StyleCacheService.getCacheSize('test')).toBeLessThanOrEqual(maxSize + 1);
42
- });
43
- });
44
-
45
- describe('clearCache', () => {
46
- it('should clear specific cache', () => {
47
- const styleFactory = () => ({ test: 'style' });
48
- StyleCacheService.getCachedStyles('test1', 'key1', styleFactory);
49
- StyleCacheService.getCachedStyles('test2', 'key1', styleFactory);
50
-
51
- expect(StyleCacheService.getCacheSize('test1')).toBe(1);
52
- expect(StyleCacheService.getCacheSize('test2')).toBe(1);
53
-
54
- StyleCacheService.clearCache('test1');
55
-
56
- expect(StyleCacheService.getCacheSize('test1')).toBe(0);
57
- expect(StyleCacheService.getCacheSize('test2')).toBe(1);
58
- });
59
- });
60
-
61
- describe('clearAllCaches', () => {
62
- it('should clear all caches', () => {
63
- const styleFactory = () => ({ test: 'style' });
64
- StyleCacheService.getCachedStyles('test1', 'key1', styleFactory);
65
- StyleCacheService.getCachedStyles('test2', 'key1', styleFactory);
66
-
67
- expect(StyleCacheService.getCacheSize('test1')).toBe(1);
68
- expect(StyleCacheService.getCacheSize('test2')).toBe(1);
69
-
70
- StyleCacheService.clearAllCaches();
71
-
72
- expect(StyleCacheService.getCacheSize('test1')).toBe(0);
73
- expect(StyleCacheService.getCacheSize('test2')).toBe(0);
74
- });
75
- });
76
-
77
- describe('createTokenCacheKey', () => {
78
- it('should create cache key from design tokens', () => {
79
- const tokens = {
80
- colors: { backgroundPrimary: '#fff', primary: '#000', textPrimary: '#333', textSecondary: '#666', textTertiary: '#999', onSurface: '#111', secondary: '#555', info: '#00f' },
81
- spacing: { xs: 4, sm: 8, md: 16, lg: 24 }
82
- };
83
-
84
- const key1 = StyleCacheService.createTokenCacheKey(tokens);
85
- const key2 = StyleCacheService.createTokenCacheKey(tokens);
86
-
87
- expect(key1).toBe(key2);
88
- expect(typeof key1).toBe('string');
89
- });
90
-
91
- it('should include additional keys in cache key', () => {
92
- const tokens = {
93
- colors: { backgroundPrimary: '#fff', primary: '#000', textPrimary: '#333', textSecondary: '#666', textTertiary: '#999', onSurface: '#111', secondary: '#555', info: '#00f' },
94
- spacing: { xs: 4, sm: 8, md: 16, lg: 24 }
95
- };
96
-
97
- const key1 = StyleCacheService.createTokenCacheKey(tokens, { extra: 'value' });
98
- const key2 = StyleCacheService.createTokenCacheKey(tokens, { extra: 'different' });
99
-
100
- expect(key1).not.toBe(key2);
101
- });
102
- });
103
-
104
- describe('createSimpleCacheKey', () => {
105
- it('should create simple cache key', () => {
106
- const key = StyleCacheService.createSimpleCacheKey('test-key');
107
- expect(key).toBe('test-key');
108
- });
109
- });
110
- });
@@ -1,71 +0,0 @@
1
- /**
2
- * Tests for UrlHandlerService
3
- */
4
-
5
- import { UrlHandlerService } from '../domain/services/UrlHandlerService';
6
-
7
- describe('UrlHandlerService', () => {
8
- beforeEach(() => {
9
- UrlHandlerService.clearCache();
10
- });
11
-
12
- describe('isValidUrl', () => {
13
- it('should return true for valid HTTP URLs', () => {
14
- expect(UrlHandlerService['isValidUrl']('http://example.com')).toBe(true);
15
- expect(UrlHandlerService['isValidUrl']('https://example.com')).toBe(true);
16
- });
17
-
18
- it('should return true for valid mailto URLs', () => {
19
- expect(UrlHandlerService['isValidUrl']('mailto:test@example.com')).toBe(true);
20
- });
21
-
22
- it('should return true for valid tel URLs', () => {
23
- expect(UrlHandlerService['isValidUrl']('tel:+1234567890')).toBe(true);
24
- });
25
-
26
- it('should return true for valid FTP URLs', () => {
27
- expect(UrlHandlerService['isValidUrl']('ftp://example.com')).toBe(true);
28
- });
29
-
30
- it('should return false for invalid URLs', () => {
31
- expect(UrlHandlerService['isValidUrl']('')).toBe(false);
32
- expect(UrlHandlerService['isValidUrl']('invalid-url')).toBe(false);
33
- expect(UrlHandlerService['isValidUrl']('www.example.com')).toBe(false);
34
- expect(UrlHandlerService['isValidUrl'](undefined as any)).toBe(false);
35
- expect(UrlHandlerService['isValidUrl'](null as any)).toBe(false);
36
- expect(UrlHandlerService['isValidUrl'](123 as any)).toBe(false);
37
- });
38
- });
39
-
40
- describe('canOpenUrl', () => {
41
- it('should return false for invalid URLs', async () => {
42
- const result = await UrlHandlerService.canOpenUrl('invalid-url');
43
- expect(result).toBe(false);
44
- });
45
-
46
- it('should cache results', async () => {
47
- const mockLinking = {
48
- canOpenURL: jest.fn().mockResolvedValue(true)
49
- };
50
-
51
- // Mock the Linking module
52
- jest.doMock('react-native', () => ({
53
- Linking: mockLinking
54
- }));
55
-
56
- const url = 'https://example.com';
57
- await UrlHandlerService.canOpenUrl(url);
58
- await UrlHandlerService.canOpenUrl(url);
59
-
60
- expect(mockLinking.canOpenURL).toHaveBeenCalledTimes(1);
61
- });
62
- });
63
-
64
- describe('clearCache', () => {
65
- it('should clear all caches', () => {
66
- UrlHandlerService.clearCache();
67
- // Test passes if no errors are thrown
68
- expect(true).toBe(true);
69
- });
70
- });
71
- });
@@ -1,82 +0,0 @@
1
- /**
2
- * Jest setup file
3
- */
4
-
5
- // Mock React Native modules
6
- jest.mock('react-native', () => ({
7
- View: 'View',
8
- Text: 'Text',
9
- ScrollView: 'ScrollView',
10
- TouchableOpacity: 'TouchableOpacity',
11
- StyleSheet: {
12
- create: jest.fn((styles) => styles),
13
- },
14
- Linking: {
15
- canOpenURL: jest.fn(() => Promise.resolve(true)),
16
- openURL: jest.fn(() => Promise.resolve()),
17
- },
18
- Platform: {
19
- OS: 'ios',
20
- select: jest.fn((obj) => obj.ios),
21
- },
22
- }));
23
-
24
- // Mock design system modules (only if they exist)
25
- try {
26
- jest.mock('@umituz/react-native-design-system', () => ({
27
- useAppDesignTokens: jest.fn(() => ({
28
- colors: {
29
- backgroundPrimary: '#ffffff',
30
- primary: '#007AFF',
31
- textPrimary: '#000000',
32
- textSecondary: '#666666',
33
- textTertiary: '#999999',
34
- onSurface: '#000000',
35
- secondary: '#5856D6',
36
- info: '#007AFF',
37
- },
38
- spacing: {
39
- xs: 4,
40
- sm: 8,
41
- md: 16,
42
- lg: 24,
43
- },
44
- })),
45
- }));
46
- } catch (e) {
47
- // Module not found, skip mocking
48
- }
49
-
50
- try {
51
- jest.mock('@umituz/react-native-design-system', () => ({
52
- AtomicText: 'AtomicText',
53
- AtomicIcon: 'AtomicIcon',
54
- AtomicButton: 'AtomicButton',
55
- }));
56
- } catch (e) {
57
- // Module not found, skip mocking
58
- }
59
-
60
- try {
61
- jest.mock('@umituz/react-native-design-system', () => ({
62
- ScreenLayout: 'ScreenLayout',
63
- }));
64
- } catch (e) {
65
- // Module not found, skip mocking
66
- }
67
-
68
- try {
69
- jest.mock('react-native-safe-area-context', () => ({
70
- useSafeAreaInsets: jest.fn(() => ({
71
- top: 44,
72
- bottom: 34,
73
- left: 0,
74
- right: 0,
75
- })),
76
- }));
77
- } catch (e) {
78
- // Module not found, skip mocking
79
- }
80
-
81
- // Global test setup
82
- (global as any).__DEV__ = true;
@@ -1,186 +0,0 @@
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', () => ({
11
- useAppDesignTokens: () => ({
12
- colors: {
13
- backgroundPrimary: '#ffffff',
14
- surface: '#f5f5f5',
15
- },
16
- }),
17
- }));
18
-
19
- jest.mock('@umituz/react-native-design-system', () => ({
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
- });