@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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Support Section Component
|
|
3
|
+
* Renders Support entry points: Feedback and Rating
|
|
4
|
+
* Agnostic of UI implementation via render props
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useCallback } from "react";
|
|
8
|
+
import { Linking } from "react-native";
|
|
9
|
+
import { FeedbackModal } from "./FeedbackModal";
|
|
10
|
+
import type { FeedbackType } from "../../domain/entities/FeedbackEntity";
|
|
11
|
+
|
|
12
|
+
export interface FeedbackConfig {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
initialType?: FeedbackType;
|
|
17
|
+
onSubmit?: (data: { type: any; rating: number; description: string; title: string }) => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RatingConfig {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
title?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
storeUrl?: string;
|
|
25
|
+
onRate?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FeedbackModalTexts {
|
|
29
|
+
title?: string;
|
|
30
|
+
ratingLabel?: string;
|
|
31
|
+
descriptionPlaceholder?: string;
|
|
32
|
+
submitButton?: string;
|
|
33
|
+
submittingButton?: string;
|
|
34
|
+
feedbackTypes?: Array<{ type: FeedbackType; label: string }>;
|
|
35
|
+
defaultTitle?: (type: FeedbackType) => string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SupportSectionProps {
|
|
39
|
+
feedbackConfig: { enabled: boolean; config?: FeedbackConfig };
|
|
40
|
+
ratingConfig: { enabled: boolean; config?: RatingConfig };
|
|
41
|
+
renderSection: (props: { title: string; children: React.ReactNode }) => React.ReactElement | null;
|
|
42
|
+
renderItem: (props: {
|
|
43
|
+
title: string;
|
|
44
|
+
icon: any;
|
|
45
|
+
onPress: () => void;
|
|
46
|
+
isLast?: boolean
|
|
47
|
+
}) => React.ReactElement | null;
|
|
48
|
+
/** Texts for the feedback modal */
|
|
49
|
+
feedbackModalTexts?: FeedbackModalTexts;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const SupportSection: React.FC<SupportSectionProps> = ({
|
|
53
|
+
feedbackConfig,
|
|
54
|
+
ratingConfig,
|
|
55
|
+
renderSection,
|
|
56
|
+
renderItem,
|
|
57
|
+
feedbackModalTexts
|
|
58
|
+
}) => {
|
|
59
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
60
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
61
|
+
|
|
62
|
+
const handleFeedbackSubmit = async (data: { type: any; rating: number; description: string; title: string }) => {
|
|
63
|
+
if (feedbackConfig.config?.onSubmit) {
|
|
64
|
+
setIsSubmitting(true);
|
|
65
|
+
try {
|
|
66
|
+
await feedbackConfig.config.onSubmit(data);
|
|
67
|
+
setModalVisible(false);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (__DEV__) {
|
|
70
|
+
console.error("Feedback submission error:", error);
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
setIsSubmitting(false);
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
if (__DEV__) {
|
|
77
|
+
console.warn("No onSubmit handler provided for Feedback");
|
|
78
|
+
}
|
|
79
|
+
setModalVisible(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleRateApp = useCallback(async () => {
|
|
84
|
+
const config = ratingConfig.config;
|
|
85
|
+
if (config?.onRate) {
|
|
86
|
+
config.onRate();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (config?.storeUrl) {
|
|
91
|
+
const supported = await Linking.canOpenURL(config.storeUrl);
|
|
92
|
+
if (supported) {
|
|
93
|
+
await Linking.openURL(config.storeUrl);
|
|
94
|
+
} else {
|
|
95
|
+
if (__DEV__) {
|
|
96
|
+
console.warn("Cannot open store URL:", config.storeUrl);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (__DEV__) {
|
|
101
|
+
console.warn("No storeUrl or onRate provided for Rating");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, [ratingConfig.config]);
|
|
105
|
+
|
|
106
|
+
const showFeedback = feedbackConfig.enabled;
|
|
107
|
+
const showRating = ratingConfig.enabled;
|
|
108
|
+
|
|
109
|
+
if (!showFeedback && !showRating) return null;
|
|
110
|
+
|
|
111
|
+
// Use provided titles, no hardcoded defaults
|
|
112
|
+
const sectionTitle = (showFeedback && feedbackConfig.config?.title) || (showRating && ratingConfig.config?.title);
|
|
113
|
+
|
|
114
|
+
// If no section title provided, don't render
|
|
115
|
+
if (!sectionTitle) return null;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
{renderSection({
|
|
120
|
+
title: sectionTitle,
|
|
121
|
+
children: (
|
|
122
|
+
<>
|
|
123
|
+
{showFeedback && feedbackConfig.config?.description && renderItem({
|
|
124
|
+
title: feedbackConfig.config.description,
|
|
125
|
+
icon: "mail-outline",
|
|
126
|
+
onPress: () => setModalVisible(true),
|
|
127
|
+
isLast: !showRating
|
|
128
|
+
})}
|
|
129
|
+
|
|
130
|
+
{showRating && ratingConfig.config?.description && renderItem({
|
|
131
|
+
title: ratingConfig.config.description,
|
|
132
|
+
icon: "star-outline",
|
|
133
|
+
onPress: handleRateApp,
|
|
134
|
+
isLast: true
|
|
135
|
+
})}
|
|
136
|
+
</>
|
|
137
|
+
)
|
|
138
|
+
})}
|
|
139
|
+
|
|
140
|
+
{showFeedback && feedbackModalTexts && (
|
|
141
|
+
<FeedbackModal
|
|
142
|
+
visible={modalVisible}
|
|
143
|
+
onClose={() => setModalVisible(false)}
|
|
144
|
+
onSubmit={handleFeedbackSubmit}
|
|
145
|
+
initialType={feedbackConfig.config?.initialType}
|
|
146
|
+
isSubmitting={isSubmitting}
|
|
147
|
+
title={feedbackModalTexts.title}
|
|
148
|
+
texts={{
|
|
149
|
+
ratingLabel: feedbackModalTexts.ratingLabel,
|
|
150
|
+
descriptionPlaceholder: feedbackModalTexts.descriptionPlaceholder,
|
|
151
|
+
submitButton: feedbackModalTexts.submitButton,
|
|
152
|
+
submittingButton: feedbackModalTexts.submittingButton,
|
|
153
|
+
feedbackTypes: feedbackModalTexts.feedbackTypes,
|
|
154
|
+
defaultTitle: feedbackModalTexts.defaultTitle,
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete Feedback Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import type { IFeedbackRepository } from '../../domain/repositories/IFeedbackRepository';
|
|
7
|
+
|
|
8
|
+
export function useDeleteFeedback(repository: IFeedbackRepository) {
|
|
9
|
+
const queryClient = useQueryClient();
|
|
10
|
+
|
|
11
|
+
return useMutation({
|
|
12
|
+
mutationFn: async (feedbackId: string) => {
|
|
13
|
+
const result = await repository.deleteFeedback(feedbackId);
|
|
14
|
+
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw new Error(result.error.message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return result.data;
|
|
20
|
+
},
|
|
21
|
+
onSuccess: () => {
|
|
22
|
+
queryClient.invalidateQueries({ queryKey: ['feedback'] });
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Form Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useCallback } from 'react';
|
|
6
|
+
import type { FeedbackType, FeedbackRating } from '../../domain/entities/FeedbackEntity';
|
|
7
|
+
|
|
8
|
+
export interface FeedbackFormState {
|
|
9
|
+
type: FeedbackType;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
rating?: FeedbackRating;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const initialState: FeedbackFormState = {
|
|
16
|
+
type: 'general',
|
|
17
|
+
title: '',
|
|
18
|
+
description: '',
|
|
19
|
+
rating: undefined,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function useFeedbackForm(defaultValues?: Partial<FeedbackFormState>) {
|
|
23
|
+
const [formState, setFormState] = useState<FeedbackFormState>({
|
|
24
|
+
...initialState,
|
|
25
|
+
...defaultValues,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const setType = useCallback((type: FeedbackType) => {
|
|
29
|
+
setFormState((prev) => ({ ...prev, type }));
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const setTitle = useCallback((title: string) => {
|
|
33
|
+
setFormState((prev) => ({ ...prev, title }));
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const setDescription = useCallback((description: string) => {
|
|
37
|
+
setFormState((prev) => ({ ...prev, description }));
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const setRating = useCallback((rating: FeedbackRating | undefined) => {
|
|
41
|
+
setFormState((prev) => ({ ...prev, rating }));
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const reset = useCallback(() => {
|
|
45
|
+
setFormState({ ...initialState, ...defaultValues });
|
|
46
|
+
}, [defaultValues]);
|
|
47
|
+
|
|
48
|
+
const isValid = formState.title.trim().length > 0 && formState.description.trim().length > 0;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
formState,
|
|
52
|
+
setType,
|
|
53
|
+
setTitle,
|
|
54
|
+
setDescription,
|
|
55
|
+
setRating,
|
|
56
|
+
reset,
|
|
57
|
+
isValid,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit Feedback Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import type { IFeedbackRepository } from '../../domain/repositories/IFeedbackRepository';
|
|
7
|
+
import {
|
|
8
|
+
createFeedback,
|
|
9
|
+
type FeedbackType,
|
|
10
|
+
} from '../../domain/entities/FeedbackEntity';
|
|
11
|
+
|
|
12
|
+
interface SubmitFeedbackParams {
|
|
13
|
+
userId: string | null;
|
|
14
|
+
userEmail?: string | null;
|
|
15
|
+
type: FeedbackType;
|
|
16
|
+
title: string;
|
|
17
|
+
description: string;
|
|
18
|
+
rating?: 1 | 2 | 3 | 4 | 5;
|
|
19
|
+
deviceInfo?: {
|
|
20
|
+
platform: string;
|
|
21
|
+
osVersion: string;
|
|
22
|
+
appVersion: string;
|
|
23
|
+
};
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useSubmitFeedback(repository: IFeedbackRepository) {
|
|
28
|
+
const queryClient = useQueryClient();
|
|
29
|
+
|
|
30
|
+
return useMutation({
|
|
31
|
+
mutationFn: async (params: SubmitFeedbackParams) => {
|
|
32
|
+
const feedback = createFeedback(
|
|
33
|
+
params.userId,
|
|
34
|
+
params.type,
|
|
35
|
+
params.title,
|
|
36
|
+
params.description,
|
|
37
|
+
params.userEmail,
|
|
38
|
+
params.rating,
|
|
39
|
+
params.deviceInfo,
|
|
40
|
+
params.metadata
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const result = await repository.submitFeedback(feedback);
|
|
44
|
+
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
throw new Error(result.error.message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result.data;
|
|
50
|
+
},
|
|
51
|
+
onSuccess: () => {
|
|
52
|
+
queryClient.invalidateQueries({ queryKey: ['feedback'] });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Feedback Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import type { IFeedbackRepository } from '../../domain/repositories/IFeedbackRepository';
|
|
7
|
+
|
|
8
|
+
export function useUserFeedback(
|
|
9
|
+
repository: IFeedbackRepository,
|
|
10
|
+
userId: string | null
|
|
11
|
+
) {
|
|
12
|
+
return useQuery({
|
|
13
|
+
queryKey: ['feedback', userId],
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
if (!userId) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const result = await repository.getUserFeedback(userId);
|
|
20
|
+
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
throw new Error(result.error.message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result.data;
|
|
26
|
+
},
|
|
27
|
+
enabled: !!userId,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ContentValidationService
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ContentValidationService } from '../domain/services/ContentValidationService';
|
|
6
|
+
|
|
7
|
+
describe('ContentValidationService', () => {
|
|
8
|
+
describe('validateScreenContent', () => {
|
|
9
|
+
const originalWarn = console.warn;
|
|
10
|
+
const mockWarn = jest.fn();
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mockWarn.mockClear();
|
|
14
|
+
console.warn = mockWarn;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
console.warn = originalWarn;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should warn when neither content nor url is provided', () => {
|
|
22
|
+
ContentValidationService.validateScreenContent(
|
|
23
|
+
undefined,
|
|
24
|
+
undefined,
|
|
25
|
+
'Test Title',
|
|
26
|
+
'View Online',
|
|
27
|
+
'Open',
|
|
28
|
+
'TestScreen'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(mockWarn).toHaveBeenCalledWith(
|
|
32
|
+
'TestScreen: Either content or url must be provided'
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should warn when title is not provided', () => {
|
|
37
|
+
ContentValidationService.validateScreenContent(
|
|
38
|
+
'Some content',
|
|
39
|
+
undefined,
|
|
40
|
+
undefined,
|
|
41
|
+
'View Online',
|
|
42
|
+
'Open',
|
|
43
|
+
'TestScreen'
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(mockWarn).toHaveBeenCalledWith(
|
|
47
|
+
'TestScreen: title is required'
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should warn when url is provided but viewOnlineText is missing', () => {
|
|
52
|
+
ContentValidationService.validateScreenContent(
|
|
53
|
+
undefined,
|
|
54
|
+
'https://example.com',
|
|
55
|
+
'Test Title',
|
|
56
|
+
undefined,
|
|
57
|
+
'Open',
|
|
58
|
+
'TestScreen'
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(mockWarn).toHaveBeenCalledWith(
|
|
62
|
+
'TestScreen: viewOnlineText is required when url is provided'
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should warn when url is provided but openText is missing', () => {
|
|
67
|
+
ContentValidationService.validateScreenContent(
|
|
68
|
+
undefined,
|
|
69
|
+
'https://example.com',
|
|
70
|
+
'Test Title',
|
|
71
|
+
'View Online',
|
|
72
|
+
undefined,
|
|
73
|
+
'TestScreen'
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(mockWarn).toHaveBeenCalledWith(
|
|
77
|
+
'TestScreen: openText is required when url is provided'
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should not warn when all required props are provided', () => {
|
|
82
|
+
ContentValidationService.validateScreenContent(
|
|
83
|
+
'Some content',
|
|
84
|
+
undefined,
|
|
85
|
+
'Test Title',
|
|
86
|
+
'View Online',
|
|
87
|
+
'Open',
|
|
88
|
+
'TestScreen'
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(mockWarn).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('validateLegalLinks', () => {
|
|
96
|
+
const originalWarn = console.warn;
|
|
97
|
+
const mockWarn = jest.fn();
|
|
98
|
+
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
mockWarn.mockClear();
|
|
101
|
+
console.warn = mockWarn;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
afterEach(() => {
|
|
105
|
+
console.warn = originalWarn;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should warn when privacyPolicyUrl is provided but privacyText is missing', () => {
|
|
109
|
+
ContentValidationService.validateLegalLinks(
|
|
110
|
+
'https://privacy.com',
|
|
111
|
+
undefined,
|
|
112
|
+
undefined,
|
|
113
|
+
undefined,
|
|
114
|
+
undefined,
|
|
115
|
+
undefined
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(mockWarn).toHaveBeenCalledWith(
|
|
119
|
+
'LegalLinks: privacyText is required when privacyPolicyUrl is provided'
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should warn when termsOfServiceUrl is provided but termsText is missing', () => {
|
|
124
|
+
ContentValidationService.validateLegalLinks(
|
|
125
|
+
undefined,
|
|
126
|
+
'https://terms.com',
|
|
127
|
+
undefined,
|
|
128
|
+
undefined,
|
|
129
|
+
undefined,
|
|
130
|
+
undefined
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(mockWarn).toHaveBeenCalledWith(
|
|
134
|
+
'LegalLinks: termsText is required when termsOfServiceUrl is provided'
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should not warn when onPrivacyPress is provided instead of privacyText', () => {
|
|
139
|
+
const onPrivacyPress = jest.fn();
|
|
140
|
+
|
|
141
|
+
ContentValidationService.validateLegalLinks(
|
|
142
|
+
'https://privacy.com',
|
|
143
|
+
undefined,
|
|
144
|
+
undefined,
|
|
145
|
+
undefined,
|
|
146
|
+
onPrivacyPress,
|
|
147
|
+
undefined
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
expect(mockWarn).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('hasValidContent', () => {
|
|
155
|
+
it('should return true when content is provided', () => {
|
|
156
|
+
expect(ContentValidationService.hasValidContent('Some content')).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return true when url is provided', () => {
|
|
160
|
+
expect(ContentValidationService.hasValidContent(undefined, 'https://example.com')).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should return false when neither content nor url is provided', () => {
|
|
164
|
+
expect(ContentValidationService.hasValidContent()).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('shouldShowUrlSection', () => {
|
|
169
|
+
it('should return true when url is provided', () => {
|
|
170
|
+
expect(ContentValidationService.shouldShowUrlSection('https://example.com')).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should return true when onUrlPress is provided', () => {
|
|
174
|
+
expect(ContentValidationService.shouldShowUrlSection(undefined, jest.fn())).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should return false when neither url nor onUrlPress is provided', () => {
|
|
178
|
+
expect(ContentValidationService.shouldShowUrlSection()).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('shouldShowLegalItem', () => {
|
|
183
|
+
it('should return true when both onPress and title are provided', () => {
|
|
184
|
+
expect(ContentValidationService.shouldShowLegalItem(jest.fn(), 'Title')).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should return false when onPress is missing', () => {
|
|
188
|
+
expect(ContentValidationService.shouldShowLegalItem(undefined, 'Title')).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should return false when title is missing', () => {
|
|
192
|
+
expect(ContentValidationService.shouldShowLegalItem(jest.fn())).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for StyleCacheService
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleCacheService } from '../domain/services/StyleCacheService';
|
|
6
|
+
|
|
7
|
+
describe('StyleCacheService', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
StyleCacheService.clearAllCaches();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('getCachedStyles', () => {
|
|
13
|
+
it('should cache and return styles', () => {
|
|
14
|
+
const styleFactory = () => ({ test: 'style' });
|
|
15
|
+
const result1 = StyleCacheService.getCachedStyles('test', 'key1', styleFactory);
|
|
16
|
+
const result2 = StyleCacheService.getCachedStyles('test', 'key1', styleFactory);
|
|
17
|
+
|
|
18
|
+
expect(result1).toEqual({ test: 'style' });
|
|
19
|
+
expect(result1).toBe(result2); // Should be the same reference
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should create new styles for different keys', () => {
|
|
23
|
+
const styleFactory = () => ({ test: 'style' });
|
|
24
|
+
const result1 = StyleCacheService.getCachedStyles('test', 'key1', styleFactory);
|
|
25
|
+
const result2 = StyleCacheService.getCachedStyles('test', 'key2', styleFactory);
|
|
26
|
+
|
|
27
|
+
expect(result1).toEqual(result2);
|
|
28
|
+
expect(result1).not.toBe(result2); // Should be different references
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should limit cache size', () => {
|
|
32
|
+
const styleFactory = (index: number) => ({ test: `style${index}` });
|
|
33
|
+
const maxSize = 3;
|
|
34
|
+
|
|
35
|
+
// Fill cache beyond max size
|
|
36
|
+
for (let i = 0; i < maxSize + 2; i++) {
|
|
37
|
+
StyleCacheService.getCachedStyles('test', `key${i}`, () => styleFactory(i), maxSize);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Cache should be at most maxSize + 1 (since cleanup happens after adding)
|
|
41
|
+
expect(StyleCacheService.getCacheSize('test')).toBeLessThanOrEqual(maxSize + 1);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('clearCache', () => {
|
|
46
|
+
it('should clear specific cache', () => {
|
|
47
|
+
const styleFactory = () => ({ test: 'style' });
|
|
48
|
+
StyleCacheService.getCachedStyles('test1', 'key1', styleFactory);
|
|
49
|
+
StyleCacheService.getCachedStyles('test2', 'key1', styleFactory);
|
|
50
|
+
|
|
51
|
+
expect(StyleCacheService.getCacheSize('test1')).toBe(1);
|
|
52
|
+
expect(StyleCacheService.getCacheSize('test2')).toBe(1);
|
|
53
|
+
|
|
54
|
+
StyleCacheService.clearCache('test1');
|
|
55
|
+
|
|
56
|
+
expect(StyleCacheService.getCacheSize('test1')).toBe(0);
|
|
57
|
+
expect(StyleCacheService.getCacheSize('test2')).toBe(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('clearAllCaches', () => {
|
|
62
|
+
it('should clear all caches', () => {
|
|
63
|
+
const styleFactory = () => ({ test: 'style' });
|
|
64
|
+
StyleCacheService.getCachedStyles('test1', 'key1', styleFactory);
|
|
65
|
+
StyleCacheService.getCachedStyles('test2', 'key1', styleFactory);
|
|
66
|
+
|
|
67
|
+
expect(StyleCacheService.getCacheSize('test1')).toBe(1);
|
|
68
|
+
expect(StyleCacheService.getCacheSize('test2')).toBe(1);
|
|
69
|
+
|
|
70
|
+
StyleCacheService.clearAllCaches();
|
|
71
|
+
|
|
72
|
+
expect(StyleCacheService.getCacheSize('test1')).toBe(0);
|
|
73
|
+
expect(StyleCacheService.getCacheSize('test2')).toBe(0);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('createTokenCacheKey', () => {
|
|
78
|
+
it('should create cache key from design tokens', () => {
|
|
79
|
+
const tokens = {
|
|
80
|
+
colors: { backgroundPrimary: '#fff', primary: '#000', textPrimary: '#333', textSecondary: '#666', textTertiary: '#999', onSurface: '#111', secondary: '#555', info: '#00f' },
|
|
81
|
+
spacing: { xs: 4, sm: 8, md: 16, lg: 24 }
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const key1 = StyleCacheService.createTokenCacheKey(tokens);
|
|
85
|
+
const key2 = StyleCacheService.createTokenCacheKey(tokens);
|
|
86
|
+
|
|
87
|
+
expect(key1).toBe(key2);
|
|
88
|
+
expect(typeof key1).toBe('string');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should include additional keys in cache key', () => {
|
|
92
|
+
const tokens = {
|
|
93
|
+
colors: { backgroundPrimary: '#fff', primary: '#000', textPrimary: '#333', textSecondary: '#666', textTertiary: '#999', onSurface: '#111', secondary: '#555', info: '#00f' },
|
|
94
|
+
spacing: { xs: 4, sm: 8, md: 16, lg: 24 }
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const key1 = StyleCacheService.createTokenCacheKey(tokens, { extra: 'value' });
|
|
98
|
+
const key2 = StyleCacheService.createTokenCacheKey(tokens, { extra: 'different' });
|
|
99
|
+
|
|
100
|
+
expect(key1).not.toBe(key2);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('createSimpleCacheKey', () => {
|
|
105
|
+
it('should create simple cache key', () => {
|
|
106
|
+
const key = StyleCacheService.createSimpleCacheKey('test-key');
|
|
107
|
+
expect(key).toBe('test-key');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|