@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,328 +0,0 @@
1
- /**
2
- * Integration tests for the entire About package
3
- */
4
- import '../types/global.d.ts';
5
- import './types.d.ts';
6
- import React from 'react';
7
- import { View, Text } from 'react-native';
8
- import { render, waitFor, fireEvent } from '@testing-library/react';
9
- import { AboutScreen } from '../presentation/screens/AboutScreen';
10
- import { AboutConfig } from '../domain/entities/AppInfo';
11
-
12
- // Mock console methods
13
- const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();
14
- const mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
15
-
16
- describe('About Package Integration', () => {
17
- const fullConfig: AboutConfig = {
18
- appInfo: {
19
- name: 'Integration Test App',
20
- version: '2.0.0',
21
- description: 'This is an integration test app',
22
- developer: 'Test Developer',
23
- contactEmail: 'integration@test.com',
24
- websiteUrl: 'https://integration.example.com',
25
- websiteDisplay: 'integration.example.com',
26
- moreAppsUrl: 'https://apps.integration.example.com',
27
- },
28
- theme: {
29
- primary: '#FF0000',
30
- secondary: '#00FF00',
31
- background: '#0000FF',
32
- text: '#FFFFFF',
33
- border: '#CCCCCC',
34
- },
35
- style: {
36
- containerStyle: { backgroundColor: '#f0f0f0' },
37
- itemStyle: { padding: 20 },
38
- textStyle: { fontSize: 18 },
39
- iconStyle: { color: '#333333' },
40
- },
41
- actions: {
42
- onWebsitePress: jest.fn(),
43
- onEmailPress: jest.fn(),
44
- onMoreAppsPress: jest.fn(),
45
- },
46
- };
47
-
48
- beforeEach(() => {
49
- jest.clearAllMocks();
50
- });
51
-
52
- afterEach(() => {
53
- mockConsoleLog.mockClear();
54
- mockConsoleError.mockClear();
55
- });
56
-
57
- describe('Complete Flow', () => {
58
- it('should render complete about screen with all features', async () => {
59
- const { getByText, queryByText } = render(
60
- <AboutScreen config={fullConfig} />
61
- );
62
-
63
- // Should show loading initially
64
- expect(getByText((content) => content.includes('Loading'))).toBeTruthy();
65
-
66
- // Wait for content to load
67
- await waitFor(() => {
68
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
69
- });
70
-
71
- // Check header content
72
- expect(getByText('Integration Test App')).toBeTruthy();
73
- expect(getByText('Version 2.0.0')).toBeTruthy();
74
- expect(getByText('This is an integration test app')).toBeTruthy();
75
-
76
- // Check content items
77
- expect(getByText('Developer')).toBeTruthy();
78
- expect(getByText('Test Developer')).toBeTruthy();
79
- expect(getByText('Contact')).toBeTruthy();
80
- expect(getByText('integration@test.com')).toBeTruthy();
81
- expect(getByText('Website')).toBeTruthy();
82
- expect(getByText('integration.example.com')).toBeTruthy();
83
- expect(getByText('More Apps')).toBeTruthy();
84
- });
85
-
86
- it('should handle all user interactions', async () => {
87
- const { getByTestId } = render(
88
- <AboutScreen config={fullConfig} />
89
- );
90
-
91
- // Wait for content to load
92
- await waitFor(() => {
93
- expect(getByTestId('email-item')).toBeTruthy();
94
- });
95
-
96
- // Test all interactions
97
- fireEvent.click(getByTestId('email-item'));
98
- fireEvent.click(getByTestId('website-item'));
99
- fireEvent.click(getByTestId('more-apps-item'));
100
-
101
- // Verify all actions were called
102
- expect(fullConfig.actions!.onEmailPress).toHaveBeenCalledTimes(1);
103
- expect(fullConfig.actions!.onWebsitePress).toHaveBeenCalledTimes(1);
104
- expect(fullConfig.actions!.onMoreAppsPress).toHaveBeenCalledTimes(1);
105
- });
106
- });
107
-
108
- describe('Minimal Configuration', () => {
109
- it('should work with minimal config', async () => {
110
- const minimalConfig: AboutConfig = {
111
- appInfo: {
112
- name: 'Minimal App',
113
- version: '1.0.0',
114
- },
115
- };
116
-
117
- const { getByText, queryByText } = render(
118
- <AboutScreen config={minimalConfig} />
119
- );
120
-
121
- await waitFor(() => {
122
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
123
- });
124
-
125
- // Should show basic info
126
- expect(getByText('Minimal App')).toBeTruthy();
127
- expect(getByText('Version 1.0.0')).toBeTruthy();
128
-
129
- // Should not show optional items
130
- expect(queryByText('Developer')).toBeFalsy();
131
- expect(queryByText('Contact')).toBeFalsy();
132
- expect(queryByText('Website')).toBeFalsy();
133
- });
134
- });
135
-
136
- describe('Custom Components', () => {
137
- it('should render custom header and footer', async () => {
138
- const CustomHeader = () => <View testID="custom-header"><Text>Custom Header</Text></View>;
139
- const CustomFooter = () => <View testID="custom-footer"><Text>Custom Footer</Text></View>;
140
-
141
- const { getByTestId, queryByText } = render(
142
- <AboutScreen
143
- config={fullConfig}
144
- headerComponent={<CustomHeader />}
145
- footerComponent={<CustomFooter />}
146
- showHeader={false}
147
- />
148
- );
149
-
150
- await waitFor(() => {
151
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
152
- });
153
-
154
- // Should show custom components
155
- expect(getByTestId('custom-header')).toBeTruthy();
156
- expect(getByTestId('custom-footer')).toBeTruthy();
157
-
158
- // Should not show default header
159
- expect(queryByText('Integration Test App')).toBeFalsy();
160
- expect(queryByText('Version 2.0.0')).toBeFalsy();
161
- });
162
- });
163
-
164
- describe('Error Handling', () => {
165
- it('should handle invalid config gracefully', async () => {
166
- const { queryByText, getByText } = render(
167
- <AboutScreen config={null as unknown} />
168
- );
169
-
170
- await waitFor(() => {
171
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
172
- });
173
-
174
- expect(getByText('No app information available')).toBeTruthy();
175
- });
176
-
177
- it('should handle missing actions gracefully', async () => {
178
- const configWithoutActions: AboutConfig = {
179
- appInfo: fullConfig.appInfo,
180
- };
181
-
182
- const { getByTestId } = render(
183
- <AboutScreen config={configWithoutActions} />
184
- );
185
-
186
- await waitFor(() => {
187
- expect(getByTestId('email-item')).toBeTruthy();
188
- });
189
-
190
- // Should not crash when pressing items without actions
191
- expect(() => {
192
- fireEvent.click(getByTestId('email-item'));
193
- fireEvent.click(getByTestId('website-item'));
194
- }).not.toThrow();
195
- });
196
- });
197
-
198
- describe('Performance', () => {
199
- it('should handle rapid re-renders', async () => {
200
- const { rerender, getByText, queryByText } = render(
201
- <AboutScreen config={fullConfig} />
202
- );
203
-
204
- await waitFor(() => {
205
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
206
- });
207
-
208
- // Rapid re-renders with different configs
209
- for (let i = 0; i < 5; i++) {
210
- const newConfig = {
211
- ...fullConfig,
212
- appInfo: {
213
- ...fullConfig.appInfo,
214
- name: `App ${i}`,
215
- },
216
- };
217
-
218
- rerender(<AboutScreen config={newConfig} />);
219
-
220
- await waitFor(() => {
221
- expect(getByText(`App ${i}`)).toBeTruthy();
222
- });
223
- }
224
-
225
- // Should not crash and should show final state
226
- expect(getByText('App 4')).toBeTruthy();
227
- });
228
-
229
- it('should handle prop changes efficiently', async () => {
230
- const { rerender, getByText, queryByText } = render(
231
- <AboutScreen config={fullConfig} />
232
- );
233
-
234
- await waitFor(() => {
235
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
236
- });
237
-
238
- // Change showHeader prop
239
- rerender(<AboutScreen config={fullConfig} showHeader={false} />);
240
-
241
- await waitFor(() => {
242
- expect(queryByText('Integration Test App')).toBeFalsy();
243
- });
244
-
245
- // Change back
246
- rerender(<AboutScreen config={fullConfig} showHeader={true} />);
247
-
248
- await waitFor(() => {
249
- expect(getByText('Integration Test App')).toBeTruthy();
250
- });
251
- });
252
- });
253
-
254
- describe('Memory Management', () => {
255
- it('should cleanup properly on unmount', async () => {
256
- const { unmount, queryByText } = render(
257
- <AboutScreen config={fullConfig} />
258
- );
259
-
260
- await waitFor(() => {
261
- expect(queryByText((content) => content.includes('Loading'))).toBeFalsy();
262
- });
263
-
264
- // Should not throw when unmounting
265
- expect(() => {
266
- unmount();
267
- }).not.toThrow();
268
- });
269
-
270
- it('should handle unmount during loading', () => {
271
- const { unmount } = render(
272
- <AboutScreen config={fullConfig} />
273
- );
274
-
275
- // Should not throw when unmounting during loading
276
- expect(() => {
277
- unmount();
278
- }).not.toThrow();
279
- });
280
- });
281
-
282
- describe('Accessibility', () => {
283
- it('should have proper test IDs', async () => {
284
- const { getByTestId } = render(
285
- <AboutScreen config={fullConfig} testID="about-screen" />
286
- );
287
-
288
- await waitFor(() => {
289
- expect(getByTestId('about-screen')).toBeTruthy();
290
- });
291
-
292
- // Check for item test IDs
293
- expect(getByTestId('developer-item')).toBeTruthy();
294
- expect(getByTestId('email-item')).toBeTruthy();
295
- expect(getByTestId('website-item')).toBeTruthy();
296
- expect(getByTestId('more-apps-item')).toBeTruthy();
297
- });
298
- });
299
-
300
- describe('Console Logging', () => {
301
- it('should log in development mode', async () => {
302
- const originalDev = global.__DEV__;
303
- global.__DEV__ = true;
304
-
305
- render(<AboutScreen config={fullConfig} />);
306
-
307
- await waitFor(() => {
308
- expect(mockConsoleLog).toHaveBeenCalled();
309
- });
310
-
311
- global.__DEV__ = originalDev;
312
- });
313
-
314
- it('should not log in production mode', async () => {
315
- const originalDev = global.__DEV__;
316
- global.__DEV__ = false;
317
-
318
- render(<AboutScreen config={fullConfig} />);
319
-
320
- await waitFor(() => {
321
- // Wait a bit to ensure no logging
322
- expect(mockConsoleLog).not.toHaveBeenCalled();
323
- });
324
-
325
- global.__DEV__ = originalDev;
326
- });
327
- });
328
- });
@@ -1,5 +0,0 @@
1
- declare global {
2
- const __DEV__: boolean;
3
- }
4
-
5
- export {};
@@ -1,93 +0,0 @@
1
- /**
2
- * Tests for AppInfo entity
3
- */
4
- import { AppInfo } from '../AppInfo';
5
-
6
- describe('AppInfo Entity', () => {
7
- const validAppInfo: AppInfo = {
8
- name: 'Test App',
9
- version: '1.0.0',
10
- description: 'Test Description',
11
- developer: 'Test Developer',
12
- contactEmail: 'test@example.com',
13
- websiteUrl: 'https://example.com',
14
- websiteDisplay: 'example.com',
15
- moreAppsUrl: 'https://apps.example.com',
16
- };
17
-
18
- describe('Validation', () => {
19
- it('should accept valid AppInfo', () => {
20
- expect(() => {
21
- const appInfo: AppInfo = validAppInfo;
22
- expect(appInfo.name).toBe('Test App');
23
- expect(appInfo.version).toBe('1.0.0');
24
- }).not.toThrow();
25
- });
26
-
27
- it('should accept minimal AppInfo', () => {
28
- const minimalAppInfo: AppInfo = {
29
- name: 'Minimal App',
30
- version: '1.0.0',
31
- };
32
-
33
- expect(minimalAppInfo.name).toBe('Minimal App');
34
- expect(minimalAppInfo.version).toBe('1.0.0');
35
- expect(minimalAppInfo.description).toBeUndefined();
36
- expect(minimalAppInfo.developer).toBeUndefined();
37
- });
38
-
39
- it('should accept AppInfo with optional fields', () => {
40
- const appInfoWithOptionals: AppInfo = {
41
- name: 'App',
42
- version: '1.0.0',
43
- description: 'Description',
44
- developer: 'Developer',
45
- };
46
-
47
- expect(appInfoWithOptionals.description).toBe('Description');
48
- expect(appInfoWithOptionals.developer).toBe('Developer');
49
- expect(appInfoWithOptionals.contactEmail).toBeUndefined();
50
- });
51
- });
52
-
53
- describe('Type Safety', () => {
54
- it('should have correct types', () => {
55
- const appInfo: AppInfo = validAppInfo;
56
-
57
- expect(typeof appInfo.name).toBe('string');
58
- expect(typeof appInfo.version).toBe('string');
59
- expect(typeof appInfo.description).toBe('string');
60
- expect(typeof appInfo.developer).toBe('string');
61
- expect(typeof appInfo.contactEmail).toBe('string');
62
- expect(typeof appInfo.websiteUrl).toBe('string');
63
- expect(typeof appInfo.websiteDisplay).toBe('string');
64
- expect(typeof appInfo.moreAppsUrl).toBe('string');
65
- });
66
-
67
- it('should allow undefined for optional fields', () => {
68
- const appInfo: AppInfo = {
69
- name: 'Test',
70
- version: '1.0.0',
71
- description: undefined,
72
- developer: undefined,
73
- contactEmail: undefined,
74
- websiteUrl: undefined,
75
- websiteDisplay: undefined,
76
- moreAppsUrl: undefined,
77
- };
78
-
79
- expect(appInfo.description).toBeUndefined();
80
- expect(appInfo.developer).toBeUndefined();
81
- });
82
- });
83
-
84
- describe('Immutability', () => {
85
- it('should be assignable but not inherently immutable', () => {
86
- const appInfo: AppInfo = { ...validAppInfo };
87
-
88
- // TypeScript allows mutation but we can test the behavior
89
- appInfo.name = 'Modified App';
90
- expect(appInfo.name).toBe('Modified App');
91
- });
92
- });
93
- });
@@ -1,153 +0,0 @@
1
- /**
2
- * Tests for AboutRepository
3
- */
4
- import { AboutRepository } from '../AboutRepository';
5
- import { AppInfo } from '../../../domain/entities/AppInfo';
6
-
7
- describe('AboutRepository', () => {
8
- let repository: AboutRepository;
9
- const mockAppInfo: AppInfo = {
10
- name: 'Test App',
11
- version: '1.0.0',
12
- description: 'Test Description',
13
- developer: 'Test Developer',
14
- };
15
-
16
- beforeEach(() => {
17
- repository = new AboutRepository();
18
- });
19
-
20
- afterEach(() => {
21
- repository.destroy();
22
- });
23
-
24
- describe('saveAppInfo', () => {
25
- it('should save app info successfully', async () => {
26
- await expect(repository.saveAppInfo(mockAppInfo)).resolves.not.toThrow();
27
-
28
- const savedInfo = await repository.getAppInfo();
29
- expect(savedInfo).toEqual(mockAppInfo);
30
- });
31
-
32
- it('should throw error for invalid input', async () => {
33
- await expect(repository.saveAppInfo(null as unknown)).rejects.toThrow('Invalid app info provided');
34
- await expect(repository.saveAppInfo(undefined as unknown)).rejects.toThrow('Invalid app info provided');
35
- await expect(repository.saveAppInfo('invalid' as unknown)).rejects.toThrow('Invalid app info provided');
36
- });
37
-
38
- it('should store a copy of the data', async () => {
39
- const appInfoCopy = { ...mockAppInfo };
40
- await repository.saveAppInfo(mockAppInfo);
41
-
42
- // Modify original object
43
- appInfoCopy.name = 'Modified';
44
-
45
- const savedInfo = await repository.getAppInfo();
46
- expect(savedInfo.name).toBe('Test App'); // Should not be affected
47
- });
48
- });
49
-
50
- describe('getAppInfo', () => {
51
- it('should return saved app info', async () => {
52
- await repository.saveAppInfo(mockAppInfo);
53
-
54
- const retrievedInfo = await repository.getAppInfo();
55
- expect(retrievedInfo).toEqual(mockAppInfo);
56
- });
57
-
58
- it('should throw error when not initialized', async () => {
59
- await expect(repository.getAppInfo()).rejects.toThrow('App info not initialized');
60
- });
61
-
62
- it('should return a copy of the data', async () => {
63
- await repository.saveAppInfo(mockAppInfo);
64
-
65
- const retrievedInfo = await repository.getAppInfo();
66
-
67
- // Modify returned object
68
- const modifiedInfo = { ...retrievedInfo };
69
- modifiedInfo.name = 'Modified';
70
-
71
- // Get it again to verify original is unchanged
72
- const retrievedInfo2 = await repository.getAppInfo();
73
- expect(retrievedInfo2.name).toBe('Test App');
74
- });
75
-
76
- it('should throw error when destroyed', async () => {
77
- await repository.saveAppInfo(mockAppInfo);
78
- repository.destroy();
79
-
80
- await expect(repository.getAppInfo()).rejects.toThrow('Repository has been destroyed');
81
- });
82
- });
83
-
84
- describe('updateAppInfo', () => {
85
- beforeEach(async () => {
86
- await repository.saveAppInfo(mockAppInfo);
87
- });
88
-
89
- it('should update app info successfully', async () => {
90
- const updates = { name: 'Updated App', version: '2.0.0' };
91
-
92
- const updatedInfo = await repository.updateAppInfo(updates);
93
-
94
- expect(updatedInfo.name).toBe('Updated App');
95
- expect(updatedInfo.version).toBe('2.0.0');
96
- expect(updatedInfo.description).toBe('Test Description'); // Unchanged
97
- expect(updatedInfo.developer).toBe('Test Developer'); // Unchanged
98
- });
99
-
100
- it('should throw error when not initialized', async () => {
101
- const newRepository = new AboutRepository();
102
-
103
- await expect(newRepository.updateAppInfo({ name: 'Test' })).rejects.toThrow('App info not initialized');
104
- });
105
-
106
- it('should throw error for invalid updates', async () => {
107
- await expect(repository.updateAppInfo(null as unknown)).rejects.toThrow('Invalid updates provided');
108
- await expect(repository.updateAppInfo(undefined as unknown)).rejects.toThrow('Invalid updates provided');
109
- await expect(repository.updateAppInfo('invalid' as unknown)).rejects.toThrow('Invalid updates provided');
110
- });
111
-
112
- it('should return a copy of the updated data', async () => {
113
- const updates = { name: 'Updated App' };
114
-
115
- const updatedInfo = await repository.updateAppInfo(updates);
116
-
117
- // Modify returned object
118
- const modifiedInfo = { ...updatedInfo };
119
- modifiedInfo.name = 'Modified';
120
-
121
- // Get it again to verify original is unchanged
122
- const updatedInfo2 = await repository.getAppInfo();
123
- expect(updatedInfo2.name).toBe('Updated App');
124
- });
125
-
126
- it('should throw error when destroyed', async () => {
127
- repository.destroy();
128
-
129
- await expect(repository.updateAppInfo({ name: 'Test' })).rejects.toThrow('Repository has been destroyed');
130
- });
131
- });
132
-
133
- describe('destroy', () => {
134
- it('should cleanup resources', async () => {
135
- await repository.saveAppInfo(mockAppInfo);
136
-
137
- repository.destroy();
138
-
139
- await expect(repository.getAppInfo()).rejects.toThrow('Repository has been destroyed');
140
- await expect(repository.updateAppInfo({ name: 'Test' })).rejects.toThrow('Repository has been destroyed');
141
- await expect(repository.saveAppInfo(mockAppInfo)).rejects.toThrow('Repository has been destroyed');
142
- });
143
-
144
- it('should be safe to call multiple times', async () => {
145
- await repository.saveAppInfo(mockAppInfo);
146
-
147
- repository.destroy();
148
- repository.destroy(); // Should not throw
149
-
150
- await expect(repository.getAppInfo()).rejects.toThrow('Repository has been destroyed');
151
- });
152
- });
153
- });