@umituz/react-native-settings 4.20.61 → 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 (78) hide show
  1. package/package.json +8 -60
  2. package/src/domains/gamification/README.md +343 -0
  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 +91 -0
  13. package/src/domains/gamification/components/GamificationSettingsItem.tsx +33 -0
  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 +70 -0
  20. package/src/domains/gamification/examples/localization.example.json +71 -0
  21. package/src/domains/gamification/hooks/useGamification.ts +91 -0
  22. package/src/domains/gamification/index.ts +65 -0
  23. package/src/domains/gamification/store/gamificationStore.ts +162 -0
  24. package/src/domains/gamification/types/index.ts +103 -0
  25. package/src/domains/gamification/types/settings.ts +28 -0
  26. package/src/domains/gamification/utils/calculations.ts +85 -0
  27. package/src/index.ts +18 -8
  28. package/src/presentation/navigation/SettingsStackNavigator.tsx +12 -0
  29. package/src/presentation/navigation/types.ts +2 -0
  30. package/src/presentation/navigation/utils/navigationScreenOptions.ts +7 -0
  31. package/src/presentation/screens/types/UserFeatureConfig.ts +2 -0
  32. package/src/presentation/utils/configCreators.ts +147 -0
  33. package/src/presentation/utils/index.ts +5 -0
  34. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -51
  35. package/.github/ISSUE_TEMPLATE/documentation.md +0 -52
  36. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -63
  37. package/.github/PULL_REQUEST_TEMPLATE.md +0 -84
  38. package/AI_AGENT_GUIDELINES.md +0 -367
  39. package/ARCHITECTURE.md +0 -246
  40. package/CHANGELOG.md +0 -67
  41. package/CODE_OF_CONDUCT.md +0 -75
  42. package/CONTRIBUTING.md +0 -107
  43. package/DOCUMENTATION_MIGRATION.md +0 -319
  44. package/DOCUMENTATION_TEMPLATE.md +0 -155
  45. package/SECURITY.md +0 -98
  46. package/SETTINGS_SCREEN_GUIDE.md +0 -185
  47. package/TESTING.md +0 -358
  48. package/src/__tests__/integration.test.tsx +0 -371
  49. package/src/__tests__/performance.test.tsx +0 -369
  50. package/src/__tests__/setup.test.tsx +0 -20
  51. package/src/__tests__/setup.ts +0 -154
  52. package/src/domains/about/__tests__/integration.test.tsx +0 -328
  53. package/src/domains/about/__tests__/types.d.ts +0 -5
  54. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +0 -93
  55. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +0 -153
  56. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +0 -178
  57. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +0 -293
  58. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +0 -201
  59. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +0 -71
  60. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +0 -229
  61. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +0 -240
  62. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +0 -199
  63. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +0 -366
  64. package/src/domains/about/utils/__tests__/index.test.ts +0 -408
  65. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +0 -195
  66. package/src/domains/appearance/__tests__/hooks/index.test.tsx +0 -232
  67. package/src/domains/appearance/__tests__/integration/index.test.tsx +0 -207
  68. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +0 -299
  69. package/src/domains/appearance/__tests__/setup.ts +0 -88
  70. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +0 -175
  71. package/src/domains/cloud-sync/presentation/components/__tests__/CloudSyncSetting.test.tsx +0 -78
  72. package/src/domains/legal/__tests__/ContentValidationService.test.ts +0 -195
  73. package/src/domains/legal/__tests__/StyleCacheService.test.ts +0 -110
  74. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +0 -71
  75. package/src/domains/legal/__tests__/setup.ts +0 -82
  76. package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +0 -186
  77. package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +0 -322
  78. 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
- });