@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.
- package/package.json +16 -15
- package/src/domains/about/__tests__/integration.test.tsx +328 -0
- package/src/domains/about/__tests__/types.d.ts +5 -0
- package/src/domains/about/domain/entities/AppInfo.ts +74 -0
- package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +93 -0
- package/src/domains/about/domain/repositories/IAboutRepository.ts +22 -0
- package/src/domains/about/index.ts +10 -0
- package/src/domains/about/infrastructure/repositories/AboutRepository.ts +68 -0
- package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +153 -0
- package/src/domains/about/presentation/components/AboutContent.tsx +104 -0
- package/src/domains/about/presentation/components/AboutHeader.tsx +79 -0
- package/src/domains/about/presentation/components/AboutSection.tsx +134 -0
- package/src/domains/about/presentation/components/AboutSettingItem.tsx +208 -0
- package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +178 -0
- package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +293 -0
- package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +201 -0
- package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +71 -0
- package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +229 -0
- package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +240 -0
- package/src/domains/about/presentation/hooks/useAboutInfo.ts +262 -0
- package/src/domains/about/presentation/screens/AboutScreen.tsx +195 -0
- package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +199 -0
- package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +366 -0
- package/src/domains/about/types/global.d.ts +15 -0
- package/src/domains/about/utils/__tests__/index.test.ts +408 -0
- package/src/domains/about/utils/index.ts +160 -0
- package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +195 -0
- package/src/domains/appearance/__tests__/hooks/index.test.tsx +232 -0
- package/src/domains/appearance/__tests__/integration/index.test.tsx +207 -0
- package/src/domains/appearance/__tests__/services/appearanceService.test.ts +299 -0
- package/src/domains/appearance/__tests__/setup.ts +96 -0
- package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +175 -0
- package/src/domains/appearance/data/colorPalettes.ts +94 -0
- package/src/domains/appearance/hooks/index.ts +6 -0
- package/src/domains/appearance/hooks/useAppearance.ts +61 -0
- package/src/domains/appearance/hooks/useAppearanceActions.ts +144 -0
- package/src/domains/appearance/index.ts +7 -0
- package/src/domains/appearance/infrastructure/services/appearanceService.ts +301 -0
- package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +79 -0
- package/src/domains/appearance/infrastructure/services/validation.ts +91 -0
- package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +120 -0
- package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +132 -0
- package/src/domains/appearance/presentation/components/AppearanceHeader.tsx +67 -0
- package/src/domains/appearance/presentation/components/AppearancePreview.tsx +141 -0
- package/src/domains/appearance/presentation/components/AppearanceSection.tsx +139 -0
- package/src/domains/appearance/presentation/components/ColorPicker.tsx +113 -0
- package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +186 -0
- package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +110 -0
- package/src/domains/appearance/presentation/components/ThemeOption.tsx +138 -0
- package/src/domains/appearance/presentation/components/index.ts +6 -0
- package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +226 -0
- package/src/domains/appearance/presentation/screens/index.ts +2 -0
- package/src/domains/appearance/types/index.ts +54 -0
- package/src/domains/faqs/domain/entities/FAQEntity.ts +16 -0
- package/src/domains/faqs/domain/services/FAQSearchService.ts +36 -0
- package/src/domains/faqs/domain/services/index.ts +1 -0
- package/src/domains/faqs/index.ts +7 -0
- package/src/domains/faqs/presentation/components/FAQCategory.tsx +71 -0
- package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +75 -0
- package/src/domains/faqs/presentation/components/FAQItem.tsx +103 -0
- package/src/domains/faqs/presentation/components/FAQSearchBar.tsx +70 -0
- package/src/domains/faqs/presentation/components/FAQSection.tsx +50 -0
- package/src/domains/faqs/presentation/components/index.ts +18 -0
- package/src/domains/faqs/presentation/hooks/index.ts +6 -0
- package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +51 -0
- package/src/domains/faqs/presentation/hooks/useFAQSearch.ts +33 -0
- package/src/domains/faqs/presentation/screens/FAQScreen.tsx +129 -0
- package/src/domains/faqs/presentation/screens/index.ts +2 -0
- package/src/domains/feedback/domain/entities/FeedbackEntity.ts +92 -0
- package/src/domains/feedback/domain/repositories/IFeedbackRepository.ts +28 -0
- package/src/domains/feedback/index.ts +6 -0
- package/src/domains/feedback/presentation/components/FeedbackForm.tsx +189 -0
- package/src/domains/feedback/presentation/components/FeedbackModal.tsx +111 -0
- package/src/domains/feedback/presentation/components/SupportSection.tsx +160 -0
- package/src/domains/feedback/presentation/hooks/useDeleteFeedback.ts +25 -0
- package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +59 -0
- package/src/domains/feedback/presentation/hooks/useSubmitFeedback.ts +55 -0
- package/src/domains/feedback/presentation/hooks/useUserFeedback.ts +29 -0
- package/src/domains/legal/__tests__/ContentValidationService.test.ts +195 -0
- package/src/domains/legal/__tests__/StyleCacheService.test.ts +110 -0
- package/src/domains/legal/__tests__/UrlHandlerService.test.ts +71 -0
- package/src/domains/legal/__tests__/setup.ts +82 -0
- package/src/domains/legal/domain/entities/LegalConfig.ts +26 -0
- package/src/domains/legal/domain/services/ContentValidationService.ts +89 -0
- package/src/domains/legal/domain/services/StyleCacheService.ts +97 -0
- package/src/domains/legal/domain/services/UrlHandlerService.ts +128 -0
- package/src/domains/legal/index.ts +8 -0
- package/src/domains/legal/presentation/components/LegalItem.tsx +177 -0
- package/src/domains/legal/presentation/components/LegalLinks.tsx +154 -0
- package/src/domains/legal/presentation/components/LegalSection.tsx +134 -0
- package/src/domains/legal/presentation/screens/LegalScreen.tsx +237 -0
- package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +214 -0
- package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +214 -0
- package/src/index.ts +19 -0
- package/src/presentation/components/DevSettingsSection.tsx +2 -2
- package/src/presentation/components/SettingItem.tsx +2 -2
- package/src/presentation/components/SettingsErrorBoundary.tsx +2 -2
- package/src/presentation/components/SettingsFooter.tsx +2 -2
- package/src/presentation/components/SettingsSection.tsx +2 -2
- package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
- package/src/presentation/screens/SettingsScreen.tsx +2 -2
- package/src/presentation/screens/components/SettingsContent.tsx +2 -2
- package/src/presentation/screens/components/SettingsHeader.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.17.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.17.16",
|
|
4
|
+
"description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, and FAQs",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
"user-settings",
|
|
19
19
|
"theme",
|
|
20
20
|
"language",
|
|
21
|
-
"notifications"
|
|
21
|
+
"notifications",
|
|
22
|
+
"about",
|
|
23
|
+
"legal",
|
|
24
|
+
"appearance",
|
|
25
|
+
"feedback",
|
|
26
|
+
"faqs",
|
|
27
|
+
"consolidated"
|
|
22
28
|
],
|
|
23
29
|
"author": "Ümit UZ <umit@umituz.com>",
|
|
24
30
|
"license": "MIT",
|
|
@@ -30,13 +36,9 @@
|
|
|
30
36
|
"@expo/vector-icons": ">=14.0.0",
|
|
31
37
|
"@react-navigation/native": ">=6.0.0",
|
|
32
38
|
"@react-navigation/stack": ">=6.0.0",
|
|
33
|
-
"@umituz/react-native-about": "latest",
|
|
34
|
-
"@umituz/react-native-appearance": "latest",
|
|
35
39
|
"@umituz/react-native-auth": "latest",
|
|
36
40
|
"@umituz/react-native-avatar": "latest",
|
|
37
|
-
"@umituz/react-native-
|
|
38
|
-
"@umituz/react-native-feedback": "latest",
|
|
39
|
-
"@umituz/react-native-legal": "latest",
|
|
41
|
+
"@umituz/react-native-design-system": "latest",
|
|
40
42
|
"@umituz/react-native-localization": "latest",
|
|
41
43
|
"@umituz/react-native-notifications": "latest",
|
|
42
44
|
"@umituz/react-native-onboarding": "latest",
|
|
@@ -53,13 +55,9 @@
|
|
|
53
55
|
"@expo/vector-icons": "^15.0.0",
|
|
54
56
|
"@types/jest": "^29.5.14",
|
|
55
57
|
"@types/react": "~19.1.10",
|
|
56
|
-
"@umituz/react-native-about": "latest",
|
|
57
|
-
"@umituz/react-native-appearance": "latest",
|
|
58
58
|
"@umituz/react-native-auth": "latest",
|
|
59
59
|
"@umituz/react-native-avatar": "latest",
|
|
60
|
-
"@umituz/react-native-
|
|
61
|
-
"@umituz/react-native-feedback": "latest",
|
|
62
|
-
"@umituz/react-native-legal": "latest",
|
|
60
|
+
"@umituz/react-native-design-system": "latest",
|
|
63
61
|
"@umituz/react-native-localization": "latest",
|
|
64
62
|
"@umituz/react-native-notifications": "latest",
|
|
65
63
|
"@umituz/react-native-onboarding": "latest",
|
|
@@ -76,5 +74,8 @@
|
|
|
76
74
|
"src",
|
|
77
75
|
"README.md",
|
|
78
76
|
"LICENSE"
|
|
79
|
-
]
|
|
80
|
-
|
|
77
|
+
],
|
|
78
|
+
"dependencies": {
|
|
79
|
+
"@umituz/react-native-design-system": "^2.2.0"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain Entity - Application Information
|
|
3
|
+
* Pure business logic, no external dependencies
|
|
4
|
+
* Part of About Domain
|
|
5
|
+
*/
|
|
6
|
+
export interface AppInfo {
|
|
7
|
+
/** Application name */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Application version (semver format) */
|
|
10
|
+
version: string;
|
|
11
|
+
/** Application description */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Developer/Company name */
|
|
14
|
+
developer?: string;
|
|
15
|
+
/** Contact email */
|
|
16
|
+
contactEmail?: string;
|
|
17
|
+
/** Website URL */
|
|
18
|
+
websiteUrl?: string;
|
|
19
|
+
/** Website display text */
|
|
20
|
+
websiteDisplay?: string;
|
|
21
|
+
/** More apps URL */
|
|
22
|
+
moreAppsUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Configuration interface for About component
|
|
27
|
+
* Fully configurable by parent application
|
|
28
|
+
*/
|
|
29
|
+
export interface AboutConfig {
|
|
30
|
+
/** Application information */
|
|
31
|
+
appInfo?: Partial<AppInfo>;
|
|
32
|
+
/** Custom theme colors */
|
|
33
|
+
theme?: {
|
|
34
|
+
primary?: string;
|
|
35
|
+
secondary?: string;
|
|
36
|
+
background?: string;
|
|
37
|
+
text?: string;
|
|
38
|
+
border?: string;
|
|
39
|
+
};
|
|
40
|
+
/** Custom styling options */
|
|
41
|
+
style?: {
|
|
42
|
+
containerStyle?: Record<string, unknown>;
|
|
43
|
+
itemStyle?: Record<string, unknown>;
|
|
44
|
+
textStyle?: Record<string, unknown>;
|
|
45
|
+
iconStyle?: Record<string, unknown>;
|
|
46
|
+
};
|
|
47
|
+
/** Custom actions */
|
|
48
|
+
actions?: {
|
|
49
|
+
onWebsitePress?: () => void;
|
|
50
|
+
onEmailPress?: () => void;
|
|
51
|
+
onMoreAppsPress?: () => void;
|
|
52
|
+
};
|
|
53
|
+
/** Localized texts for section headers and labels */
|
|
54
|
+
texts?: {
|
|
55
|
+
contact?: string;
|
|
56
|
+
more?: string;
|
|
57
|
+
developer?: string;
|
|
58
|
+
email?: string;
|
|
59
|
+
website?: string;
|
|
60
|
+
moreApps?: string;
|
|
61
|
+
loading?: string;
|
|
62
|
+
errorPrefix?: string;
|
|
63
|
+
noInfo?: string;
|
|
64
|
+
versionPrefix?: string;
|
|
65
|
+
};
|
|
66
|
+
/** Navigation route name */
|
|
67
|
+
route?: string;
|
|
68
|
+
/** Default navigation route name */
|
|
69
|
+
defaultRoute?: string;
|
|
70
|
+
/** Section title */
|
|
71
|
+
title?: string;
|
|
72
|
+
/** Section description */
|
|
73
|
+
description?: string;
|
|
74
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository interface for About data
|
|
3
|
+
* Defines contract for data access layer
|
|
4
|
+
*/
|
|
5
|
+
import { AppInfo } from '../entities/AppInfo';
|
|
6
|
+
|
|
7
|
+
export interface IAboutRepository {
|
|
8
|
+
/**
|
|
9
|
+
* Get application information
|
|
10
|
+
*/
|
|
11
|
+
getAppInfo(): Promise<AppInfo>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Save application information
|
|
15
|
+
*/
|
|
16
|
+
saveAppInfo(appInfo: AppInfo): Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Update application information
|
|
20
|
+
*/
|
|
21
|
+
updateAppInfo(updates: Partial<AppInfo>): Promise<AppInfo>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* About Domain
|
|
3
|
+
* User information, app details, version info
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './presentation/screens/AboutScreen';
|
|
7
|
+
export * from './presentation/components/AboutContent';
|
|
8
|
+
export * from './presentation/components/AboutSection';
|
|
9
|
+
export * from './presentation/components/AboutSettingItem';
|
|
10
|
+
export * from './presentation/components/AboutHeader';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository implementation for About data
|
|
3
|
+
* Handles data persistence and retrieval
|
|
4
|
+
* Optimized for performance and memory safety
|
|
5
|
+
*/
|
|
6
|
+
import { AppInfo } from '../../domain/entities/AppInfo';
|
|
7
|
+
import { IAboutRepository } from '../../domain/repositories/IAboutRepository';
|
|
8
|
+
|
|
9
|
+
export class AboutRepository implements IAboutRepository {
|
|
10
|
+
private appInfo: AppInfo | null = null;
|
|
11
|
+
private isDestroyed = false;
|
|
12
|
+
|
|
13
|
+
async getAppInfo(): Promise<AppInfo> {
|
|
14
|
+
if (this.isDestroyed) {
|
|
15
|
+
throw new Error('Repository has been destroyed');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!this.appInfo) {
|
|
19
|
+
throw new Error('App info not initialized');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Return a deep copy to prevent mutations
|
|
23
|
+
return { ...this.appInfo };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async saveAppInfo(appInfo: AppInfo): Promise<void> {
|
|
27
|
+
if (this.isDestroyed) {
|
|
28
|
+
throw new Error('Repository has been destroyed');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate input to prevent invalid data
|
|
32
|
+
if (!appInfo || typeof appInfo !== 'object') {
|
|
33
|
+
throw new Error('Invalid app info provided');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Store a deep copy to prevent external mutations
|
|
37
|
+
this.appInfo = { ...appInfo };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async updateAppInfo(updates: Partial<AppInfo>): Promise<AppInfo> {
|
|
41
|
+
if (this.isDestroyed) {
|
|
42
|
+
throw new Error('Repository has been destroyed');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!this.appInfo) {
|
|
46
|
+
throw new Error('App info not initialized');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate input to prevent invalid data
|
|
50
|
+
if (!updates || typeof updates !== 'object') {
|
|
51
|
+
throw new Error('Invalid updates provided');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Create new object to prevent mutations
|
|
55
|
+
this.appInfo = { ...this.appInfo, ...updates };
|
|
56
|
+
|
|
57
|
+
// Return a deep copy to prevent mutations
|
|
58
|
+
return { ...this.appInfo };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Cleanup method to prevent memory leaks
|
|
63
|
+
*/
|
|
64
|
+
destroy(): void {
|
|
65
|
+
this.appInfo = null;
|
|
66
|
+
this.isDestroyed = true;
|
|
67
|
+
}
|
|
68
|
+
}
|