@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.
- package/package.json +6 -61
- package/src/domains/feedback/domain/entities/FeedbackEntity.ts +8 -8
- package/src/domains/gamification/components/AchievementCard.tsx +142 -0
- package/src/domains/gamification/components/AchievementItem.tsx +182 -0
- package/src/domains/gamification/components/AchievementToast.tsx +122 -0
- package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +84 -0
- package/src/domains/gamification/components/GamificationScreen/Header.tsx +29 -0
- package/src/domains/gamification/components/GamificationScreen/StatsGrid.tsx +51 -0
- package/src/domains/gamification/components/GamificationScreen/index.tsx +111 -0
- package/src/domains/gamification/components/GamificationScreen/styles.ts +43 -0
- package/src/domains/gamification/components/GamificationScreen/types.ts +77 -0
- package/src/domains/gamification/components/GamificationScreenWrapper.tsx +4 -4
- package/src/domains/gamification/components/GamificationSettingsItem.tsx +1 -1
- package/src/domains/gamification/components/LevelProgress.tsx +129 -0
- package/src/domains/gamification/components/PointsBadge.tsx +60 -0
- package/src/domains/gamification/components/StatsCard.tsx +89 -0
- package/src/domains/gamification/components/StreakDisplay.tsx +119 -0
- package/src/domains/gamification/components/index.ts +13 -0
- package/src/domains/gamification/examples/gamification.config.example.ts +1 -1
- package/src/domains/gamification/hooks/useGamification.ts +91 -0
- package/src/domains/gamification/index.ts +46 -19
- package/src/domains/gamification/store/gamificationStore.ts +162 -0
- package/src/domains/gamification/types/index.ts +95 -23
- package/src/domains/gamification/types/settings.ts +28 -0
- package/src/domains/gamification/utils/calculations.ts +85 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -51
- package/.github/ISSUE_TEMPLATE/documentation.md +0 -52
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -63
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -84
- package/AI_AGENT_GUIDELINES.md +0 -367
- package/ARCHITECTURE.md +0 -246
- package/CHANGELOG.md +0 -67
- package/CODE_OF_CONDUCT.md +0 -75
- package/CONTRIBUTING.md +0 -107
- package/DOCUMENTATION_MIGRATION.md +0 -319
- package/DOCUMENTATION_TEMPLATE.md +0 -155
- package/SECURITY.md +0 -98
- package/SETTINGS_SCREEN_GUIDE.md +0 -185
- package/TESTING.md +0 -358
- package/src/__tests__/integration.test.tsx +0 -371
- package/src/__tests__/performance.test.tsx +0 -369
- package/src/__tests__/setup.test.tsx +0 -20
- package/src/__tests__/setup.ts +0 -154
- package/src/domains/about/__tests__/integration.test.tsx +0 -328
- package/src/domains/about/__tests__/types.d.ts +0 -5
- package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +0 -93
- package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +0 -153
- package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +0 -178
- package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +0 -293
- package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +0 -201
- package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +0 -71
- package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +0 -229
- package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +0 -240
- package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +0 -199
- package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +0 -366
- package/src/domains/about/utils/__tests__/index.test.ts +0 -408
- package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +0 -195
- package/src/domains/appearance/__tests__/hooks/index.test.tsx +0 -232
- package/src/domains/appearance/__tests__/integration/index.test.tsx +0 -207
- package/src/domains/appearance/__tests__/services/appearanceService.test.ts +0 -299
- package/src/domains/appearance/__tests__/setup.ts +0 -88
- package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +0 -175
- package/src/domains/cloud-sync/presentation/components/__tests__/CloudSyncSetting.test.tsx +0 -78
- package/src/domains/legal/__tests__/ContentValidationService.test.ts +0 -195
- package/src/domains/legal/__tests__/StyleCacheService.test.ts +0 -110
- package/src/domains/legal/__tests__/UrlHandlerService.test.ts +0 -71
- package/src/domains/legal/__tests__/setup.ts +0 -82
- package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +0 -186
- package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +0 -322
- 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
|
-
});
|