@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,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
- });