@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,408 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for utility functions
|
|
3
|
-
*/
|
|
4
|
-
import '../../types/global.d.ts';
|
|
5
|
-
import { Linking } from 'react-native';
|
|
6
|
-
import {
|
|
7
|
-
createDefaultConfig,
|
|
8
|
-
validateConfig,
|
|
9
|
-
mergeConfigs,
|
|
10
|
-
isValidEmail,
|
|
11
|
-
isValidUrl,
|
|
12
|
-
openUrl,
|
|
13
|
-
sendEmail,
|
|
14
|
-
} from '../index';
|
|
15
|
-
|
|
16
|
-
// Mock Linking
|
|
17
|
-
jest.mock('react-native', () => ({
|
|
18
|
-
Linking: {
|
|
19
|
-
canOpenURL: jest.fn(),
|
|
20
|
-
openURL: jest.fn(),
|
|
21
|
-
},
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
// Mock console methods
|
|
25
|
-
jest.spyOn(console, 'log').mockImplementation();
|
|
26
|
-
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
|
|
27
|
-
|
|
28
|
-
describe('Utils', () => {
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
jest.clearAllMocks();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('createDefaultConfig', () => {
|
|
34
|
-
it('should create default config', () => {
|
|
35
|
-
const config = createDefaultConfig();
|
|
36
|
-
|
|
37
|
-
expect(config).toEqual({
|
|
38
|
-
appInfo: {
|
|
39
|
-
name: '',
|
|
40
|
-
version: '1.0.0',
|
|
41
|
-
description: '',
|
|
42
|
-
developer: '',
|
|
43
|
-
contactEmail: '',
|
|
44
|
-
websiteUrl: '',
|
|
45
|
-
websiteDisplay: '',
|
|
46
|
-
moreAppsUrl: '',
|
|
47
|
-
privacyPolicyUrl: '',
|
|
48
|
-
termsOfServiceUrl: '',
|
|
49
|
-
},
|
|
50
|
-
theme: {
|
|
51
|
-
primary: '#007AFF',
|
|
52
|
-
secondary: '#5856D6',
|
|
53
|
-
background: '#FFFFFF',
|
|
54
|
-
text: '#000000',
|
|
55
|
-
border: '#E5E5E5',
|
|
56
|
-
},
|
|
57
|
-
style: {
|
|
58
|
-
containerStyle: {},
|
|
59
|
-
itemStyle: {},
|
|
60
|
-
textStyle: {},
|
|
61
|
-
iconStyle: {},
|
|
62
|
-
},
|
|
63
|
-
actions: {
|
|
64
|
-
onWebsitePress: undefined,
|
|
65
|
-
onEmailPress: undefined,
|
|
66
|
-
onPrivacyPress: undefined,
|
|
67
|
-
onTermsPress: undefined,
|
|
68
|
-
onMoreAppsPress: undefined,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should merge with overrides', () => {
|
|
74
|
-
const overrides = {
|
|
75
|
-
appInfo: {
|
|
76
|
-
name: 'Test App',
|
|
77
|
-
version: '2.0.0',
|
|
78
|
-
},
|
|
79
|
-
theme: {
|
|
80
|
-
primary: '#FF0000',
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const config = createDefaultConfig(overrides);
|
|
85
|
-
|
|
86
|
-
expect(config.appInfo.name).toBe('Test App');
|
|
87
|
-
expect(config.appInfo.version).toBe('2.0.0');
|
|
88
|
-
expect(config.appInfo.description).toBe(''); // Should keep default
|
|
89
|
-
expect(config.theme.primary).toBe('#FF0000');
|
|
90
|
-
expect(config.theme.secondary).toBe('#5856D6'); // Should keep default
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should handle empty overrides', () => {
|
|
94
|
-
const config = createDefaultConfig({});
|
|
95
|
-
|
|
96
|
-
expect(config.appInfo.name).toBe('');
|
|
97
|
-
expect(config.theme.primary).toBe('#007AFF');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should handle null/undefined overrides', () => {
|
|
101
|
-
const config = createDefaultConfig(null as unknown);
|
|
102
|
-
|
|
103
|
-
expect(config.appInfo.name).toBe('');
|
|
104
|
-
expect(config.theme.primary).toBe('#007AFF');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('validateConfig', () => {
|
|
109
|
-
it('should validate valid config', () => {
|
|
110
|
-
const validConfig = {
|
|
111
|
-
appInfo: {
|
|
112
|
-
name: 'Test App',
|
|
113
|
-
version: '1.0.0',
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
expect(validateConfig(validConfig)).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should reject invalid config', () => {
|
|
121
|
-
expect(validateConfig(null)).toBe(false);
|
|
122
|
-
expect(validateConfig(undefined)).toBe(false);
|
|
123
|
-
expect(validateConfig('invalid')).toBe(false);
|
|
124
|
-
expect(validateConfig(123)).toBe(false);
|
|
125
|
-
expect(validateConfig([])).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should reject config without appInfo', () => {
|
|
129
|
-
const configWithoutAppInfo = {
|
|
130
|
-
theme: {
|
|
131
|
-
primary: '#FF0000',
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
expect(validateConfig(configWithoutAppInfo)).toBe(false);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should reject config with invalid appInfo', () => {
|
|
139
|
-
const configWithInvalidAppInfo = {
|
|
140
|
-
appInfo: 'invalid',
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
expect(validateConfig(configWithInvalidAppInfo)).toBe(false);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should accept config with empty appInfo', () => {
|
|
147
|
-
const configWithEmptyAppInfo = {
|
|
148
|
-
appInfo: {},
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
expect(validateConfig(configWithEmptyAppInfo)).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe('mergeConfigs', () => {
|
|
156
|
-
it('should merge multiple configs', () => {
|
|
157
|
-
const config1 = { appInfo: { name: 'App1' } };
|
|
158
|
-
const config2 = { appInfo: { version: '1.0.0' } };
|
|
159
|
-
const config3 = { theme: { primary: '#FF0000' } };
|
|
160
|
-
|
|
161
|
-
const merged = mergeConfigs(config1, config2, config3);
|
|
162
|
-
|
|
163
|
-
expect(merged).toEqual({
|
|
164
|
-
appInfo: {
|
|
165
|
-
name: 'App1',
|
|
166
|
-
version: '1.0.0',
|
|
167
|
-
},
|
|
168
|
-
theme: {
|
|
169
|
-
primary: '#FF0000',
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('should handle empty configs', () => {
|
|
175
|
-
const merged = mergeConfigs();
|
|
176
|
-
|
|
177
|
-
expect(merged).toEqual({});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should handle null/undefined configs', () => {
|
|
181
|
-
const config1 = { appInfo: { name: 'App1' } };
|
|
182
|
-
|
|
183
|
-
const merged = mergeConfigs(config1, null, undefined, {} as unknown);
|
|
184
|
-
|
|
185
|
-
expect(merged).toEqual({
|
|
186
|
-
appInfo: {
|
|
187
|
-
name: 'App1',
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should override with later configs', () => {
|
|
193
|
-
const config1 = { appInfo: { name: 'App1', version: '1.0.0' } };
|
|
194
|
-
const config2 = { appInfo: { name: 'App2' } };
|
|
195
|
-
|
|
196
|
-
const merged = mergeConfigs(config1, config2);
|
|
197
|
-
|
|
198
|
-
expect(merged.appInfo.name).toBe('App2');
|
|
199
|
-
expect(merged.appInfo.version).toBe('1.0.0');
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
describe('isValidEmail', () => {
|
|
204
|
-
it('should validate valid emails', () => {
|
|
205
|
-
expect(isValidEmail('test@example.com')).toBe(true);
|
|
206
|
-
expect(isValidEmail('user.name@domain.co.uk')).toBe(true);
|
|
207
|
-
expect(isValidEmail('user+tag@example.org')).toBe(true);
|
|
208
|
-
expect(isValidEmail('user123@test-domain.com')).toBe(true);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('should reject invalid emails', () => {
|
|
212
|
-
expect(isValidEmail('')).toBe(false);
|
|
213
|
-
expect(isValidEmail('invalid')).toBe(false);
|
|
214
|
-
expect(isValidEmail('test@')).toBe(false);
|
|
215
|
-
expect(isValidEmail('@example.com')).toBe(false);
|
|
216
|
-
expect(isValidEmail('test@.com')).toBe(false);
|
|
217
|
-
expect(isValidEmail('test@example')).toBe(false);
|
|
218
|
-
expect(isValidEmail('test..test@example.com')).toBe(false);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('should handle edge cases', () => {
|
|
222
|
-
expect(isValidEmail('a@b.c')).toBe(true);
|
|
223
|
-
expect(isValidEmail('test@example.c')).toBe(true);
|
|
224
|
-
expect(isValidEmail('test@123.456.789.0')).toBe(true);
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe('isValidUrl', () => {
|
|
229
|
-
it('should validate valid URLs', () => {
|
|
230
|
-
expect(isValidUrl('https://example.com')).toBe(true);
|
|
231
|
-
expect(isValidUrl('http://example.com')).toBe(true);
|
|
232
|
-
expect(isValidUrl('https://www.example.com')).toBe(true);
|
|
233
|
-
expect(isValidUrl('https://example.com/path')).toBe(true);
|
|
234
|
-
expect(isValidUrl('https://example.com/path?query=value')).toBe(true);
|
|
235
|
-
expect(isValidUrl('https://example.com/path#fragment')).toBe(true);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('should reject invalid URLs', () => {
|
|
239
|
-
expect(isValidUrl('')).toBe(false);
|
|
240
|
-
expect(isValidUrl('not-a-url')).toBe(false);
|
|
241
|
-
expect(isValidUrl('example.com')).toBe(false);
|
|
242
|
-
expect(isValidUrl('ftp://example.com')).toBe(false);
|
|
243
|
-
expect(isValidUrl('://example.com')).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('should handle edge cases', () => {
|
|
247
|
-
expect(isValidUrl('http://localhost')).toBe(true);
|
|
248
|
-
expect(isValidUrl('https://192.168.1.1')).toBe(true);
|
|
249
|
-
expect(isValidUrl('https://example.com:8080')).toBe(true);
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe('openUrl', () => {
|
|
254
|
-
|
|
255
|
-
beforeEach(() => {
|
|
256
|
-
Linking.canOpenURL.mockClear();
|
|
257
|
-
Linking.openURL.mockClear();
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('should open valid URL in browser environment', async () => {
|
|
261
|
-
// Mock window.open directly since jsdom already provides window
|
|
262
|
-
const originalOpen = window.open;
|
|
263
|
-
const mockOpen = jest.fn().mockReturnValue(true);
|
|
264
|
-
window.open = mockOpen;
|
|
265
|
-
|
|
266
|
-
const result = await openUrl('https://example.com');
|
|
267
|
-
|
|
268
|
-
expect(mockOpen).toHaveBeenCalledWith('https://example.com', '_blank');
|
|
269
|
-
expect(result).toBe(true);
|
|
270
|
-
|
|
271
|
-
// Restore original
|
|
272
|
-
window.open = originalOpen;
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should open valid URL in React Native environment', async () => {
|
|
276
|
-
// Remove window object to force React Native path
|
|
277
|
-
const originalWindow = global.window;
|
|
278
|
-
delete global.window;
|
|
279
|
-
|
|
280
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
281
|
-
Linking.openURL.mockResolvedValue();
|
|
282
|
-
|
|
283
|
-
const result = await openUrl('https://example.com');
|
|
284
|
-
|
|
285
|
-
expect(Linking.canOpenURL).toHaveBeenCalledWith('https://example.com');
|
|
286
|
-
expect(Linking.openURL).toHaveBeenCalledWith('https://example.com');
|
|
287
|
-
expect(result).toBe(true);
|
|
288
|
-
|
|
289
|
-
global.window = originalWindow;
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('should handle URL that cannot be opened', async () => {
|
|
293
|
-
// Remove window object to force React Native path
|
|
294
|
-
const originalWindow = global.window;
|
|
295
|
-
delete global.window;
|
|
296
|
-
|
|
297
|
-
Linking.canOpenURL.mockResolvedValue(false);
|
|
298
|
-
|
|
299
|
-
const result = await openUrl('https://example.com');
|
|
300
|
-
|
|
301
|
-
expect(Linking.canOpenURL).toHaveBeenCalledWith('https://example.com');
|
|
302
|
-
expect(Linking.openURL).not.toHaveBeenCalled();
|
|
303
|
-
expect(result).toBe(false);
|
|
304
|
-
|
|
305
|
-
global.window = originalWindow;
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('should handle errors', async () => {
|
|
309
|
-
// Remove window object to force React Native path
|
|
310
|
-
const originalWindow = global.window;
|
|
311
|
-
delete global.window;
|
|
312
|
-
|
|
313
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
314
|
-
Linking.openURL.mockRejectedValue(new Error('Failed to open'));
|
|
315
|
-
|
|
316
|
-
const result = await openUrl('https://example.com');
|
|
317
|
-
|
|
318
|
-
expect(result).toBe(false);
|
|
319
|
-
expect(mockConsoleError).toHaveBeenCalledWith('Failed to open URL:', expect.any(Error));
|
|
320
|
-
|
|
321
|
-
global.window = originalWindow;
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('should log errors in development', async () => {
|
|
325
|
-
const originalDev = global.__DEV__;
|
|
326
|
-
global.__DEV__ = true;
|
|
327
|
-
|
|
328
|
-
// Remove window object to force React Native path
|
|
329
|
-
const originalWindow = global.window;
|
|
330
|
-
delete global.window;
|
|
331
|
-
|
|
332
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
333
|
-
Linking.openURL.mockRejectedValue(new Error('Failed to open'));
|
|
334
|
-
|
|
335
|
-
await openUrl('https://example.com');
|
|
336
|
-
|
|
337
|
-
expect(mockConsoleError).toHaveBeenCalledWith('Failed to open URL:', expect.any(Error));
|
|
338
|
-
|
|
339
|
-
global.__DEV__ = originalDev;
|
|
340
|
-
global.window = originalWindow;
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
describe('sendEmail', () => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
beforeEach(() => {
|
|
348
|
-
Linking.canOpenURL.mockClear();
|
|
349
|
-
Linking.openURL.mockClear();
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should send email without subject', async () => {
|
|
353
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
354
|
-
Linking.openURL.mockResolvedValue();
|
|
355
|
-
|
|
356
|
-
const result = await sendEmail('test@example.com');
|
|
357
|
-
|
|
358
|
-
expect(Linking.canOpenURL).toHaveBeenCalledWith('mailto:test@example.com');
|
|
359
|
-
expect(Linking.openURL).toHaveBeenCalledWith('mailto:test@example.com');
|
|
360
|
-
expect(result).toBe(true);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
it('should send email with subject', async () => {
|
|
364
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
365
|
-
Linking.openURL.mockResolvedValue();
|
|
366
|
-
|
|
367
|
-
const result = await sendEmail('test@example.com', 'Test Subject');
|
|
368
|
-
|
|
369
|
-
expect(Linking.canOpenURL).toHaveBeenCalledWith('mailto:test@example.com?subject=Test%20Subject');
|
|
370
|
-
expect(Linking.openURL).toHaveBeenCalledWith('mailto:test@example.com?subject=Test%20Subject');
|
|
371
|
-
expect(result).toBe(true);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it('should handle email that cannot be sent', async () => {
|
|
375
|
-
Linking.canOpenURL.mockResolvedValue(false);
|
|
376
|
-
|
|
377
|
-
const result = await sendEmail('test@example.com');
|
|
378
|
-
|
|
379
|
-
expect(Linking.canOpenURL).toHaveBeenCalledWith('mailto:test@example.com');
|
|
380
|
-
expect(Linking.openURL).not.toHaveBeenCalled();
|
|
381
|
-
expect(result).toBe(false);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it('should handle errors', async () => {
|
|
385
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
386
|
-
Linking.openURL.mockRejectedValue(new Error('Failed to send email'));
|
|
387
|
-
|
|
388
|
-
const result = await sendEmail('test@example.com');
|
|
389
|
-
|
|
390
|
-
expect(result).toBe(false);
|
|
391
|
-
expect(mockConsoleError).toHaveBeenCalledWith('Failed to send email:', expect.any(Error));
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('should log errors in development', async () => {
|
|
395
|
-
const originalDev = global.__DEV__;
|
|
396
|
-
global.__DEV__ = true;
|
|
397
|
-
|
|
398
|
-
Linking.canOpenURL.mockResolvedValue(true);
|
|
399
|
-
Linking.openURL.mockRejectedValue(new Error('Failed to send email'));
|
|
400
|
-
|
|
401
|
-
await sendEmail('test@example.com');
|
|
402
|
-
|
|
403
|
-
expect(mockConsoleError).toHaveBeenCalledWith('Failed to send email:', expect.any(Error));
|
|
404
|
-
|
|
405
|
-
global.__DEV__ = originalDev;
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
});
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import React from 'react';
|
|
6
|
-
import { render, fireEvent, act } from '@testing-library/react-native';
|
|
7
|
-
import { AppearanceScreen } from '../../presentation/screens/AppearanceScreen';
|
|
8
|
-
import { ThemeOption } from '../../presentation/components/ThemeOption';
|
|
9
|
-
import { ColorPicker } from '../../presentation/components/ColorPicker';
|
|
10
|
-
import type { ThemeMode, CustomThemeColors } from '../../types';
|
|
11
|
-
|
|
12
|
-
// Mock hooks
|
|
13
|
-
jest.mock('../../hooks', () => ({
|
|
14
|
-
useAppearance: jest.fn(() => ({
|
|
15
|
-
themeMode: 'dark',
|
|
16
|
-
customColors: {},
|
|
17
|
-
isInitialized: true,
|
|
18
|
-
setThemeMode: jest.fn(),
|
|
19
|
-
toggleTheme: jest.fn(),
|
|
20
|
-
setCustomColors: jest.fn(),
|
|
21
|
-
resetCustomColors: jest.fn(),
|
|
22
|
-
reset: jest.fn(),
|
|
23
|
-
})),
|
|
24
|
-
useAppearanceActions: jest.fn(() => ({
|
|
25
|
-
localCustomColors: {},
|
|
26
|
-
handleThemeSelect: jest.fn(),
|
|
27
|
-
handleColorChange: jest.fn(),
|
|
28
|
-
handleResetColors: jest.fn(),
|
|
29
|
-
})),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
const mockUseAppearance = require('../../hooks').useAppearance;
|
|
33
|
-
const mockUseAppearanceActions = require('../../hooks').useAppearanceActions;
|
|
34
|
-
|
|
35
|
-
describe('AppearanceScreen', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
jest.clearAllMocks();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
jest.restoreAllMocks();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should render correctly with default props', () => {
|
|
45
|
-
const { getByTestId } = render(<AppearanceScreen />);
|
|
46
|
-
|
|
47
|
-
expect(getByTestId('appearance-screen')).toBeTruthy();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should render custom header when provided', () => {
|
|
51
|
-
const CustomHeader = () => <div>Custom Header</div>;
|
|
52
|
-
const { getByText } = render(
|
|
53
|
-
<AppearanceScreen headerComponent={<CustomHeader />} />
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
expect(getByText('Custom Header')).toBeTruthy();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should hide theme section when showThemeSection is false', () => {
|
|
60
|
-
const { queryByTestId } = render(
|
|
61
|
-
<AppearanceScreen showThemeSection={false} />
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
expect(queryByTestId('theme-mode-section')).toBeFalsy();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should hide colors section when showColorsSection is false', () => {
|
|
68
|
-
const { queryByTestId } = render(
|
|
69
|
-
<AppearanceScreen showColorsSection={false} />
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
expect(queryByTestId('custom-colors-section')).toBeFalsy();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should hide preview section when showPreviewSection is false', () => {
|
|
76
|
-
const { queryByTestId } = render(
|
|
77
|
-
<AppearanceScreen showPreviewSection={false} />
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
expect(queryByTestId('appearance-preview')).toBeFalsy();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should apply custom container styles', () => {
|
|
84
|
-
const customStyle = { backgroundColor: 'red' };
|
|
85
|
-
const { getByTestId } = render(
|
|
86
|
-
<AppearanceScreen containerStyle={customStyle} />
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const container = getByTestId('appearance-screen');
|
|
90
|
-
expect(container.props.style).toEqual(
|
|
91
|
-
expect.objectContaining(customStyle)
|
|
92
|
-
);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should apply custom content container styles', () => {
|
|
96
|
-
const customStyle = { padding: 20 };
|
|
97
|
-
const { getByTestId } = render(
|
|
98
|
-
<AppearanceScreen contentContainerStyle={customStyle} />
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const container = getByTestId('appearance-screen');
|
|
102
|
-
const scrollView = container.findByType('ScrollView');
|
|
103
|
-
expect(scrollView.props.contentContainerStyle).toEqual(
|
|
104
|
-
expect.objectContaining(customStyle)
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should handle theme selection', async () => {
|
|
109
|
-
const mockHandleThemeSelect = jest.fn();
|
|
110
|
-
mockUseAppearanceActions.mockReturnValue({
|
|
111
|
-
localCustomColors: {},
|
|
112
|
-
handleThemeSelect: mockHandleThemeSelect,
|
|
113
|
-
handleColorChange: jest.fn(),
|
|
114
|
-
handleResetColors: jest.fn(),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const { getByTestId } = render(<AppearanceScreen />);
|
|
118
|
-
|
|
119
|
-
// Find and click theme option
|
|
120
|
-
const themeOption = getByTestId('theme-option-light');
|
|
121
|
-
await act(async () => {
|
|
122
|
-
fireEvent.press(themeOption);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
expect(mockHandleThemeSelect).toHaveBeenCalledWith('light');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('should handle color change', async () => {
|
|
129
|
-
const mockHandleColorChange = jest.fn();
|
|
130
|
-
mockUseAppearanceActions.mockReturnValue({
|
|
131
|
-
localCustomColors: {},
|
|
132
|
-
handleThemeSelect: jest.fn(),
|
|
133
|
-
handleColorChange: mockHandleColorChange,
|
|
134
|
-
handleResetColors: jest.fn(),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const { getByTestId } = render(<AppearanceScreen />);
|
|
138
|
-
|
|
139
|
-
// Find and click color option
|
|
140
|
-
const colorOption = getByTestId('color-option-primary');
|
|
141
|
-
await act(async () => {
|
|
142
|
-
fireEvent.press(colorOption);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(mockHandleColorChange).toHaveBeenCalledWith('primary', expect.any(String));
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should handle reset colors', async () => {
|
|
149
|
-
const mockHandleResetColors = jest.fn();
|
|
150
|
-
mockUseAppearanceActions.mockReturnValue({
|
|
151
|
-
localCustomColors: { primary: '#FF0000' },
|
|
152
|
-
handleThemeSelect: jest.fn(),
|
|
153
|
-
handleColorChange: jest.fn(),
|
|
154
|
-
handleResetColors: mockHandleResetColors,
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const { getByTestId } = render(<AppearanceScreen />);
|
|
158
|
-
|
|
159
|
-
// Find and click reset button
|
|
160
|
-
const resetButton = getByTestId('reset-colors-button');
|
|
161
|
-
await act(async () => {
|
|
162
|
-
fireEvent.press(resetButton);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
expect(mockHandleResetColors).toHaveBeenCalled();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should not re-render unnecessarily', () => {
|
|
169
|
-
const { rerender } = render(<AppearanceScreen />);
|
|
170
|
-
|
|
171
|
-
// Rerender with same props
|
|
172
|
-
rerender(<AppearanceScreen />);
|
|
173
|
-
|
|
174
|
-
// Hooks should not be called again for same props
|
|
175
|
-
expect(mockUseAppearance).toHaveBeenCalledTimes(1);
|
|
176
|
-
expect(mockUseAppearanceActions).toHaveBeenCalledTimes(1);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should handle performance optimizations', () => {
|
|
180
|
-
const startTime = performance.now();
|
|
181
|
-
|
|
182
|
-
const { rerender } = render(<AppearanceScreen />);
|
|
183
|
-
|
|
184
|
-
// Multiple rerenders
|
|
185
|
-
for (let i = 0; i < 10; i++) {
|
|
186
|
-
rerender(<AppearanceScreen />);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const endTime = performance.now();
|
|
190
|
-
const renderTime = endTime - startTime;
|
|
191
|
-
|
|
192
|
-
// Should render quickly even with multiple updates
|
|
193
|
-
expect(renderTime).toBeLessThan(100); // 100ms for 10 renders
|
|
194
|
-
});
|
|
195
|
-
});
|