@umituz/react-native-settings 4.17.14 → 4.17.16

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 (103) hide show
  1. package/package.json +16 -15
  2. package/src/domains/about/__tests__/integration.test.tsx +328 -0
  3. package/src/domains/about/__tests__/types.d.ts +5 -0
  4. package/src/domains/about/domain/entities/AppInfo.ts +74 -0
  5. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +93 -0
  6. package/src/domains/about/domain/repositories/IAboutRepository.ts +22 -0
  7. package/src/domains/about/index.ts +10 -0
  8. package/src/domains/about/infrastructure/repositories/AboutRepository.ts +68 -0
  9. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +153 -0
  10. package/src/domains/about/presentation/components/AboutContent.tsx +104 -0
  11. package/src/domains/about/presentation/components/AboutHeader.tsx +79 -0
  12. package/src/domains/about/presentation/components/AboutSection.tsx +134 -0
  13. package/src/domains/about/presentation/components/AboutSettingItem.tsx +208 -0
  14. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +178 -0
  15. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +293 -0
  16. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +201 -0
  17. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +71 -0
  18. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +229 -0
  19. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +240 -0
  20. package/src/domains/about/presentation/hooks/useAboutInfo.ts +262 -0
  21. package/src/domains/about/presentation/screens/AboutScreen.tsx +195 -0
  22. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +199 -0
  23. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +366 -0
  24. package/src/domains/about/types/global.d.ts +15 -0
  25. package/src/domains/about/utils/__tests__/index.test.ts +408 -0
  26. package/src/domains/about/utils/index.ts +160 -0
  27. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +195 -0
  28. package/src/domains/appearance/__tests__/hooks/index.test.tsx +232 -0
  29. package/src/domains/appearance/__tests__/integration/index.test.tsx +207 -0
  30. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +299 -0
  31. package/src/domains/appearance/__tests__/setup.ts +96 -0
  32. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +175 -0
  33. package/src/domains/appearance/data/colorPalettes.ts +94 -0
  34. package/src/domains/appearance/hooks/index.ts +6 -0
  35. package/src/domains/appearance/hooks/useAppearance.ts +61 -0
  36. package/src/domains/appearance/hooks/useAppearanceActions.ts +144 -0
  37. package/src/domains/appearance/index.ts +7 -0
  38. package/src/domains/appearance/infrastructure/services/appearanceService.ts +301 -0
  39. package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +79 -0
  40. package/src/domains/appearance/infrastructure/services/validation.ts +91 -0
  41. package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +120 -0
  42. package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +132 -0
  43. package/src/domains/appearance/presentation/components/AppearanceHeader.tsx +67 -0
  44. package/src/domains/appearance/presentation/components/AppearancePreview.tsx +141 -0
  45. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +139 -0
  46. package/src/domains/appearance/presentation/components/ColorPicker.tsx +113 -0
  47. package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +186 -0
  48. package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +110 -0
  49. package/src/domains/appearance/presentation/components/ThemeOption.tsx +138 -0
  50. package/src/domains/appearance/presentation/components/index.ts +6 -0
  51. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +226 -0
  52. package/src/domains/appearance/presentation/screens/index.ts +2 -0
  53. package/src/domains/appearance/types/index.ts +54 -0
  54. package/src/domains/faqs/domain/entities/FAQEntity.ts +16 -0
  55. package/src/domains/faqs/domain/services/FAQSearchService.ts +36 -0
  56. package/src/domains/faqs/domain/services/index.ts +1 -0
  57. package/src/domains/faqs/index.ts +7 -0
  58. package/src/domains/faqs/presentation/components/FAQCategory.tsx +71 -0
  59. package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +75 -0
  60. package/src/domains/faqs/presentation/components/FAQItem.tsx +103 -0
  61. package/src/domains/faqs/presentation/components/FAQSearchBar.tsx +70 -0
  62. package/src/domains/faqs/presentation/components/FAQSection.tsx +50 -0
  63. package/src/domains/faqs/presentation/components/index.ts +18 -0
  64. package/src/domains/faqs/presentation/hooks/index.ts +6 -0
  65. package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +51 -0
  66. package/src/domains/faqs/presentation/hooks/useFAQSearch.ts +33 -0
  67. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +129 -0
  68. package/src/domains/faqs/presentation/screens/index.ts +2 -0
  69. package/src/domains/feedback/domain/entities/FeedbackEntity.ts +92 -0
  70. package/src/domains/feedback/domain/repositories/IFeedbackRepository.ts +28 -0
  71. package/src/domains/feedback/index.ts +6 -0
  72. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +189 -0
  73. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +111 -0
  74. package/src/domains/feedback/presentation/components/SupportSection.tsx +160 -0
  75. package/src/domains/feedback/presentation/hooks/useDeleteFeedback.ts +25 -0
  76. package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +59 -0
  77. package/src/domains/feedback/presentation/hooks/useSubmitFeedback.ts +55 -0
  78. package/src/domains/feedback/presentation/hooks/useUserFeedback.ts +29 -0
  79. package/src/domains/legal/__tests__/ContentValidationService.test.ts +195 -0
  80. package/src/domains/legal/__tests__/StyleCacheService.test.ts +110 -0
  81. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +71 -0
  82. package/src/domains/legal/__tests__/setup.ts +82 -0
  83. package/src/domains/legal/domain/entities/LegalConfig.ts +26 -0
  84. package/src/domains/legal/domain/services/ContentValidationService.ts +89 -0
  85. package/src/domains/legal/domain/services/StyleCacheService.ts +97 -0
  86. package/src/domains/legal/domain/services/UrlHandlerService.ts +128 -0
  87. package/src/domains/legal/index.ts +8 -0
  88. package/src/domains/legal/presentation/components/LegalItem.tsx +177 -0
  89. package/src/domains/legal/presentation/components/LegalLinks.tsx +154 -0
  90. package/src/domains/legal/presentation/components/LegalSection.tsx +134 -0
  91. package/src/domains/legal/presentation/screens/LegalScreen.tsx +237 -0
  92. package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +214 -0
  93. package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +214 -0
  94. package/src/index.ts +19 -0
  95. package/src/presentation/components/DevSettingsSection.tsx +2 -2
  96. package/src/presentation/components/SettingItem.tsx +2 -2
  97. package/src/presentation/components/SettingsErrorBoundary.tsx +2 -2
  98. package/src/presentation/components/SettingsFooter.tsx +2 -2
  99. package/src/presentation/components/SettingsSection.tsx +2 -2
  100. package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
  101. package/src/presentation/screens/SettingsScreen.tsx +2 -2
  102. package/src/presentation/screens/components/SettingsContent.tsx +2 -2
  103. package/src/presentation/screens/components/SettingsHeader.tsx +2 -2
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Appearance Service Tests
3
+ */
4
+
5
+ import { AppearanceService, appearanceService } from '../../infrastructure/services/appearanceService';
6
+ import type { ThemeMode, CustomThemeColors } from '../../types';
7
+
8
+ // Mock dependencies
9
+ jest.mock('../../infrastructure/stores/appearanceStore');
10
+ jest.mock('../../infrastructure/storage/appearanceStorage');
11
+ jest.mock('@umituz/react-native-design-system', () => ({
12
+ useTheme: {
13
+ getState: jest.fn(() => ({
14
+ setThemeMode: jest.fn(),
15
+ })),
16
+ },
17
+ useDesignSystemTheme: {
18
+ getState: jest.fn(() => ({
19
+ setThemeMode: jest.fn(),
20
+ setCustomColors: jest.fn(),
21
+ })),
22
+ },
23
+ }));
24
+
25
+ const mockAppearanceStore = require('../../infrastructure/stores/appearanceStore').useAppearanceStore;
26
+ const mockAppearanceStorage = require('../../infrastructure/storage/appearanceStorage').AppearanceStorage;
27
+ const mockUseTheme = require('@umituz/react-native-design-system').useTheme;
28
+ const mockUseDesignSystemTheme = require('@umituz/react-native-design-system').useDesignSystemTheme;
29
+
30
+ describe('AppearanceService', () => {
31
+ let service: AppearanceService;
32
+ let mockGetState: jest.MockedFunction<typeof mockAppearanceStore.getState>;
33
+ let mockSetSettings: jest.MockedFunction<typeof mockAppearanceStore.getState>;
34
+ let mockSetInitialized: jest.MockedFunction<typeof mockAppearanceStore.getState>;
35
+ let mockUpdateThemeMode: jest.MockedFunction<typeof mockAppearanceStore.getState>;
36
+ let mockUpdateCustomColors: jest.MockedFunction<typeof mockAppearanceStore.getState>;
37
+ let mockResetState: jest.MockedFunction<typeof mockAppearanceStore.getState>;
38
+
39
+ beforeEach(() => {
40
+ jest.clearAllMocks();
41
+
42
+ service = appearanceService;
43
+
44
+ mockGetState = mockAppearanceStore.getState as jest.MockedFunction<typeof mockAppearanceStore.getState>;
45
+ mockSetSettings = mockAppearanceStore.getState as jest.MockedFunction<typeof mockAppearanceStore.getState>;
46
+ mockSetInitialized = mockAppearanceStore.getState as jest.MockedFunction<typeof mockAppearanceStore.getState>;
47
+ mockUpdateThemeMode = mockAppearanceStore.getState as jest.MockedFunction<typeof mockAppearanceStore.getState>;
48
+ mockUpdateCustomColors = mockAppearanceStore.getState as jest.MockedFunction<typeof mockAppearanceStore.getState>;
49
+ mockResetState = mockAppearanceStore.getState as jest.MockedFunction<typeof mockAppearanceStore.getState>;
50
+
51
+ // Setup mock implementations
52
+ mockGetState.mockReturnValue({
53
+ settings: { themeMode: 'dark' as ThemeMode },
54
+ isInitialized: false,
55
+ });
56
+
57
+ mockSetSettings.mockReturnValue({} as any);
58
+ mockSetInitialized.mockReturnValue({} as any);
59
+ mockUpdateThemeMode.mockReturnValue({} as any);
60
+ mockUpdateCustomColors.mockReturnValue({} as any);
61
+ mockResetState.mockReturnValue({} as any);
62
+
63
+ mockAppearanceStorage.getSettings.mockResolvedValue(null);
64
+ mockAppearanceStorage.setSettings.mockResolvedValue();
65
+ mockAppearanceStorage.clear.mockResolvedValue();
66
+
67
+ mockUseTheme.getState.mockReturnValue({
68
+ setThemeMode: jest.fn(),
69
+ });
70
+
71
+ mockUseDesignSystemTheme.getState.mockReturnValue({
72
+ setThemeMode: jest.fn(),
73
+ setCustomColors: jest.fn(),
74
+ });
75
+ });
76
+
77
+ afterEach(() => {
78
+ jest.restoreAllMocks();
79
+ });
80
+
81
+ describe('initialize', () => {
82
+ it('should initialize with saved settings', async () => {
83
+ const savedSettings = { themeMode: 'light' as ThemeMode };
84
+ mockAppearanceStorage.getSettings.mockResolvedValue(savedSettings);
85
+
86
+ await service.initialize();
87
+
88
+ expect(mockAppearanceStorage.getSettings).toHaveBeenCalled();
89
+ expect(mockSetSettings).toHaveBeenCalledWith(savedSettings);
90
+ expect(mockSetInitialized).toHaveBeenCalledWith(true);
91
+ expect(mockUseTheme.getState().setThemeMode).toHaveBeenCalledWith('light');
92
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).toHaveBeenCalledWith('light');
93
+ expect(mockUseDesignSystemTheme.getState().setCustomColors).toHaveBeenCalledWith(undefined);
94
+ });
95
+
96
+ it('should initialize with default settings when no saved settings', async () => {
97
+ mockAppearanceStorage.getSettings.mockResolvedValue(null);
98
+
99
+ await service.initialize();
100
+
101
+ expect(mockSetSettings).toHaveBeenCalledWith({ themeMode: 'dark' });
102
+ expect(mockSetInitialized).toHaveBeenCalledWith(true);
103
+ expect(mockUseTheme.getState().setThemeMode).toHaveBeenCalledWith('dark');
104
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).toHaveBeenCalledWith('dark');
105
+ });
106
+
107
+ it('should handle initialization errors gracefully', async () => {
108
+ const error = new Error('Init failed');
109
+ mockAppearanceStorage.getSettings.mockRejectedValue(error);
110
+
111
+ await service.initialize();
112
+
113
+ expect(mockSetSettings).toHaveBeenCalledWith({ themeMode: 'dark' });
114
+ expect(mockSetInitialized).toHaveBeenCalledWith(true);
115
+ });
116
+
117
+ it('should prevent multiple initializations', async () => {
118
+ const savedSettings = { themeMode: 'light' as ThemeMode };
119
+ mockAppearanceStorage.getSettings.mockResolvedValue(savedSettings);
120
+
121
+ const promise1 = service.initialize();
122
+ const promise2 = service.initialize();
123
+
124
+ await Promise.all([promise1, promise2]);
125
+
126
+ expect(mockAppearanceStorage.getSettings).toHaveBeenCalledTimes(1);
127
+ });
128
+ });
129
+
130
+ describe('getThemeMode', () => {
131
+ it('should return current theme mode', () => {
132
+ mockGetState.mockReturnValue({
133
+ settings: { themeMode: 'light' as ThemeMode },
134
+ isInitialized: true,
135
+ });
136
+
137
+ const result = service.getThemeMode();
138
+
139
+ expect(result).toBe('light');
140
+ expect(mockGetState).toHaveBeenCalled();
141
+ });
142
+ });
143
+
144
+ describe('setThemeMode', () => {
145
+ it('should set theme mode and sync with design system', async () => {
146
+ const newMode: ThemeMode = 'light';
147
+ mockGetState.mockReturnValue({
148
+ settings: { themeMode: 'dark' as ThemeMode },
149
+ isInitialized: true,
150
+ });
151
+
152
+ await service.setThemeMode(newMode);
153
+
154
+ expect(mockUpdateThemeMode).toHaveBeenCalledWith(newMode);
155
+ expect(mockAppearanceStorage.setSettings).toHaveBeenCalledWith({
156
+ themeMode: newMode,
157
+ });
158
+ expect(mockUseTheme.getState().setThemeMode).toHaveBeenCalledWith(newMode);
159
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).toHaveBeenCalledWith(newMode);
160
+ });
161
+
162
+ it('should handle setThemeMode errors', async () => {
163
+ const error = new Error('Set theme failed');
164
+ mockAppearanceStorage.setSettings.mockRejectedValue(error);
165
+
166
+ await expect(service.setThemeMode('light')).rejects.toThrow(error);
167
+ });
168
+ });
169
+
170
+ describe('toggleTheme', () => {
171
+ it('should toggle between light and dark', async () => {
172
+ mockGetState.mockReturnValue({
173
+ settings: { themeMode: 'light' as ThemeMode },
174
+ isInitialized: true,
175
+ });
176
+
177
+ await service.toggleTheme();
178
+
179
+ expect(mockUpdateThemeMode).toHaveBeenCalledWith('dark');
180
+ });
181
+
182
+ it('should toggle from dark to light', async () => {
183
+ mockGetState.mockReturnValue({
184
+ settings: { themeMode: 'dark' as ThemeMode },
185
+ isInitialized: true,
186
+ });
187
+
188
+ await service.toggleTheme();
189
+
190
+ expect(mockUpdateThemeMode).toHaveBeenCalledWith('light');
191
+ });
192
+ });
193
+
194
+ describe('getCustomColors', () => {
195
+ it('should return current custom colors', () => {
196
+ const customColors: CustomThemeColors = { primary: '#FF0000' };
197
+ mockGetState.mockReturnValue({
198
+ settings: { themeMode: 'dark', customColors },
199
+ isInitialized: true,
200
+ });
201
+
202
+ const result = service.getCustomColors();
203
+
204
+ expect(result).toEqual(customColors);
205
+ });
206
+
207
+ it('should return undefined when no custom colors', () => {
208
+ mockGetState.mockReturnValue({
209
+ settings: { themeMode: 'dark' },
210
+ isInitialized: true,
211
+ });
212
+
213
+ const result = service.getCustomColors();
214
+
215
+ expect(result).toBeUndefined();
216
+ });
217
+ });
218
+
219
+ describe('setCustomColors', () => {
220
+ it('should set custom colors and sync with design system', async () => {
221
+ const newColors: CustomThemeColors = { primary: '#FF0000' };
222
+ const currentColors = { secondary: '#00FF00' };
223
+ mockGetState.mockReturnValue({
224
+ settings: { themeMode: 'dark', customColors: currentColors },
225
+ isInitialized: true,
226
+ });
227
+
228
+ await service.setCustomColors(newColors);
229
+
230
+ expect(mockUpdateCustomColors).toHaveBeenCalledWith({
231
+ ...currentColors,
232
+ ...newColors,
233
+ });
234
+ expect(mockAppearanceStorage.setSettings).toHaveBeenCalledWith({
235
+ themeMode: 'dark',
236
+ customColors: { ...currentColors, ...newColors },
237
+ });
238
+ expect(mockUseDesignSystemTheme.getState().setCustomColors).toHaveBeenCalledWith({
239
+ ...currentColors,
240
+ ...newColors,
241
+ });
242
+ });
243
+
244
+ it('should handle setCustomColors errors', async () => {
245
+ const error = new Error('Set colors failed');
246
+ mockAppearanceStorage.setSettings.mockRejectedValue(error);
247
+
248
+ await expect(service.setCustomColors({ primary: '#FF0000' })).rejects.toThrow(error);
249
+ });
250
+ });
251
+
252
+ describe('resetCustomColors', () => {
253
+ it('should reset custom colors', async () => {
254
+ mockGetState.mockReturnValue({
255
+ settings: { themeMode: 'dark', customColors: { primary: '#FF0000' } },
256
+ isInitialized: true,
257
+ });
258
+
259
+ await service.resetCustomColors();
260
+
261
+ expect(mockUpdateCustomColors).toHaveBeenCalledWith(undefined);
262
+ expect(mockAppearanceStorage.setSettings).toHaveBeenCalledWith({
263
+ themeMode: 'dark',
264
+ customColors: undefined,
265
+ });
266
+ expect(mockUseDesignSystemTheme.getState().setCustomColors).toHaveBeenCalledWith(undefined);
267
+ });
268
+ });
269
+
270
+ describe('reset', () => {
271
+ it('should reset all settings', async () => {
272
+ mockGetState.mockReturnValue({
273
+ settings: { themeMode: 'light', customColors: { primary: '#FF0000' } },
274
+ isInitialized: true,
275
+ });
276
+
277
+ await service.reset();
278
+
279
+ expect(mockAppearanceStorage.clear).toHaveBeenCalled();
280
+ expect(mockResetState).toHaveBeenCalled();
281
+ expect(mockUseTheme.getState().setThemeMode).toHaveBeenCalledWith('dark');
282
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).toHaveBeenCalledWith('dark');
283
+ expect(mockUseDesignSystemTheme.getState().setCustomColors).toHaveBeenCalledWith(undefined);
284
+ });
285
+ });
286
+
287
+ describe('isInitialized', () => {
288
+ it('should return initialization status', () => {
289
+ mockGetState.mockReturnValue({
290
+ settings: { themeMode: 'dark' },
291
+ isInitialized: true,
292
+ });
293
+
294
+ const result = service.isInitialized();
295
+
296
+ expect(result).toBe(true);
297
+ });
298
+ });
299
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Jest Setup File
3
+ *
4
+ * Global test setup for React Native Appearance package
5
+ */
6
+
7
+ import 'react-native-gesture-handler/jestSetup';
8
+
9
+ // Mock console methods for testing
10
+ if (__DEV__) {
11
+ global.console = {
12
+ ...console,
13
+ // Suppress specific console warnings in tests
14
+ warn: jest.fn(),
15
+ error: jest.fn(),
16
+ };
17
+ }
18
+
19
+ // Mock performance API for testing
20
+ if (typeof performance === 'undefined') {
21
+ global.performance = {
22
+ now: jest.fn(() => Date.now()),
23
+ mark: jest.fn(),
24
+ measure: jest.fn(),
25
+ getEntriesByName: jest.fn(() => []),
26
+ getEntriesByType: jest.fn(() => []),
27
+ clearMarks: jest.fn(),
28
+ clearMeasures: jest.fn(),
29
+ clearResourceTimings: jest.fn(),
30
+ } as any;
31
+ }
32
+
33
+ // Mock React Native modules
34
+ jest.mock('react-native', () => {
35
+ const RN = jest.requireActual('react-native');
36
+ return {
37
+ ...RN,
38
+ Platform: {
39
+ OS: 'ios',
40
+ select: jest.fn((obj) => obj.ios),
41
+ },
42
+ };
43
+ });
44
+
45
+ // Mock design system theme
46
+ jest.mock('@umituz/react-native-design-system', () => ({
47
+ useTheme: jest.fn(() => ({
48
+ setThemeMode: jest.fn(),
49
+ })),
50
+ useDesignSystemTheme: jest.fn(() => ({
51
+ setThemeMode: jest.fn(),
52
+ setCustomColors: jest.fn(),
53
+ })),
54
+ }));
55
+
56
+ // Mock storage
57
+ jest.mock('@umituz/react-native-storage', () => ({
58
+ storageRepository: {
59
+ getItem: jest.fn(() => ({
60
+ success: true,
61
+ data: null,
62
+ })),
63
+ setItem: jest.fn(() => ({
64
+ success: true,
65
+ })),
66
+ removeItem: jest.fn(() => ({
67
+ success: true,
68
+ })),
69
+ },
70
+ unwrap: jest.fn((result, defaultValue) =>
71
+ result.success ? result.data : defaultValue
72
+ ),
73
+ }));
74
+
75
+ // Mock alert
76
+ jest.mock('@umituz/react-native-alert', () => ({
77
+ useAlert: jest.fn(() => ({
78
+ show: jest.fn(),
79
+ showSuccess: jest.fn(),
80
+ showError: jest.fn(),
81
+ showWarning: jest.fn(),
82
+ })),
83
+ }));
84
+
85
+ // Mock localization
86
+ jest.mock('@umituz/react-native-localization', () => ({
87
+ useLocalization: jest.fn(() => ({
88
+ t: jest.fn((key) => key),
89
+ })),
90
+ }));
91
+
92
+ // Global test utilities
93
+ (globalThis as any).__TEST__ = true;
94
+
95
+ // Mock timers
96
+ jest.useFakeTimers();
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Appearance Store Tests
3
+ */
4
+
5
+ import { renderHook, act } from '@testing-library/react-native';
6
+ import { useAppearanceStore } from '../../infrastructure/stores/appearanceStore';
7
+ import type { AppearanceSettings, ThemeMode, CustomThemeColors } from '../../types';
8
+
9
+ // Mock design system theme
10
+ jest.mock('@umituz/react-native-design-system', () => ({
11
+ useTheme: {
12
+ getState: jest.fn(() => ({
13
+ setThemeMode: jest.fn(),
14
+ })),
15
+ },
16
+ useDesignSystemTheme: {
17
+ getState: jest.fn(() => ({
18
+ setThemeMode: jest.fn(),
19
+ setCustomColors: jest.fn(),
20
+ })),
21
+ },
22
+ }));
23
+
24
+ const mockUseTheme = require('@umituz/react-native-design-system').useTheme;
25
+ const mockUseDesignSystemTheme = require('@umituz/react-native-design-system').useDesignSystemTheme;
26
+
27
+ describe('useAppearanceStore', () => {
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ });
31
+
32
+ afterEach(() => {
33
+ jest.restoreAllMocks();
34
+ });
35
+
36
+ it('should initialize with default settings', () => {
37
+ const { result } = renderHook(() => useAppearanceStore());
38
+
39
+ expect(result.current).toEqual({
40
+ settings: {
41
+ themeMode: 'dark',
42
+ },
43
+ isInitialized: false,
44
+ });
45
+
46
+ expect(typeof result.current.setSettings).toBe('function');
47
+ expect(typeof result.current.setInitialized).toBe('function');
48
+ expect(typeof result.current.updateThemeMode).toBe('function');
49
+ expect(typeof result.current.updateCustomColors).toBe('function');
50
+ expect(typeof result.current.resetState).toBe('function');
51
+ });
52
+
53
+ it('should update settings correctly', () => {
54
+ const { result } = renderHook(() => useAppearanceStore());
55
+
56
+ const newSettings: AppearanceSettings = {
57
+ themeMode: 'light',
58
+ customColors: { primary: '#FF0000' },
59
+ };
60
+
61
+ act(() => {
62
+ result.current.setSettings(newSettings);
63
+ });
64
+
65
+ expect(result.current.settings).toEqual(newSettings);
66
+ expect(mockUseTheme.getState().setThemeMode).not.toHaveBeenCalled();
67
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).not.toHaveBeenCalled();
68
+ });
69
+
70
+ it('should update theme mode correctly', () => {
71
+ const { result } = renderHook(() => useAppearanceStore());
72
+
73
+ act(() => {
74
+ result.current.updateThemeMode('light');
75
+ });
76
+
77
+ expect(result.current.settings.themeMode).toBe('light');
78
+ expect(mockUseTheme.getState().setThemeMode).not.toHaveBeenCalled();
79
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).not.toHaveBeenCalled();
80
+ });
81
+
82
+ it('should not update theme mode if same value', () => {
83
+ const { result } = renderHook(() => useAppearanceStore());
84
+
85
+ act(() => {
86
+ result.current.updateThemeMode('dark'); // Default is dark
87
+ });
88
+
89
+ expect(result.current.settings.themeMode).toBe('dark');
90
+ });
91
+
92
+ it('should update custom colors correctly', () => {
93
+ const { result } = renderHook(() => useAppearanceStore());
94
+
95
+ const newColors: CustomThemeColors = { primary: '#FF0000' };
96
+
97
+ act(() => {
98
+ result.current.updateCustomColors(newColors);
99
+ });
100
+
101
+ expect(result.current.settings.customColors).toEqual(newColors);
102
+ });
103
+
104
+ it('should not update custom colors if same value', () => {
105
+ const { result } = renderHook(() => useAppearanceStore());
106
+
107
+ const newColors: CustomThemeColors = { primary: '#FF0000' };
108
+
109
+ act(() => {
110
+ result.current.updateCustomColors(newColors);
111
+ result.current.updateCustomColors(newColors); // Second call with same value
112
+ });
113
+
114
+ // Should only be called once due to optimization
115
+ expect(mockUseDesignSystemTheme.getState().setCustomColors).toHaveBeenCalledTimes(1);
116
+ });
117
+
118
+ it('should reset state correctly', () => {
119
+ const { result } = renderHook(() => useAppearanceStore());
120
+
121
+ const customColors: CustomThemeColors = { primary: '#FF0000' };
122
+
123
+ act(() => {
124
+ result.current.updateCustomColors(customColors);
125
+ result.current.setInitialized(true);
126
+ result.current.resetState();
127
+ });
128
+
129
+ expect(result.current.settings).toEqual({
130
+ themeMode: 'dark',
131
+ });
132
+ expect(result.current.isInitialized).toBe(false);
133
+ });
134
+
135
+ it('should not reset if already at default', () => {
136
+ const { result } = renderHook(() => useAppearanceStore());
137
+
138
+ act(() => {
139
+ result.current.resetState();
140
+ result.current.resetState(); // Second call
141
+ });
142
+
143
+ // Should only be called once due to optimization
144
+ expect(mockUseTheme.getState().setThemeMode).toHaveBeenCalledTimes(1);
145
+ expect(mockUseDesignSystemTheme.getState().setThemeMode).toHaveBeenCalledTimes(1);
146
+ });
147
+
148
+ it('should handle setInitialized correctly', () => {
149
+ const { result } = renderHook(() => useAppearanceStore());
150
+
151
+ act(() => {
152
+ result.current.setInitialized(true);
153
+ });
154
+
155
+ expect(result.current.isInitialized).toBe(true);
156
+
157
+ act(() => {
158
+ result.current.setInitialized(true); // Second call with same value
159
+ });
160
+
161
+ // Should not be called again due to optimization
162
+ expect(result.current.isInitialized).toBe(true);
163
+ });
164
+
165
+ it('should handle setInitialized changes correctly', () => {
166
+ const { result } = renderHook(() => useAppearanceStore());
167
+
168
+ act(() => {
169
+ result.current.setInitialized(false);
170
+ result.current.setInitialized(true);
171
+ });
172
+
173
+ expect(result.current.isInitialized).toBe(true);
174
+ });
175
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Dynamic Color Palettes
3
+ *
4
+ * Generic color palettes that can be customized by host applications
5
+ * These serve as sensible defaults for appearance customization
6
+ */
7
+
8
+ export interface ColorPalette {
9
+ name: string;
10
+ colors: string[];
11
+ }
12
+
13
+ /**
14
+ * Generic primary color palette
15
+ * Suitable for most applications
16
+ */
17
+ export const DEFAULT_PRIMARY_COLORS: ColorPalette = {
18
+ name: "primary",
19
+ colors: [
20
+ "#007AFF", // Blue
21
+ "#5856D6", // Purple
22
+ "#FF3B30", // Red
23
+ "#FF9500", // Orange
24
+ "#FFCC00", // Yellow
25
+ "#4CD964", // Green
26
+ "#5AC8FA", // Light Blue
27
+ "#FF2D92", // Pink
28
+ ],
29
+ };
30
+
31
+ /**
32
+ * Generic secondary color palette
33
+ * Neutral colors for secondary elements
34
+ */
35
+ export const DEFAULT_SECONDARY_COLORS: ColorPalette = {
36
+ name: "secondary",
37
+ colors: [
38
+ "#E5E5EA", // Light Gray
39
+ "#C7C7CC", // Medium Gray
40
+ "#8E8E93", // Dark Gray
41
+ "#636366", // Very Dark Gray
42
+ "#48484A", // Black Gray
43
+ "#3A3A3C", // Near Black
44
+ "#2C2C2E", // Almost Black
45
+ "#1C1C1E", // Black
46
+ ],
47
+ };
48
+
49
+ /**
50
+ * Generic accent color palette
51
+ * Vibrant colors for accent elements
52
+ */
53
+ export const DEFAULT_ACCENT_COLORS: ColorPalette = {
54
+ name: "accent",
55
+ colors: [
56
+ "#FF6B6B", // Light Red
57
+ "#4ECDC4", // Teal
58
+ "#45B7D1", // Sky Blue
59
+ "#96CEB4", // Sage Green
60
+ "#FFEAA7", // Light Yellow
61
+ "#DDA0DD", // Plum
62
+ "#98D8C8", // Mint
63
+ "#F7DC6F", // Soft Yellow
64
+ ],
65
+ };
66
+
67
+ /**
68
+ * Generate custom color palette
69
+ * @param baseColor - Base color to generate variations from
70
+ * @param count - Number of variations to generate
71
+ * @returns Color palette with variations
72
+ */
73
+ export const generateColorPalette = (
74
+ baseColor: string,
75
+ count: number = 8
76
+ ): ColorPalette => {
77
+ const colors: string[] = [];
78
+
79
+ for (let i = 0; i < count; i++) {
80
+ // Simple color variation logic - in real implementation,
81
+ // this would use HSL/HSV color space manipulation
82
+ colors.push(baseColor);
83
+ }
84
+
85
+ return {
86
+ name: "custom",
87
+ colors,
88
+ };
89
+ };
90
+
91
+ // Legacy exports for backward compatibility
92
+ export const PRIMARY_COLORS = DEFAULT_PRIMARY_COLORS.colors;
93
+ export const SECONDARY_COLORS = DEFAULT_SECONDARY_COLORS.colors;
94
+ export const ACCENT_COLORS = DEFAULT_ACCENT_COLORS.colors;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Appearance Hooks
3
+ */
4
+
5
+ export { useAppearance } from "./useAppearance";
6
+ export { useAppearanceActions, type AppearanceActionsConfig } from "./useAppearanceActions";