@umituz/react-native-settings 4.3.0 → 4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "Settings management for React Native apps - user preferences, theme, language, notifications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -39,6 +39,7 @@
39
39
  "@umituz/react-native-design-system-theme": "*",
40
40
  "@umituz/react-native-design-system-typography": "*",
41
41
  "@umituz/react-native-legal": "*",
42
+ "@umituz/react-native-disclaimer": "*",
42
43
  "@umituz/react-native-localization": "*",
43
44
  "@umituz/react-native-storage": "*",
44
45
  "react": ">=18.2.0",
@@ -69,6 +70,7 @@
69
70
  "@umituz/react-native-design-system-theme": "latest",
70
71
  "@umituz/react-native-design-system-typography": "latest",
71
72
  "@umituz/react-native-legal": "^2.0.3",
73
+ "@umituz/react-native-disclaimer": "latest",
72
74
  "@umituz/react-native-localization": "latest",
73
75
  "@umituz/react-native-notifications": "latest",
74
76
  "@umituz/react-native-storage": "latest",
@@ -90,4 +92,4 @@
90
92
  "README.md",
91
93
  "LICENSE"
92
94
  ]
93
- }
95
+ }
@@ -16,6 +16,7 @@ import { AboutSection } from "@umituz/react-native-about";
16
16
  import { LegalSection } from "@umituz/react-native-legal";
17
17
  import { AppearanceSection } from "@umituz/react-native-appearance";
18
18
  import { LanguageSection } from "@umituz/react-native-localization";
19
+ import { DisclaimerSetting } from "@umituz/react-native-disclaimer";
19
20
  import type { NormalizedConfig } from "../utils/normalizeConfig";
20
21
  import type { CustomSettingsSection } from "../types";
21
22
 
@@ -28,6 +29,7 @@ interface SettingsContentProps {
28
29
  notifications: boolean;
29
30
  about: boolean;
30
31
  legal: boolean;
32
+ disclaimer: boolean;
31
33
  };
32
34
  showUserProfile?: boolean;
33
35
  userProfile?: {
@@ -69,6 +71,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
69
71
  features.notifications ||
70
72
  features.about ||
71
73
  features.legal ||
74
+ features.disclaimer ||
72
75
  customSections.length > 0,
73
76
  [features, customSections.length]
74
77
  );
@@ -124,6 +127,10 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
124
127
  <LegalSection config={normalizedConfig.legal.config} />
125
128
  )}
126
129
 
130
+ {features.disclaimer && (
131
+ <DisclaimerSetting />
132
+ )}
133
+
127
134
  {customSections && customSections.length > 0 && (
128
135
  <>
129
136
  {sortedSections.map((section, index) => (
@@ -61,7 +61,7 @@ export function useFeatureDetection(
61
61
  },
62
62
  ) {
63
63
  return useMemo(() => {
64
- const { appearance, language, notifications, about, legal, account, support, developer } =
64
+ const { appearance, language, notifications, about, legal, disclaimer, account, support, developer } =
65
65
  normalizedConfig;
66
66
 
67
67
  const notificationServiceAvailable = options?.notificationServiceAvailable ?? notificationService !== null;
@@ -102,6 +102,11 @@ export function useFeatureDetection(
102
102
  (legal.config?.enabled === true ||
103
103
  (legal.config?.enabled !== false &&
104
104
  hasNavigationScreen(navigation, legal.config?.route || "Legal"))),
105
+ disclaimer:
106
+ disclaimer.enabled &&
107
+ (disclaimer.config?.enabled === true ||
108
+ (disclaimer.config?.enabled !== false &&
109
+ hasNavigationScreen(navigation, disclaimer.config?.route || "Disclaimer"))),
105
110
  account:
106
111
  account.enabled &&
107
112
  (account.config?.enabled === true ||
@@ -99,4 +99,22 @@ export interface LegalConfig {
99
99
  description?: string;
100
100
  /** Default route name when no custom route provided */
101
101
  defaultRoute?: string;
102
+ }
103
+
104
+ /**
105
+ * Disclaimer Settings Configuration
106
+ */
107
+ export interface DisclaimerConfig {
108
+ /** Show disclaimer section */
109
+ enabled?: FeatureVisibility;
110
+ /** Custom navigation route for disclaimer screen */
111
+ route?: string;
112
+ /** Custom disclaimer title */
113
+ title?: string;
114
+ /** Custom disclaimer description */
115
+ description?: string;
116
+ /** Custom short message for card display */
117
+ shortMessage?: string;
118
+ /** Default route name when no custom route provided */
119
+ defaultRoute?: string;
102
120
  }
@@ -10,6 +10,7 @@ import type {
10
10
  NotificationsConfig,
11
11
  AboutConfig,
12
12
  LegalConfig,
13
+ DisclaimerConfig,
13
14
  } from "./FeatureConfig";
14
15
  import type {
15
16
  AccountConfig,
@@ -91,6 +92,12 @@ export interface SettingsConfig {
91
92
  */
92
93
  legal?: FeatureVisibility | LegalConfig;
93
94
 
95
+ /**
96
+ * Disclaimer settings (Important notices, warnings)
97
+ * @default false
98
+ */
99
+ disclaimer?: FeatureVisibility | DisclaimerConfig;
100
+
94
101
  /**
95
102
  * Account settings
96
103
  * @default false
@@ -10,6 +10,7 @@ export type {
10
10
  NotificationsConfig,
11
11
  AboutConfig,
12
12
  LegalConfig,
13
+ DisclaimerConfig,
13
14
  } from "./FeatureConfig";
14
15
  export type {
15
16
  AccountConfig,
@@ -10,6 +10,7 @@ import type {
10
10
  NotificationsConfig,
11
11
  AboutConfig,
12
12
  LegalConfig,
13
+ DisclaimerConfig,
13
14
  AccountConfig,
14
15
  SupportConfig,
15
16
  DeveloperConfig,
@@ -37,6 +38,10 @@ export interface NormalizedConfig {
37
38
  enabled: boolean;
38
39
  config?: LegalConfig;
39
40
  };
41
+ disclaimer: {
42
+ enabled: boolean;
43
+ config?: DisclaimerConfig;
44
+ };
40
45
  account: {
41
46
  enabled: boolean;
42
47
  config?: AccountConfig;
@@ -88,6 +93,7 @@ export function normalizeSettingsConfig(
88
93
  notifications: normalizeConfigValue(config?.notifications, "auto"),
89
94
  about: normalizeConfigValue(config?.about, "auto"),
90
95
  legal: normalizeConfigValue(config?.legal, "auto"),
96
+ disclaimer: normalizeConfigValue(config?.disclaimer, false),
91
97
  account: normalizeConfigValue(config?.account, false),
92
98
  support: normalizeConfigValue(config?.support, false),
93
99
  developer: normalizeConfigValue(config?.developer, false),
@@ -1,115 +0,0 @@
1
- /**
2
- * Disclaimer Card Component
3
- * Extracted from DisclaimerSetting to follow single responsibility and 200-line rules
4
- */
5
-
6
- import React from 'react';
7
- import {
8
- View,
9
- StyleSheet,
10
- TouchableOpacity,
11
- } from 'react-native';
12
-
13
- import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system-theme';
14
- import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
15
-
16
- export interface DisclaimerCardProps {
17
- title: string;
18
- shortMessage: string;
19
- iconName: string;
20
- iconColor: string;
21
- backgroundColor: string;
22
- onPress: () => void;
23
- }
24
-
25
- export const DisclaimerCard: React.FC<DisclaimerCardProps> = ({
26
- title,
27
- shortMessage,
28
- iconName,
29
- iconColor,
30
- backgroundColor,
31
- onPress,
32
- }) => {
33
- const tokens = useAppDesignTokens();
34
- const styles = getStyles(tokens);
35
-
36
- return (
37
- <TouchableOpacity
38
- style={[
39
- styles.container,
40
- { backgroundColor },
41
- ]}
42
- onPress={onPress}
43
- activeOpacity={0.7}
44
- testID="disclaimer-setting"
45
- >
46
- {/* Icon and Title Row */}
47
- <View style={styles.headerRow}>
48
- <View
49
- style={[
50
- styles.iconContainer,
51
- {
52
- backgroundColor: withAlpha(iconColor, 0.2),
53
- borderColor: withAlpha(iconColor, 0.4),
54
- borderWidth: 1,
55
- },
56
- ]}
57
- >
58
- <AtomicIcon name={iconName} color="warning" />
59
- </View>
60
- <AtomicText type="bodyLarge" color="primary" style={styles.title}>
61
- {title}
62
- </AtomicText>
63
- <AtomicIcon name="ArrowRight" color="secondary" size="sm" />
64
- </View>
65
-
66
- {/* Short Message */}
67
- <AtomicText
68
- type="bodySmall"
69
- color="secondary"
70
- style={styles.shortMessage}
71
- >
72
- {shortMessage}
73
- </AtomicText>
74
- </TouchableOpacity>
75
- );
76
- };
77
-
78
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
79
- StyleSheet.create({
80
- container: {
81
- paddingHorizontal: tokens.spacing.md,
82
- paddingVertical: tokens.spacing.md,
83
- marginHorizontal: tokens.spacing.md,
84
- marginTop: 8,
85
- marginBottom: 8,
86
- borderRadius: 12,
87
- },
88
-
89
- headerRow: {
90
- flexDirection: 'row',
91
- alignItems: 'center',
92
- marginBottom: 12,
93
- },
94
-
95
- iconContainer: {
96
- width: 40,
97
- height: 40,
98
- borderRadius: 20,
99
- alignItems: 'center',
100
- justifyContent: 'center',
101
- marginRight: 12,
102
- },
103
-
104
- title: {
105
- flex: 1,
106
- fontWeight: tokens.typography.labelLarge.fontWeight as any,
107
- fontSize: tokens.typography.labelLarge.fontSize,
108
- },
109
-
110
- shortMessage: {
111
- lineHeight: 18,
112
- paddingLeft: 52, // Align with title (40px icon + 12px margin)
113
- fontSize: 13,
114
- },
115
- });
@@ -1,104 +0,0 @@
1
- /**
2
- * Disclaimer Modal Component
3
- * Extracted from DisclaimerSetting to follow single responsibility and 200-line rules
4
- */
5
-
6
- import React from 'react';
7
- import {
8
- View,
9
- StyleSheet,
10
- TouchableOpacity,
11
- ScrollView,
12
- } from 'react-native';
13
-
14
- import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
15
- import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
16
-
17
- export interface DisclaimerModalProps {
18
- visible: boolean;
19
- title: string;
20
- content: string;
21
- onClose: () => void;
22
- }
23
-
24
- export const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
25
- visible,
26
- title,
27
- content,
28
- onClose,
29
- }) => {
30
- const tokens = useAppDesignTokens();
31
- const styles = getStyles(tokens);
32
-
33
- if (!visible) return null;
34
-
35
- return (
36
- <View
37
- style={[
38
- styles.modalContainer,
39
- { backgroundColor: tokens.colors.backgroundPrimary },
40
- ]}
41
- >
42
- {/* Modal Header */}
43
- <View
44
- style={[
45
- styles.modalHeader,
46
- { borderBottomColor: tokens.colors.borderLight },
47
- ]}
48
- >
49
- <AtomicText type="headlineMedium" color="primary">
50
- {title}
51
- </AtomicText>
52
- <TouchableOpacity
53
- onPress={onClose}
54
- testID="close-disclaimer-modal"
55
- >
56
- <AtomicIcon name="X" color="primary" size="md" />
57
- </TouchableOpacity>
58
- </View>
59
-
60
- {/* Scrollable Content */}
61
- <ScrollView
62
- style={styles.modalContent}
63
- contentContainerStyle={styles.modalContentContainer}
64
- >
65
- <AtomicText
66
- type="bodyMedium"
67
- color="primary"
68
- style={styles.modalText}
69
- >
70
- {content}
71
- </AtomicText>
72
- </ScrollView>
73
- </View>
74
- );
75
- };
76
-
77
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
78
- StyleSheet.create({
79
- modalContainer: {
80
- flex: 1,
81
- },
82
-
83
- modalHeader: {
84
- flexDirection: 'row',
85
- justifyContent: 'space-between',
86
- alignItems: 'center',
87
- paddingHorizontal: 20,
88
- paddingVertical: 16,
89
- borderBottomWidth: 1,
90
- },
91
-
92
- modalContent: {
93
- flex: 1,
94
- },
95
-
96
- modalContentContainer: {
97
- padding: 20,
98
- },
99
-
100
- modalText: {
101
- lineHeight: 24,
102
- fontSize: 15,
103
- },
104
- });
@@ -1,114 +0,0 @@
1
- /**
2
- * DisclaimerSetting Component
3
- *
4
- * Displays customizable disclaimer with important legal notice
5
- * Used in About screens for apps that require disclaimers
6
- *
7
- * Features:
8
- * - Tappable card that opens full disclaimer modal
9
- * - Warning icon with background color
10
- * - Internationalized title and message
11
- * - Full-screen modal with scrollable content
12
- * - NO shadows (CLAUDE.md compliance)
13
- * - Universal across iOS, Android, Web (NO Platform.OS checks)
14
- *
15
- * Usage:
16
- * - Import and use in AboutScreen
17
- * - Requires translations: settings.disclaimer.title, settings.disclaimer.message, settings.disclaimer.shortMessage
18
- */
19
-
20
- import React, { useState, useEffect, useCallback } from 'react';
21
- import { Modal } from 'react-native';
22
-
23
- import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system-theme';
24
- import { useLocalization } from '@umituz/react-native-localization';
25
- import { DisclaimerCard } from './DisclaimerCard';
26
- import { DisclaimerModal } from './DisclaimerModal';
27
-
28
- export interface DisclaimerSettingProps {
29
- /** Custom title translation key */
30
- titleKey?: string;
31
- /** Custom message translation key */
32
- messageKey?: string;
33
- /** Custom short message translation key */
34
- shortMessageKey?: string;
35
- /** Custom icon name */
36
- iconName?: string;
37
- /** Custom icon color */
38
- iconColor?: string;
39
- /** Custom background color */
40
- backgroundColor?: string;
41
- /** Custom modal title */
42
- modalTitle?: string;
43
- /** Custom modal content */
44
- modalContent?: string;
45
- }
46
-
47
- export const DisclaimerSetting: React.FC<DisclaimerSettingProps> = ({
48
- titleKey = "settings.disclaimer.title",
49
- messageKey = "settings.disclaimer.message",
50
- shortMessageKey = "settings.disclaimer.shortMessage",
51
- iconName = "AlertTriangle",
52
- iconColor,
53
- backgroundColor,
54
- modalTitle,
55
- modalContent,
56
- }) => {
57
- const { t } = useLocalization();
58
- const tokens = useAppDesignTokens();
59
- const [modalVisible, setModalVisible] = useState(false);
60
-
61
- useEffect(() => {
62
- return () => {
63
- setModalVisible(false);
64
- };
65
- }, []);
66
-
67
- const title = modalTitle || t(titleKey);
68
- const content = modalContent || t(messageKey);
69
- const shortMessage = t(shortMessageKey);
70
- const finalIconColor = iconColor || tokens.colors.warning;
71
- const finalBackgroundColor = backgroundColor || withAlpha(finalIconColor, 0.1);
72
-
73
- const handleOpenModal = useCallback(() => {
74
- setModalVisible(true);
75
- if (__DEV__) {
76
- console.log('DisclaimerSetting: Modal opened');
77
- }
78
- }, []);
79
-
80
- const handleCloseModal = useCallback(() => {
81
- setModalVisible(false);
82
- if (__DEV__) {
83
- console.log('DisclaimerSetting: Modal closed');
84
- }
85
- }, []);
86
-
87
- return (
88
- <>
89
- <DisclaimerCard
90
- title={title}
91
- shortMessage={shortMessage}
92
- iconName={iconName}
93
- iconColor={finalIconColor}
94
- backgroundColor={finalBackgroundColor}
95
- onPress={handleOpenModal}
96
- />
97
-
98
- <Modal
99
- visible={modalVisible}
100
- animationType="slide"
101
- presentationStyle="pageSheet"
102
- onRequestClose={handleCloseModal}
103
- >
104
- <DisclaimerModal
105
- visible={modalVisible}
106
- title={title}
107
- content={content}
108
- onClose={handleCloseModal}
109
- />
110
- </Modal>
111
- </>
112
- );
113
- };
114
-
@@ -1,208 +0,0 @@
1
- /**
2
- * Tests for DisclaimerCard Component
3
- */
4
-
5
- import React from 'react';
6
- import { render, fireEvent } from '@testing-library/react-native';
7
- import { DisclaimerCard } from '../DisclaimerCard';
8
-
9
- // Mock dependencies
10
- jest.mock('@umituz/react-native-design-system-theme', () => ({
11
- useAppDesignTokens: () => ({
12
- colors: {
13
- backgroundPrimary: '#ffffff',
14
- },
15
- spacing: {
16
- md: 16,
17
- },
18
- typography: {
19
- labelLarge: {
20
- fontWeight: '500',
21
- fontSize: 14,
22
- },
23
- },
24
- }),
25
- withAlpha: jest.fn((color: string, alpha: number) => `${color}${alpha}`),
26
- }));
27
-
28
- jest.mock('@umituz/react-native-design-system-atoms', () => ({
29
- AtomicText: ({ children, type, color, style }: any) => (
30
- <Text style={style} testID={`atomic-text-${type}-${color}`}>
31
- {children}
32
- </Text>
33
- ),
34
- AtomicIcon: ({ name, color, size, style }: any) => (
35
- <Text style={style} testID={`atomic-icon-${name}-${color}-${size}`}>
36
- Icon: {name}
37
- </Text>
38
- ),
39
- }));
40
-
41
- import { Text } from 'react-native';
42
-
43
- describe('DisclaimerCard', () => {
44
- it('renders correctly with basic props', () => {
45
- const { getByTestId, getByText } = render(
46
- <DisclaimerCard
47
- title="Test Title"
48
- shortMessage="Test Message"
49
- iconName="AlertTriangle"
50
- iconColor="#FF9800"
51
- backgroundColor="#FFF3E0"
52
- onPress={jest.fn()}
53
- />
54
- );
55
-
56
- expect(getByText('Test Title')).toBeTruthy();
57
- expect(getByText('Test Message')).toBeTruthy();
58
- expect(getByTestId('disclaimer-setting')).toBeTruthy();
59
- expect(getByTestId('atomic-icon-AlertTriangle-warning')).toBeTruthy();
60
- expect(getByTestId('atomic-icon-ArrowRight-secondary-sm')).toBeTruthy();
61
- });
62
-
63
- it('calls onPress when pressed', () => {
64
- const mockOnPress = jest.fn();
65
- const { getByTestId } = render(
66
- <DisclaimerCard
67
- title="Test Title"
68
- shortMessage="Test Message"
69
- iconName="AlertTriangle"
70
- iconColor="#FF9800"
71
- backgroundColor="#FFF3E0"
72
- onPress={mockOnPress}
73
- />
74
- );
75
-
76
- fireEvent.press(getByTestId('disclaimer-setting'));
77
- expect(mockOnPress).toHaveBeenCalledTimes(1);
78
- });
79
-
80
- it('applies correct styling with theme tokens', () => {
81
- const { getByTestId } = render(
82
- <DisclaimerCard
83
- title="Test Title"
84
- shortMessage="Test Message"
85
- iconName="AlertTriangle"
86
- iconColor="#FF9800"
87
- backgroundColor="#FFF3E0"
88
- onPress={jest.fn()}
89
- />
90
- );
91
-
92
- const card = getByTestId('disclaimer-setting');
93
- expect(card.props.style).toContainEqual({
94
- backgroundColor: '#FFF3E0',
95
- });
96
- });
97
-
98
- it('renders with different icon names', () => {
99
- const { getByTestId } = render(
100
- <DisclaimerCard
101
- title="Test Title"
102
- shortMessage="Test Message"
103
- iconName="info"
104
- iconColor="#2196F3"
105
- backgroundColor="#E3F2FD"
106
- onPress={jest.fn()}
107
- />
108
- );
109
-
110
- expect(getByTestId('atomic-icon-info-warning')).toBeTruthy();
111
- });
112
-
113
- it('handles long text content', () => {
114
- const longTitle = 'This is a very long disclaimer title that should be handled properly';
115
- const longMessage = 'This is a very long disclaimer message that should wrap properly and not break the layout';
116
-
117
- const { getByText } = render(
118
- <DisclaimerCard
119
- title={longTitle}
120
- shortMessage={longMessage}
121
- iconName="AlertTriangle"
122
- iconColor="#FF9800"
123
- backgroundColor="#FFF3E0"
124
- onPress={jest.fn()}
125
- />
126
- );
127
-
128
- expect(getByText(longTitle)).toBeTruthy();
129
- expect(getByText(longMessage)).toBeTruthy();
130
- });
131
-
132
- it('has correct accessibility properties', () => {
133
- const mockOnPress = jest.fn();
134
- const { getByTestId } = render(
135
- <DisclaimerCard
136
- title="Test Title"
137
- shortMessage="Test Message"
138
- iconName="AlertTriangle"
139
- iconColor="#FF9800"
140
- backgroundColor="#FFF3E0"
141
- onPress={mockOnPress}
142
- />
143
- );
144
-
145
- const touchable = getByTestId('disclaimer-setting');
146
- expect(touchable.props.activeOpacity).toBe(0.7);
147
- });
148
-
149
- it('uses withAlpha utility correctly', () => {
150
- const { withAlpha } = require('@umituz/react-native-design-system-theme');
151
-
152
- render(
153
- <DisclaimerCard
154
- title="Test Title"
155
- shortMessage="Test Message"
156
- iconName="AlertTriangle"
157
- iconColor="#FF9800"
158
- backgroundColor="#FFF3E0"
159
- onPress={jest.fn()}
160
- />
161
- );
162
-
163
- expect(withAlpha).toHaveBeenCalledWith('#FF9800', 0.2);
164
- expect(withAlpha).toHaveBeenCalledWith('#FF9800', 0.4);
165
- });
166
-
167
- it('maintains correct layout structure', () => {
168
- const { getByTestId } = render(
169
- <DisclaimerCard
170
- title="Test Title"
171
- shortMessage="Test Message"
172
- iconName="AlertTriangle"
173
- iconColor="#FF9800"
174
- backgroundColor="#FFF3E0"
175
- onPress={jest.fn()}
176
- />
177
- );
178
-
179
- // Check that icon container has correct styles
180
- expect(getByTestId('atomic-icon-AlertTriangle-warning')).toBeTruthy();
181
-
182
- // Check that title and arrow are present
183
- expect(getByTestId('atomic-icon-ArrowRight-secondary-sm')).toBeTruthy();
184
- });
185
-
186
- it('handles press events correctly', () => {
187
- const mockOnPress = jest.fn();
188
- const { getByTestId } = render(
189
- <DisclaimerCard
190
- title="Test Title"
191
- shortMessage="Test Message"
192
- iconName="AlertTriangle"
193
- iconColor="#FF9800"
194
- backgroundColor="#FFF3E0"
195
- onPress={mockOnPress}
196
- />
197
- );
198
-
199
- // Test single press
200
- fireEvent.press(getByTestId('disclaimer-setting'));
201
- expect(mockOnPress).toHaveBeenCalledTimes(1);
202
-
203
- // Test multiple presses
204
- fireEvent.press(getByTestId('disclaimer-setting'));
205
- fireEvent.press(getByTestId('disclaimer-setting'));
206
- expect(mockOnPress).toHaveBeenCalledTimes(3);
207
- });
208
- });
@@ -1,236 +0,0 @@
1
- /**
2
- * Tests for DisclaimerModal Component
3
- */
4
-
5
- import React from 'react';
6
- import { render, fireEvent } from '@testing-library/react-native';
7
- import { DisclaimerModal } from '../DisclaimerModal';
8
-
9
- // Mock dependencies
10
- jest.mock('@umituz/react-native-design-system-theme', () => ({
11
- useAppDesignTokens: () => ({
12
- colors: {
13
- backgroundPrimary: '#ffffff',
14
- borderLight: '#e0e0e0',
15
- },
16
- }),
17
- }));
18
-
19
- jest.mock('@umituz/react-native-design-system-atoms', () => ({
20
- AtomicText: ({ children, type, color, style }: any) => (
21
- <Text style={style} testID={`atomic-text-${type}-${color}`}>
22
- {children}
23
- </Text>
24
- ),
25
- AtomicIcon: ({ name, color, size, style }: any) => (
26
- <Text style={style} testID={`atomic-icon-${name}-${color}-${size}`}>
27
- Icon: {name}
28
- </Text>
29
- ),
30
- }));
31
-
32
- import { Text, Modal, ScrollView } from 'react-native';
33
-
34
- describe('DisclaimerModal', () => {
35
- it('renders null when visible is false', () => {
36
- const { queryByTestId } = render(
37
- <DisclaimerModal
38
- visible={false}
39
- title="Test Title"
40
- content="Test Content"
41
- onClose={jest.fn()}
42
- />
43
- );
44
-
45
- expect(queryByTestId('disclaimer-modal')).toBeNull();
46
- });
47
-
48
- it('renders correctly when visible is true', () => {
49
- const { getByTestId, getByText } = render(
50
- <DisclaimerModal
51
- visible={true}
52
- title="Test Title"
53
- content="Test Content"
54
- onClose={jest.fn()}
55
- />
56
- );
57
-
58
- expect(getByTestId('disclaimer-modal')).toBeTruthy();
59
- expect(getByText('Test Title')).toBeTruthy();
60
- expect(getByText('Test Content')).toBeTruthy();
61
- });
62
-
63
- it('calls onClose when close button is pressed', () => {
64
- const mockOnClose = jest.fn();
65
- const { getByTestId } = render(
66
- <DisclaimerModal
67
- visible={true}
68
- title="Test Title"
69
- content="Test Content"
70
- onClose={mockOnClose}
71
- />
72
- );
73
-
74
- fireEvent.press(getByTestId('close-disclaimer-modal'));
75
- expect(mockOnClose).toHaveBeenCalledTimes(1);
76
- });
77
-
78
- it('applies correct theme colors', () => {
79
- const { getByTestId } = render(
80
- <DisclaimerModal
81
- visible={true}
82
- title="Test Title"
83
- content="Test Content"
84
- onClose={jest.fn()}
85
- />
86
- );
87
-
88
- const modalContainer = getByTestId('disclaimer-modal');
89
- expect(modalContainer.props.style).toContainEqual({
90
- backgroundColor: '#ffffff',
91
- });
92
- });
93
-
94
- it('renders header with correct structure', () => {
95
- const { getByTestId, getByText } = render(
96
- <DisclaimerModal
97
- visible={true}
98
- title="Test Title"
99
- content="Test Content"
100
- onClose={jest.fn()}
101
- />
102
- );
103
-
104
- expect(getByTestId('atomic-text-headlineMedium-primary')).toBeTruthy();
105
- expect(getByTestId('atomic-icon-X-primary-md')).toBeTruthy();
106
- expect(getByText('Test Title')).toBeTruthy();
107
- });
108
-
109
- it('renders content in ScrollView', () => {
110
- const { getByTestId } = render(
111
- <DisclaimerModal
112
- visible={true}
113
- title="Test Title"
114
- content="Test Content"
115
- onClose={jest.fn()}
116
- />
117
- );
118
-
119
- expect(getByTestId('disclaimer-modal-content')).toBeTruthy();
120
- });
121
-
122
- it('handles long content correctly', () => {
123
- const longContent = 'This is a very long disclaimer content that should scroll properly. '.repeat(20);
124
-
125
- const { getByText } = render(
126
- <DisclaimerModal
127
- visible={true}
128
- title="Test Title"
129
- content={longContent}
130
- onClose={jest.fn()}
131
- />
132
- );
133
-
134
- expect(getByText(longContent)).toBeTruthy();
135
- });
136
-
137
- it('applies correct text styling', () => {
138
- const { getByTestId } = render(
139
- <DisclaimerModal
140
- visible={true}
141
- title="Test Title"
142
- content="Test Content"
143
- onClose={jest.fn()}
144
- />
145
- );
146
-
147
- expect(getByTestId('atomic-text-bodyMedium-primary')).toBeTruthy();
148
- });
149
-
150
- it('has correct accessibility properties', () => {
151
- const mockOnClose = jest.fn();
152
- const { getByTestId } = render(
153
- <DisclaimerModal
154
- visible={true}
155
- title="Test Title"
156
- content="Test Content"
157
- onClose={mockOnClose}
158
- />
159
- );
160
-
161
- const closeButton = getByTestId('close-disclaimer-modal');
162
- expect(closeButton).toBeTruthy();
163
- });
164
-
165
- it('handles multiple close calls', () => {
166
- const mockOnClose = jest.fn();
167
- const { getByTestId } = render(
168
- <DisclaimerModal
169
- visible={true}
170
- title="Test Title"
171
- content="Test Content"
172
- onClose={mockOnClose}
173
- />
174
- );
175
-
176
- const closeButton = getByTestId('close-disclaimer-modal');
177
-
178
- fireEvent.press(closeButton);
179
- fireEvent.press(closeButton);
180
- fireEvent.press(closeButton);
181
-
182
- expect(mockOnClose).toHaveBeenCalledTimes(3);
183
- });
184
-
185
- it('maintains correct layout structure', () => {
186
- const { getByTestId } = render(
187
- <DisclaimerModal
188
- visible={true}
189
- title="Test Title"
190
- content="Test Content"
191
- onClose={jest.fn()}
192
- />
193
- );
194
-
195
- // Check modal container exists
196
- expect(getByTestId('disclaimer-modal')).toBeTruthy();
197
-
198
- // Check header elements exist
199
- expect(getByTestId('atomic-text-headlineMedium-primary')).toBeTruthy();
200
- expect(getByTestId('atomic-icon-X-primary-md')).toBeTruthy();
201
-
202
- // Check content exists
203
- expect(getByTestId('disclaimer-modal-content')).toBeTruthy();
204
- });
205
-
206
- it('handles empty content gracefully', () => {
207
- const { getByTestId, queryByText } = render(
208
- <DisclaimerModal
209
- visible={true}
210
- title="Test Title"
211
- content=""
212
- onClose={jest.fn()}
213
- />
214
- );
215
-
216
- expect(getByTestId('disclaimer-modal')).toBeTruthy();
217
- expect(getByTestId('atomic-text-headlineMedium-primary')).toBeTruthy();
218
- // Empty content should still render the text component
219
- expect(getByTestId('atomic-text-bodyMedium-primary')).toBeTruthy();
220
- });
221
-
222
- it('handles special characters in content', () => {
223
- const specialContent = 'Content with special chars: © ® ™ "quotes" \'apostrophes\'';
224
-
225
- const { getByText } = render(
226
- <DisclaimerModal
227
- visible={true}
228
- title="Test Title"
229
- content={specialContent}
230
- onClose={jest.fn()}
231
- />
232
- );
233
-
234
- expect(getByText(specialContent)).toBeTruthy();
235
- });
236
- });
@@ -1,74 +0,0 @@
1
- /**
2
- * Tests for DisclaimerSetting Component
3
- */
4
-
5
- import React from 'react';
6
- import { render, fireEvent } from '@testing-library/react-native';
7
- import { DisclaimerSetting } from '../DisclaimerSetting';
8
-
9
- // Mock dependencies
10
- jest.mock('@umituz/react-native-localization', () => ({
11
- useLocalization: () => ({
12
- t: (key: string) => key,
13
- }),
14
- }));
15
-
16
- jest.mock('@umituz/react-native-design-system-theme', () => ({
17
- useAppDesignTokens: () => ({
18
- colors: {
19
- backgroundPrimary: '#ffffff',
20
- warning: '#ff9800',
21
- },
22
- spacing: {
23
- md: 16,
24
- },
25
- }),
26
- withAlpha: (color: string, alpha: number) => `${color}${alpha}`,
27
- }));
28
-
29
- describe('DisclaimerSetting', () => {
30
- it('renders disclaimer card correctly', () => {
31
- const { getByTestId } = render(<DisclaimerSetting />);
32
-
33
- expect(getByTestId('disclaimer-setting')).toBeTruthy();
34
- });
35
-
36
- it('opens modal when card is pressed', () => {
37
- const { getByTestId } = render(<DisclaimerSetting />);
38
-
39
- const card = getByTestId('disclaimer-setting');
40
- fireEvent.press(card);
41
-
42
- // Modal should be visible now
43
- expect(getByTestId('close-disclaimer-modal')).toBeTruthy();
44
- });
45
-
46
- it('closes modal when close button is pressed', () => {
47
- const { getByTestId } = render(<DisclaimerSetting />);
48
-
49
- // Open modal first
50
- const card = getByTestId('disclaimer-setting');
51
- fireEvent.press(card);
52
-
53
- // Close modal
54
- const closeButton = getByTestId('close-disclaimer-modal');
55
- fireEvent.press(closeButton);
56
-
57
- // Modal should be closed
58
- expect(() => getByTestId('close-disclaimer-modal')).toThrow();
59
- });
60
-
61
- it('uses custom props when provided', () => {
62
- const customProps = {
63
- titleKey: 'custom.title',
64
- messageKey: 'custom.message',
65
- shortMessageKey: 'custom.shortMessage',
66
- iconName: 'info',
67
- };
68
-
69
- const { getByText } = render(<DisclaimerSetting {...customProps} />);
70
-
71
- expect(getByText('custom.title')).toBeTruthy();
72
- expect(getByText('custom.shortMessage')).toBeTruthy();
73
- });
74
- });