@umituz/react-native-settings 4.20.62 → 4.21.2

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 (70) hide show
  1. package/package.json +6 -61
  2. package/src/domains/feedback/domain/entities/FeedbackEntity.ts +8 -8
  3. package/src/domains/gamification/components/AchievementCard.tsx +142 -0
  4. package/src/domains/gamification/components/AchievementItem.tsx +182 -0
  5. package/src/domains/gamification/components/AchievementToast.tsx +122 -0
  6. package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +84 -0
  7. package/src/domains/gamification/components/GamificationScreen/Header.tsx +29 -0
  8. package/src/domains/gamification/components/GamificationScreen/StatsGrid.tsx +51 -0
  9. package/src/domains/gamification/components/GamificationScreen/index.tsx +111 -0
  10. package/src/domains/gamification/components/GamificationScreen/styles.ts +43 -0
  11. package/src/domains/gamification/components/GamificationScreen/types.ts +77 -0
  12. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +4 -4
  13. package/src/domains/gamification/components/GamificationSettingsItem.tsx +1 -1
  14. package/src/domains/gamification/components/LevelProgress.tsx +129 -0
  15. package/src/domains/gamification/components/PointsBadge.tsx +60 -0
  16. package/src/domains/gamification/components/StatsCard.tsx +89 -0
  17. package/src/domains/gamification/components/StreakDisplay.tsx +119 -0
  18. package/src/domains/gamification/components/index.ts +13 -0
  19. package/src/domains/gamification/examples/gamification.config.example.ts +1 -1
  20. package/src/domains/gamification/hooks/useGamification.ts +91 -0
  21. package/src/domains/gamification/index.ts +46 -19
  22. package/src/domains/gamification/store/gamificationStore.ts +162 -0
  23. package/src/domains/gamification/types/index.ts +95 -23
  24. package/src/domains/gamification/types/settings.ts +28 -0
  25. package/src/domains/gamification/utils/calculations.ts +85 -0
  26. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -51
  27. package/.github/ISSUE_TEMPLATE/documentation.md +0 -52
  28. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -63
  29. package/.github/PULL_REQUEST_TEMPLATE.md +0 -84
  30. package/AI_AGENT_GUIDELINES.md +0 -367
  31. package/ARCHITECTURE.md +0 -246
  32. package/CHANGELOG.md +0 -67
  33. package/CODE_OF_CONDUCT.md +0 -75
  34. package/CONTRIBUTING.md +0 -107
  35. package/DOCUMENTATION_MIGRATION.md +0 -319
  36. package/DOCUMENTATION_TEMPLATE.md +0 -155
  37. package/SECURITY.md +0 -98
  38. package/SETTINGS_SCREEN_GUIDE.md +0 -185
  39. package/TESTING.md +0 -358
  40. package/src/__tests__/integration.test.tsx +0 -371
  41. package/src/__tests__/performance.test.tsx +0 -369
  42. package/src/__tests__/setup.test.tsx +0 -20
  43. package/src/__tests__/setup.ts +0 -154
  44. package/src/domains/about/__tests__/integration.test.tsx +0 -328
  45. package/src/domains/about/__tests__/types.d.ts +0 -5
  46. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +0 -93
  47. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +0 -153
  48. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +0 -178
  49. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +0 -293
  50. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +0 -201
  51. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +0 -71
  52. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +0 -229
  53. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +0 -240
  54. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +0 -199
  55. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +0 -366
  56. package/src/domains/about/utils/__tests__/index.test.ts +0 -408
  57. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +0 -195
  58. package/src/domains/appearance/__tests__/hooks/index.test.tsx +0 -232
  59. package/src/domains/appearance/__tests__/integration/index.test.tsx +0 -207
  60. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +0 -299
  61. package/src/domains/appearance/__tests__/setup.ts +0 -88
  62. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +0 -175
  63. package/src/domains/cloud-sync/presentation/components/__tests__/CloudSyncSetting.test.tsx +0 -78
  64. package/src/domains/legal/__tests__/ContentValidationService.test.ts +0 -195
  65. package/src/domains/legal/__tests__/StyleCacheService.test.ts +0 -110
  66. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +0 -71
  67. package/src/domains/legal/__tests__/setup.ts +0 -82
  68. package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +0 -186
  69. package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +0 -322
  70. 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
- });