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