@umituz/react-native-settings 2.0.0 → 2.4.0
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/README.md +129 -3
- package/lib/__tests__/setup.d.ts +5 -0
- package/lib/__tests__/setup.d.ts.map +1 -0
- package/lib/__tests__/setup.js +143 -0
- package/lib/__tests__/setup.js.map +1 -0
- package/lib/domain/repositories/ISettingsRepository.d.ts +51 -0
- package/lib/domain/repositories/ISettingsRepository.d.ts.map +1 -0
- package/lib/domain/repositories/ISettingsRepository.js +8 -0
- package/lib/domain/repositories/ISettingsRepository.js.map +1 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +32 -0
- package/lib/index.js.map +1 -0
- package/lib/infrastructure/storage/SettingsStore.d.ts +36 -0
- package/lib/infrastructure/storage/SettingsStore.d.ts.map +1 -0
- package/lib/infrastructure/storage/SettingsStore.js +144 -0
- package/lib/infrastructure/storage/SettingsStore.js.map +1 -0
- package/lib/presentation/components/CloudSyncSetting.d.ts +16 -0
- package/lib/presentation/components/CloudSyncSetting.d.ts.map +1 -0
- package/lib/presentation/components/CloudSyncSetting.js +30 -0
- package/lib/presentation/components/CloudSyncSetting.js.map +1 -0
- package/lib/presentation/components/DisclaimerCard.d.ts +15 -0
- package/lib/presentation/components/DisclaimerCard.d.ts.map +1 -0
- package/lib/presentation/components/DisclaimerCard.js +73 -0
- package/lib/presentation/components/DisclaimerCard.js.map +1 -0
- package/lib/presentation/components/DisclaimerModal.d.ts +13 -0
- package/lib/presentation/components/DisclaimerModal.d.ts.map +1 -0
- package/lib/presentation/components/DisclaimerModal.js +62 -0
- package/lib/presentation/components/DisclaimerModal.js.map +1 -0
- package/lib/presentation/components/DisclaimerSetting.d.ts +39 -0
- package/lib/presentation/components/DisclaimerSetting.d.ts.map +1 -0
- package/lib/presentation/components/DisclaimerSetting.js +59 -0
- package/lib/presentation/components/DisclaimerSetting.js.map +1 -0
- package/lib/presentation/components/SettingItem.d.ts +45 -0
- package/lib/presentation/components/SettingItem.d.ts.map +1 -0
- package/lib/presentation/components/SettingItem.js +113 -0
- package/lib/presentation/components/SettingItem.js.map +1 -0
- package/lib/presentation/components/SettingsErrorBoundary.d.ts +23 -0
- package/lib/presentation/components/SettingsErrorBoundary.d.ts.map +1 -0
- package/lib/presentation/components/SettingsErrorBoundary.js +73 -0
- package/lib/presentation/components/SettingsErrorBoundary.js.map +1 -0
- package/lib/presentation/components/SettingsFooter.d.ts +11 -0
- package/lib/presentation/components/SettingsFooter.d.ts.map +1 -0
- package/lib/presentation/components/SettingsFooter.js +31 -0
- package/lib/presentation/components/SettingsFooter.js.map +1 -0
- package/lib/presentation/components/SettingsSection.d.ts +13 -0
- package/lib/presentation/components/SettingsSection.d.ts.map +1 -0
- package/lib/presentation/components/SettingsSection.js +37 -0
- package/lib/presentation/components/SettingsSection.js.map +1 -0
- package/lib/presentation/components/StorageClearSetting.d.ts +16 -0
- package/lib/presentation/components/StorageClearSetting.d.ts.map +1 -0
- package/lib/presentation/components/StorageClearSetting.js +21 -0
- package/lib/presentation/components/StorageClearSetting.js.map +1 -0
- package/lib/presentation/components/UserProfileHeader.d.ts +30 -0
- package/lib/presentation/components/UserProfileHeader.d.ts.map +1 -0
- package/lib/presentation/components/UserProfileHeader.js +119 -0
- package/lib/presentation/components/UserProfileHeader.js.map +1 -0
- package/lib/presentation/screens/AppearanceScreen.d.ts +8 -0
- package/lib/presentation/screens/AppearanceScreen.d.ts.map +1 -0
- package/lib/presentation/screens/AppearanceScreen.js +8 -0
- package/lib/presentation/screens/AppearanceScreen.js.map +1 -0
- package/lib/presentation/screens/SettingsScreen.d.ts +38 -0
- package/lib/presentation/screens/SettingsScreen.d.ts.map +1 -0
- package/lib/presentation/screens/SettingsScreen.js +37 -0
- package/lib/presentation/screens/SettingsScreen.js.map +1 -0
- package/lib/presentation/screens/components/AboutLegalSection.d.ts +15 -0
- package/lib/presentation/screens/components/AboutLegalSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/AboutLegalSection.js +28 -0
- package/lib/presentation/screens/components/AboutLegalSection.js.map +1 -0
- package/lib/presentation/screens/components/AppearanceSection.d.ts +12 -0
- package/lib/presentation/screens/components/AppearanceSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/AppearanceSection.js +21 -0
- package/lib/presentation/screens/components/AppearanceSection.js.map +1 -0
- package/lib/presentation/screens/components/LanguageSection.d.ts +12 -0
- package/lib/presentation/screens/components/LanguageSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/LanguageSection.js +26 -0
- package/lib/presentation/screens/components/LanguageSection.js.map +1 -0
- package/lib/presentation/screens/components/NotificationsSection.d.ts +12 -0
- package/lib/presentation/screens/components/NotificationsSection.d.ts.map +1 -0
- package/lib/presentation/screens/components/NotificationsSection.js +58 -0
- package/lib/presentation/screens/components/NotificationsSection.js.map +1 -0
- package/lib/presentation/screens/components/SettingsContent.d.ts +36 -0
- package/lib/presentation/screens/components/SettingsContent.d.ts.map +1 -0
- package/lib/presentation/screens/components/SettingsContent.js +81 -0
- package/lib/presentation/screens/components/SettingsContent.js.map +1 -0
- package/lib/presentation/screens/components/SettingsHeader.d.ts +12 -0
- package/lib/presentation/screens/components/SettingsHeader.d.ts.map +1 -0
- package/lib/presentation/screens/components/SettingsHeader.js +59 -0
- package/lib/presentation/screens/components/SettingsHeader.js.map +1 -0
- package/lib/presentation/screens/components/index.d.ts +9 -0
- package/lib/presentation/screens/components/index.d.ts.map +1 -0
- package/lib/presentation/screens/components/index.js +9 -0
- package/lib/presentation/screens/components/index.js.map +1 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.d.ts +21 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.d.ts.map +1 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.js +82 -0
- package/lib/presentation/screens/hooks/useFeatureDetection.js.map +1 -0
- package/lib/presentation/screens/types/CustomSection.d.ts +19 -0
- package/lib/presentation/screens/types/CustomSection.d.ts.map +1 -0
- package/lib/presentation/screens/types/CustomSection.js +6 -0
- package/lib/presentation/screens/types/CustomSection.js.map +1 -0
- package/lib/presentation/screens/types/ExtendedConfig.d.ts +68 -0
- package/lib/presentation/screens/types/ExtendedConfig.d.ts.map +1 -0
- package/lib/presentation/screens/types/ExtendedConfig.js +6 -0
- package/lib/presentation/screens/types/ExtendedConfig.js.map +1 -0
- package/lib/presentation/screens/types/FeatureConfig.d.ts +95 -0
- package/lib/presentation/screens/types/FeatureConfig.d.ts.map +1 -0
- package/lib/presentation/screens/types/FeatureConfig.js +6 -0
- package/lib/presentation/screens/types/FeatureConfig.js.map +1 -0
- package/lib/presentation/screens/types/SettingsConfig.d.ts +97 -0
- package/lib/presentation/screens/types/SettingsConfig.d.ts.map +1 -0
- package/lib/presentation/screens/types/SettingsConfig.js +6 -0
- package/lib/presentation/screens/types/SettingsConfig.js.map +1 -0
- package/lib/presentation/screens/types/index.d.ts +10 -0
- package/lib/presentation/screens/types/index.d.ts.map +1 -0
- package/lib/presentation/screens/types/index.js +6 -0
- package/lib/presentation/screens/types/index.js.map +1 -0
- package/lib/presentation/screens/utils/normalizeConfig.d.ts +44 -0
- package/lib/presentation/screens/utils/normalizeConfig.d.ts.map +1 -0
- package/lib/presentation/screens/utils/normalizeConfig.js +38 -0
- package/lib/presentation/screens/utils/normalizeConfig.js.map +1 -0
- package/package.json +46 -11
- package/src/__tests__/integration.test.tsx +371 -0
- package/src/__tests__/performance.test.tsx +369 -0
- package/src/__tests__/setup.test.tsx +20 -0
- package/src/__tests__/setup.ts +157 -0
- package/src/index.ts +9 -0
- package/src/infrastructure/storage/SettingsStore.ts +90 -45
- package/src/infrastructure/storage/__tests__/SettingsStore.test.tsx +302 -0
- package/src/presentation/components/CloudSyncSetting.tsx +11 -17
- package/src/presentation/components/DisclaimerCard.tsx +115 -0
- package/src/presentation/components/DisclaimerModal.tsx +104 -0
- package/src/presentation/components/DisclaimerSetting.tsx +77 -159
- package/src/presentation/components/SettingItem.tsx +11 -2
- package/src/presentation/components/SettingsErrorBoundary.tsx +126 -0
- package/src/presentation/components/StorageClearSetting.tsx +13 -8
- package/src/presentation/components/UserProfileHeader.tsx +48 -11
- package/src/presentation/components/__tests__/CloudSyncSetting.test.tsx +78 -0
- package/src/presentation/components/__tests__/DisclaimerCard.test.tsx +208 -0
- package/src/presentation/components/__tests__/DisclaimerModal.test.tsx +236 -0
- package/src/presentation/components/__tests__/DisclaimerSetting.test.tsx +74 -0
- package/src/presentation/components/__tests__/SettingItem.test.tsx +189 -0
- package/src/presentation/components/__tests__/SettingsErrorBoundary.test.tsx +186 -0
- package/src/presentation/screens/SettingsScreen.tsx +29 -159
- package/src/presentation/screens/__tests__/SettingsScreen.test.tsx +322 -0
- package/src/presentation/screens/components/AboutLegalSection.tsx +14 -5
- package/src/presentation/screens/components/AppearanceSection.tsx +1 -1
- package/src/presentation/screens/components/LanguageSection.tsx +2 -1
- package/src/presentation/screens/components/NotificationsSection.tsx +19 -14
- package/src/presentation/screens/components/SettingsContent.tsx +167 -0
- package/src/presentation/screens/components/SettingsHeader.tsx +79 -0
- package/src/presentation/screens/hooks/__tests__/useFeatureDetection.test.tsx +261 -0
- package/src/presentation/screens/hooks/useFeatureDetection.ts +15 -5
- package/src/presentation/screens/types/CustomSection.ts +20 -0
- package/src/presentation/screens/types/ExtendedConfig.ts +68 -0
- package/src/presentation/screens/types/FeatureConfig.ts +102 -0
- package/src/presentation/screens/types/SettingsConfig.ts +116 -0
- package/src/presentation/screens/types/index.ts +20 -0
- package/src/presentation/screens/utils/normalizeConfig.ts +2 -1
- package/src/presentation/screens/types.ts +0 -263
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SettingsScreen Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
7
|
+
import { SettingsScreen } from '../SettingsScreen';
|
|
8
|
+
import { NavigationContainer } from '@react-navigation/native';
|
|
9
|
+
|
|
10
|
+
// Mock dependencies
|
|
11
|
+
jest.mock('@umituz/react-native-design-system-theme', () => ({
|
|
12
|
+
useDesignSystemTheme: () => ({
|
|
13
|
+
themeMode: 'light',
|
|
14
|
+
}),
|
|
15
|
+
useAppDesignTokens: () => ({
|
|
16
|
+
colors: {
|
|
17
|
+
backgroundPrimary: '#ffffff',
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('../components/SettingsHeader', () => 'SettingsHeader');
|
|
23
|
+
jest.mock('../components/SettingsContent', () => 'SettingsContent');
|
|
24
|
+
jest.mock('../components/SettingsErrorBoundary', () => 'SettingsErrorBoundary');
|
|
25
|
+
jest.mock('../utils/normalizeConfig', () => ({
|
|
26
|
+
normalizeSettingsConfig: jest.fn((config) => ({
|
|
27
|
+
appearance: { enabled: true, config: { enabled: true } },
|
|
28
|
+
language: { enabled: true, config: { enabled: true } },
|
|
29
|
+
notifications: { enabled: true, config: { enabled: true } },
|
|
30
|
+
about: { enabled: true, config: { enabled: true } },
|
|
31
|
+
legal: { enabled: true, config: { enabled: true } },
|
|
32
|
+
account: { enabled: true, config: { enabled: true } },
|
|
33
|
+
support: { enabled: true, config: { enabled: true } },
|
|
34
|
+
developer: { enabled: true, config: { enabled: true } },
|
|
35
|
+
})),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
jest.mock('../hooks/useFeatureDetection', () => ({
|
|
39
|
+
useFeatureDetection: jest.fn(() => ({
|
|
40
|
+
appearance: true,
|
|
41
|
+
language: true,
|
|
42
|
+
notifications: true,
|
|
43
|
+
about: true,
|
|
44
|
+
legal: true,
|
|
45
|
+
account: true,
|
|
46
|
+
support: true,
|
|
47
|
+
developer: false,
|
|
48
|
+
})),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Mock navigation
|
|
52
|
+
const mockNavigation = {
|
|
53
|
+
navigate: jest.fn(),
|
|
54
|
+
goBack: jest.fn(),
|
|
55
|
+
getState: jest.fn(() => ({
|
|
56
|
+
routes: [{ name: 'Settings' }],
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
jest.mock('@react-navigation/native', () => ({
|
|
61
|
+
useNavigation: () => mockNavigation,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
// Wrapper component for navigation context
|
|
65
|
+
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
|
66
|
+
<NavigationContainer>
|
|
67
|
+
{children}
|
|
68
|
+
</NavigationContainer>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
describe('SettingsScreen', () => {
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
jest.clearAllMocks();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('renders correctly with default props', () => {
|
|
77
|
+
const { getByTestId } = render(
|
|
78
|
+
<TestWrapper>
|
|
79
|
+
<SettingsScreen />
|
|
80
|
+
</TestWrapper>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Should render the main container
|
|
84
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('renders with custom config', () => {
|
|
88
|
+
const config = {
|
|
89
|
+
appearance: true,
|
|
90
|
+
notifications: false,
|
|
91
|
+
about: true,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const { getByTestId } = render(
|
|
95
|
+
<TestWrapper>
|
|
96
|
+
<SettingsScreen config={config} />
|
|
97
|
+
</TestWrapper>
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('renders with user profile header', () => {
|
|
104
|
+
const userProfile = {
|
|
105
|
+
displayName: 'John Doe',
|
|
106
|
+
userId: 'user123',
|
|
107
|
+
isGuest: false,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const { getByTestId } = render(
|
|
111
|
+
<TestWrapper>
|
|
112
|
+
<SettingsScreen
|
|
113
|
+
showUserProfile={true}
|
|
114
|
+
userProfile={userProfile}
|
|
115
|
+
/>
|
|
116
|
+
</TestWrapper>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('renders with custom footer text', () => {
|
|
123
|
+
const { getByTestId } = render(
|
|
124
|
+
<TestWrapper>
|
|
125
|
+
<SettingsScreen
|
|
126
|
+
footerText="Custom Footer Text"
|
|
127
|
+
/>
|
|
128
|
+
</TestWrapper>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('renders with custom sections', () => {
|
|
135
|
+
const customSections = [
|
|
136
|
+
{
|
|
137
|
+
title: 'Custom Section',
|
|
138
|
+
data: [
|
|
139
|
+
{
|
|
140
|
+
id: 'custom-item',
|
|
141
|
+
title: 'Custom Item',
|
|
142
|
+
icon: 'Settings',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const { getByTestId } = render(
|
|
149
|
+
<TestWrapper>
|
|
150
|
+
<SettingsScreen
|
|
151
|
+
customSections={customSections}
|
|
152
|
+
/>
|
|
153
|
+
</TestWrapper>
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('renders with close button', () => {
|
|
160
|
+
const mockOnClose = jest.fn();
|
|
161
|
+
|
|
162
|
+
const { getByTestId } = render(
|
|
163
|
+
<TestWrapper>
|
|
164
|
+
<SettingsScreen
|
|
165
|
+
showCloseButton={true}
|
|
166
|
+
onClose={mockOnClose}
|
|
167
|
+
/>
|
|
168
|
+
</TestWrapper>
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('applies correct theme styling', () => {
|
|
175
|
+
const { getByTestId } = render(
|
|
176
|
+
<TestWrapper>
|
|
177
|
+
<SettingsScreen />
|
|
178
|
+
</TestWrapper>
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const container = getByTestId('settings-screen');
|
|
182
|
+
expect(container.props.style).toContainEqual({
|
|
183
|
+
backgroundColor: '#ffffff',
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('handles feature detection options', () => {
|
|
188
|
+
const featureOptions = {
|
|
189
|
+
notificationServiceAvailable: false,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const { getByTestId } = render(
|
|
193
|
+
<TestWrapper>
|
|
194
|
+
<SettingsScreen
|
|
195
|
+
featureOptions={featureOptions}
|
|
196
|
+
/>
|
|
197
|
+
</TestWrapper>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('wraps content in error boundary', () => {
|
|
204
|
+
const { getByTestId } = render(
|
|
205
|
+
<TestWrapper>
|
|
206
|
+
<SettingsScreen />
|
|
207
|
+
</TestWrapper>
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('renders without footer when showFooter is false', () => {
|
|
214
|
+
const { getByTestId } = render(
|
|
215
|
+
<TestWrapper>
|
|
216
|
+
<SettingsScreen
|
|
217
|
+
showFooter={false}
|
|
218
|
+
/>
|
|
219
|
+
</TestWrapper>
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('handles guest user profile', () => {
|
|
226
|
+
const guestProfile = {
|
|
227
|
+
displayName: 'Guest User',
|
|
228
|
+
userId: 'guest123',
|
|
229
|
+
isGuest: true,
|
|
230
|
+
guestDisplayName: 'Guest',
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const { getByTestId } = render(
|
|
234
|
+
<TestWrapper>
|
|
235
|
+
<SettingsScreen
|
|
236
|
+
showUserProfile={true}
|
|
237
|
+
userProfile={guestProfile}
|
|
238
|
+
/>
|
|
239
|
+
</TestWrapper>
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('handles user profile with avatar', () => {
|
|
246
|
+
const profileWithAvatar = {
|
|
247
|
+
displayName: 'John Doe',
|
|
248
|
+
userId: 'user123',
|
|
249
|
+
isGuest: false,
|
|
250
|
+
avatarUrl: 'https://example.com/avatar.jpg',
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const { getByTestId } = render(
|
|
254
|
+
<TestWrapper>
|
|
255
|
+
<SettingsScreen
|
|
256
|
+
showUserProfile={true}
|
|
257
|
+
userProfile={profileWithAvatar}
|
|
258
|
+
/>
|
|
259
|
+
</TestWrapper>
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('handles empty config', () => {
|
|
266
|
+
const { getByTestId } = render(
|
|
267
|
+
<TestWrapper>
|
|
268
|
+
<SettingsScreen config={{}} />
|
|
269
|
+
</TestWrapper>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('handles missing user profile props', () => {
|
|
276
|
+
const { getByTestId } = render(
|
|
277
|
+
<TestWrapper>
|
|
278
|
+
<SettingsScreen
|
|
279
|
+
showUserProfile={true}
|
|
280
|
+
userProfile={{}}
|
|
281
|
+
/>
|
|
282
|
+
</TestWrapper>
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('handles null custom sections', () => {
|
|
289
|
+
const { getByTestId } = render(
|
|
290
|
+
<TestWrapper>
|
|
291
|
+
<SettingsScreen
|
|
292
|
+
customSections={null as any}
|
|
293
|
+
/>
|
|
294
|
+
</TestWrapper>
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('handles undefined feature options', () => {
|
|
301
|
+
const { getByTestId } = render(
|
|
302
|
+
<TestWrapper>
|
|
303
|
+
<SettingsScreen
|
|
304
|
+
featureOptions={undefined as any}
|
|
305
|
+
/>
|
|
306
|
+
</TestWrapper>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('maintains correct component structure', () => {
|
|
313
|
+
const { getByTestId } = render(
|
|
314
|
+
<TestWrapper>
|
|
315
|
+
<SettingsScreen />
|
|
316
|
+
</TestWrapper>
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Should have main container
|
|
320
|
+
expect(getByTestId('settings-screen')).toBeTruthy();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -7,6 +7,7 @@ import React from "react";
|
|
|
7
7
|
import { Info, FileText } from "lucide-react-native";
|
|
8
8
|
import { useNavigation } from "@react-navigation/native";
|
|
9
9
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
|
+
import type { NavigationProp } from "@react-navigation/native";
|
|
10
11
|
import { SettingItem } from "../../components/SettingItem";
|
|
11
12
|
import { SettingsSection } from "../../components/SettingsSection";
|
|
12
13
|
import type { AboutConfig, LegalConfig } from "../types";
|
|
@@ -18,25 +19,33 @@ interface AboutLegalSectionProps {
|
|
|
18
19
|
legalConfig?: LegalConfig;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
type RootStackParamList = {
|
|
23
|
+
About: undefined;
|
|
24
|
+
Legal: undefined;
|
|
25
|
+
[key: string]: undefined;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type NavigationType = NavigationProp<RootStackParamList>;
|
|
29
|
+
|
|
21
30
|
export const AboutLegalSection: React.FC<AboutLegalSectionProps> = ({
|
|
22
31
|
showAbout,
|
|
23
32
|
showLegal,
|
|
24
33
|
aboutConfig,
|
|
25
34
|
legalConfig,
|
|
26
35
|
}) => {
|
|
27
|
-
const navigation = useNavigation();
|
|
36
|
+
const navigation = useNavigation<NavigationType>();
|
|
28
37
|
const { t } = useLocalization();
|
|
29
38
|
|
|
30
39
|
if (!showAbout && !showLegal) {
|
|
31
40
|
return null;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
const aboutRoute = aboutConfig?.route || "About";
|
|
43
|
+
const aboutRoute = aboutConfig?.route || aboutConfig?.defaultRoute || "About";
|
|
35
44
|
const aboutTitle = aboutConfig?.title || t("settings.about.title");
|
|
36
45
|
const aboutDescription =
|
|
37
46
|
aboutConfig?.description || t("settings.about.description");
|
|
38
47
|
|
|
39
|
-
const legalRoute = legalConfig?.route || "Legal";
|
|
48
|
+
const legalRoute = legalConfig?.route || legalConfig?.defaultRoute || "Legal";
|
|
40
49
|
const legalTitle = legalConfig?.title || t("settings.legal.title");
|
|
41
50
|
const legalDescription =
|
|
42
51
|
legalConfig?.description || t("settings.legal.description");
|
|
@@ -48,7 +57,7 @@ export const AboutLegalSection: React.FC<AboutLegalSectionProps> = ({
|
|
|
48
57
|
icon={Info}
|
|
49
58
|
title={aboutTitle}
|
|
50
59
|
value={aboutDescription}
|
|
51
|
-
onPress={() => navigation.navigate(aboutRoute as
|
|
60
|
+
onPress={() => navigation.navigate(aboutRoute as string)}
|
|
52
61
|
/>
|
|
53
62
|
)}
|
|
54
63
|
{showLegal && (
|
|
@@ -56,7 +65,7 @@ export const AboutLegalSection: React.FC<AboutLegalSectionProps> = ({
|
|
|
56
65
|
icon={FileText}
|
|
57
66
|
title={legalTitle}
|
|
58
67
|
value={legalDescription}
|
|
59
|
-
onPress={() => navigation.navigate(legalRoute as
|
|
68
|
+
onPress={() => navigation.navigate(legalRoute as string)}
|
|
60
69
|
isLast={true}
|
|
61
70
|
/>
|
|
62
71
|
)}
|
|
@@ -21,7 +21,7 @@ export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
|
|
|
21
21
|
const navigation = useNavigation();
|
|
22
22
|
const { t } = useLocalization();
|
|
23
23
|
|
|
24
|
-
const route = config?.route || "Appearance";
|
|
24
|
+
const route = config?.route || config?.defaultRoute || "Appearance";
|
|
25
25
|
const title = config?.title || t("settings.appearance.title");
|
|
26
26
|
const description = config?.description || t("settings.appearance.description");
|
|
27
27
|
|
|
@@ -26,9 +26,10 @@ export const LanguageSection: React.FC<LanguageSectionProps> = ({
|
|
|
26
26
|
const description = config?.description || "";
|
|
27
27
|
|
|
28
28
|
const currentLang = getLanguageByCode(currentLanguage);
|
|
29
|
+
const defaultLanguageDisplay = config?.defaultLanguageDisplay || "English";
|
|
29
30
|
const languageDisplay = currentLang
|
|
30
31
|
? `${currentLang.flag} ${currentLang.nativeName}`
|
|
31
|
-
:
|
|
32
|
+
: defaultLanguageDisplay;
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
35
|
<SettingsSection title={t("settings.sections.app.title")}>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Single Responsibility: Render notifications settings section
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useState, useEffect } from "react";
|
|
6
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
7
7
|
import { Bell } from "lucide-react-native";
|
|
8
8
|
import { useNavigation } from "@react-navigation/native";
|
|
9
9
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
@@ -12,11 +12,16 @@ import { SettingsSection } from "../../components/SettingsSection";
|
|
|
12
12
|
import type { NotificationsConfig } from "../types";
|
|
13
13
|
|
|
14
14
|
// Optional notification service
|
|
15
|
-
let notificationService:
|
|
15
|
+
let notificationService: {
|
|
16
|
+
hasPermissions?: () => Promise<boolean>;
|
|
17
|
+
requestPermissions?: () => Promise<void>;
|
|
18
|
+
} | null = null;
|
|
16
19
|
try {
|
|
17
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
const module = require("@umituz/react-native-notifications");
|
|
22
|
+
if (module?.notificationService && typeof module.notificationService === 'object') {
|
|
23
|
+
notificationService = module.notificationService;
|
|
24
|
+
}
|
|
20
25
|
} catch {
|
|
21
26
|
// Package not available
|
|
22
27
|
}
|
|
@@ -28,7 +33,7 @@ interface NotificationsSectionProps {
|
|
|
28
33
|
export const NotificationsSection: React.FC<NotificationsSectionProps> = ({
|
|
29
34
|
config,
|
|
30
35
|
}) => {
|
|
31
|
-
const navigation = useNavigation();
|
|
36
|
+
const navigation = useNavigation<any>();
|
|
32
37
|
const { t } = useLocalization();
|
|
33
38
|
const [notificationsEnabled, setNotificationsEnabled] = useState(
|
|
34
39
|
config?.initialValue ?? true,
|
|
@@ -40,27 +45,27 @@ export const NotificationsSection: React.FC<NotificationsSectionProps> = ({
|
|
|
40
45
|
}
|
|
41
46
|
}, [config?.initialValue]);
|
|
42
47
|
|
|
43
|
-
const handleToggle = async (value: boolean) => {
|
|
48
|
+
const handleToggle = useCallback(async (value: boolean) => {
|
|
44
49
|
if (notificationService && !value) {
|
|
45
|
-
const hasPermissions = await notificationService.hasPermissions();
|
|
50
|
+
const hasPermissions = await notificationService.hasPermissions?.();
|
|
46
51
|
if (!hasPermissions) {
|
|
47
|
-
await notificationService.requestPermissions();
|
|
52
|
+
await notificationService.requestPermissions?.();
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
setNotificationsEnabled(value);
|
|
52
57
|
config?.onToggleChange?.(value);
|
|
53
|
-
};
|
|
58
|
+
}, [config?.onToggleChange]);
|
|
54
59
|
|
|
55
|
-
const handlePress = async () => {
|
|
60
|
+
const handlePress = useCallback(async () => {
|
|
56
61
|
if (notificationService) {
|
|
57
|
-
const hasPermissions = await notificationService.hasPermissions();
|
|
62
|
+
const hasPermissions = await notificationService.hasPermissions?.();
|
|
58
63
|
if (!hasPermissions) {
|
|
59
|
-
await notificationService.requestPermissions();
|
|
64
|
+
await notificationService.requestPermissions?.();
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
|
-
navigation.navigate(
|
|
63
|
-
};
|
|
67
|
+
navigation.navigate(config?.route || config?.defaultRoute || "Notifications" as any);
|
|
68
|
+
}, [navigation, config?.route, config?.defaultRoute]);
|
|
64
69
|
|
|
65
70
|
const title = config?.title || t("settings.notifications.title");
|
|
66
71
|
const description = config?.description || t("settings.notifications.description");
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Content Component
|
|
3
|
+
* Renders all settings sections and custom content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { View, ScrollView, StyleSheet } from "react-native";
|
|
8
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
9
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
10
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
11
|
+
import { SettingsFooter } from "../../components/SettingsFooter";
|
|
12
|
+
import { UserProfileHeader } from "../../components/UserProfileHeader";
|
|
13
|
+
import { SettingsSection } from "../../components/SettingsSection";
|
|
14
|
+
import { AppearanceSection } from "./AppearanceSection";
|
|
15
|
+
import { LanguageSection } from "./LanguageSection";
|
|
16
|
+
import { NotificationsSection } from "./NotificationsSection";
|
|
17
|
+
import { AboutLegalSection } from "./AboutLegalSection";
|
|
18
|
+
import type { NormalizedConfig } from "../utils/normalizeConfig";
|
|
19
|
+
import type { CustomSettingsSection } from "../types";
|
|
20
|
+
|
|
21
|
+
interface SettingsContentProps {
|
|
22
|
+
normalizedConfig: NormalizedConfig;
|
|
23
|
+
config?: any; // Original config for emptyStateText
|
|
24
|
+
features: {
|
|
25
|
+
appearance: boolean;
|
|
26
|
+
language: boolean;
|
|
27
|
+
notifications: boolean;
|
|
28
|
+
about: boolean;
|
|
29
|
+
legal: boolean;
|
|
30
|
+
};
|
|
31
|
+
showUserProfile?: boolean;
|
|
32
|
+
userProfile?: {
|
|
33
|
+
displayName?: string;
|
|
34
|
+
userId?: string;
|
|
35
|
+
isGuest?: boolean;
|
|
36
|
+
avatarUrl?: string;
|
|
37
|
+
accountSettingsRoute?: string;
|
|
38
|
+
onPress?: () => void;
|
|
39
|
+
guestDisplayName?: string;
|
|
40
|
+
avatarServiceUrl?: string;
|
|
41
|
+
};
|
|
42
|
+
showFooter?: boolean;
|
|
43
|
+
footerText?: string;
|
|
44
|
+
customSections?: CustomSettingsSection[];
|
|
45
|
+
showCloseButton?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
49
|
+
normalizedConfig,
|
|
50
|
+
config,
|
|
51
|
+
features,
|
|
52
|
+
showUserProfile = false,
|
|
53
|
+
userProfile,
|
|
54
|
+
showFooter = true,
|
|
55
|
+
footerText,
|
|
56
|
+
customSections = [],
|
|
57
|
+
showCloseButton = false,
|
|
58
|
+
}) => {
|
|
59
|
+
const tokens = useAppDesignTokens();
|
|
60
|
+
const insets = useSafeAreaInsets();
|
|
61
|
+
const { t } = useLocalization();
|
|
62
|
+
|
|
63
|
+
const hasAnyFeatures = useMemo(() =>
|
|
64
|
+
features.appearance ||
|
|
65
|
+
features.language ||
|
|
66
|
+
features.notifications ||
|
|
67
|
+
features.about ||
|
|
68
|
+
features.legal ||
|
|
69
|
+
customSections.length > 0,
|
|
70
|
+
[features, customSections.length]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const sortedSections = useMemo(() => {
|
|
74
|
+
return Array.from(customSections)
|
|
75
|
+
.sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
|
|
76
|
+
}, [customSections]);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ScrollView
|
|
80
|
+
style={styles.scrollView}
|
|
81
|
+
contentContainerStyle={[
|
|
82
|
+
styles.scrollContent,
|
|
83
|
+
{
|
|
84
|
+
paddingTop: showCloseButton ? tokens.spacing.md : insets.top + tokens.spacing.md,
|
|
85
|
+
paddingBottom: tokens.spacing.xxxl + tokens.spacing.xl,
|
|
86
|
+
paddingHorizontal: 0,
|
|
87
|
+
},
|
|
88
|
+
]}
|
|
89
|
+
showsVerticalScrollIndicator={false}
|
|
90
|
+
>
|
|
91
|
+
{showUserProfile && (
|
|
92
|
+
<View style={styles.profileContainer}>
|
|
93
|
+
<UserProfileHeader
|
|
94
|
+
displayName={userProfile?.displayName}
|
|
95
|
+
userId={userProfile?.userId}
|
|
96
|
+
isGuest={userProfile?.isGuest}
|
|
97
|
+
avatarUrl={userProfile?.avatarUrl}
|
|
98
|
+
accountSettingsRoute={userProfile?.accountSettingsRoute}
|
|
99
|
+
onPress={userProfile?.onPress}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
)}
|
|
103
|
+
|
|
104
|
+
{features.appearance && (
|
|
105
|
+
<AppearanceSection config={normalizedConfig.appearance.config} />
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{features.language && (
|
|
109
|
+
<LanguageSection config={normalizedConfig.language.config} />
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{features.notifications && (
|
|
113
|
+
<NotificationsSection config={normalizedConfig.notifications.config} />
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{(features.about || features.legal) && (
|
|
117
|
+
<AboutLegalSection
|
|
118
|
+
showAbout={features.about}
|
|
119
|
+
showLegal={features.legal}
|
|
120
|
+
aboutConfig={normalizedConfig.about.config}
|
|
121
|
+
legalConfig={normalizedConfig.legal.config}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
|
|
125
|
+
{customSections && customSections.length > 0 && (
|
|
126
|
+
<>
|
|
127
|
+
{sortedSections.map((section, index) => (
|
|
128
|
+
<SettingsSection
|
|
129
|
+
key={section.id || `custom-${index}`}
|
|
130
|
+
title={section.title}
|
|
131
|
+
>
|
|
132
|
+
{section.content}
|
|
133
|
+
</SettingsSection>
|
|
134
|
+
))}
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
{!hasAnyFeatures && (
|
|
139
|
+
<View style={styles.emptyContainer}>
|
|
140
|
+
<SettingsSection
|
|
141
|
+
title={config?.emptyStateText || t("settings.noOptionsAvailable") || "No settings available"}
|
|
142
|
+
>
|
|
143
|
+
<View />
|
|
144
|
+
</SettingsSection>
|
|
145
|
+
</View>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{showFooter && <SettingsFooter versionText={footerText} />}
|
|
149
|
+
</ScrollView>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const styles = StyleSheet.create({
|
|
154
|
+
scrollView: {
|
|
155
|
+
flex: 1,
|
|
156
|
+
},
|
|
157
|
+
scrollContent: {
|
|
158
|
+
flexGrow: 1,
|
|
159
|
+
},
|
|
160
|
+
profileContainer: {
|
|
161
|
+
marginBottom: 32,
|
|
162
|
+
paddingHorizontal: 0,
|
|
163
|
+
},
|
|
164
|
+
emptyContainer: {
|
|
165
|
+
paddingVertical: 24,
|
|
166
|
+
},
|
|
167
|
+
});
|