@umituz/react-native-localization 2.7.0 → 3.0.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 +21 -10
- package/scripts/prepublish.js +29 -16
- package/src/domain/repositories/ILocalizationRepository.ts +2 -2
- package/src/index.ts +5 -1
- package/src/infrastructure/components/LanguageSwitcher.tsx +90 -37
- package/src/infrastructure/components/LocalizationProvider.tsx +115 -7
- package/src/infrastructure/components/__tests__/LanguageSwitcher.test.tsx +91 -0
- package/src/infrastructure/components/useLanguageNavigation.ts +4 -4
- package/src/infrastructure/config/TranslationCache.ts +27 -0
- package/src/infrastructure/config/TranslationLoader.ts +4 -8
- package/src/infrastructure/config/__tests__/TranslationCache.test.ts +44 -0
- package/src/infrastructure/config/__tests__/languagesData.test.ts +49 -0
- package/src/infrastructure/config/languages.ts +1 -1
- package/src/infrastructure/config/languagesData.ts +91 -61
- package/src/infrastructure/hooks/__tests__/useTranslation.test.ts +52 -0
- package/src/infrastructure/hooks/useLocalization.ts +58 -0
- package/src/infrastructure/hooks/useTranslation.ts +84 -29
- package/src/infrastructure/storage/LanguageInitializer.ts +7 -5
- package/src/infrastructure/storage/LanguageSwitcher.ts +3 -3
- package/src/infrastructure/storage/LocalizationStore.ts +103 -94
- package/src/infrastructure/storage/types/LocalizationState.ts +31 -0
- package/src/presentation/components/LanguageItem.tsx +109 -0
- package/src/presentation/components/SearchInput.tsx +90 -0
- package/src/presentation/components/__tests__/LanguageItem.test.tsx +106 -0
- package/src/presentation/components/__tests__/SearchInput.test.tsx +95 -0
- package/src/presentation/screens/LanguageSelectionScreen.tsx +148 -0
- package/src/presentation/screens/__tests__/LanguageSelectionScreen.test.tsx +166 -0
- package/src/scripts/prepublish.ts +48 -0
- package/src/infrastructure/locales/en-US/alerts.json +0 -107
- package/src/infrastructure/locales/en-US/auth.json +0 -34
- package/src/infrastructure/locales/en-US/branding.json +0 -8
- package/src/infrastructure/locales/en-US/clipboard.json +0 -9
- package/src/infrastructure/locales/en-US/common.json +0 -57
- package/src/infrastructure/locales/en-US/datetime.json +0 -138
- package/src/infrastructure/locales/en-US/device.json +0 -14
- package/src/infrastructure/locales/en-US/editor.json +0 -64
- package/src/infrastructure/locales/en-US/errors.json +0 -41
- package/src/infrastructure/locales/en-US/general.json +0 -57
- package/src/infrastructure/locales/en-US/goals.json +0 -5
- package/src/infrastructure/locales/en-US/haptics.json +0 -6
- package/src/infrastructure/locales/en-US/home.json +0 -62
- package/src/infrastructure/locales/en-US/index.ts +0 -54
- package/src/infrastructure/locales/en-US/navigation.json +0 -6
- package/src/infrastructure/locales/en-US/onboarding.json +0 -26
- package/src/infrastructure/locales/en-US/projects.json +0 -34
- package/src/infrastructure/locales/en-US/settings.json +0 -45
- package/src/infrastructure/locales/en-US/sharing.json +0 -8
- package/src/infrastructure/locales/en-US/templates.json +0 -28
- package/src/infrastructure/scripts/createLocaleLoaders.js +0 -177
|
@@ -1,105 +1,114 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Localization Store
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Uses separate classes for initialization, switching, and translation
|
|
6
|
-
* Follows Single Responsibility Principle
|
|
2
|
+
* Localization Store Factory
|
|
3
|
+
* Creates and manages localization state with proper separation of concerns
|
|
7
4
|
*/
|
|
8
5
|
|
|
9
6
|
import { create } from 'zustand';
|
|
10
|
-
import {
|
|
7
|
+
import type { LocalizationState, LocalizationActions, LocalizationGetters, Language } from './types/LocalizationState';
|
|
11
8
|
import { LanguageInitializer } from './LanguageInitializer';
|
|
12
9
|
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
interface LocalizationState {
|
|
16
|
-
currentLanguage: string;
|
|
17
|
-
isRTL: boolean;
|
|
18
|
-
isInitialized: boolean;
|
|
19
|
-
supportedLanguages: Language[];
|
|
20
|
-
setLanguage: (languageCode: string) => Promise<void>;
|
|
21
|
-
initialize: () => Promise<void>;
|
|
22
|
-
}
|
|
10
|
+
import { languageRegistry } from '../config/languagesData';
|
|
23
11
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
isInitialized: false,
|
|
28
|
-
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Initialize localization system
|
|
32
|
-
*/
|
|
33
|
-
initialize: async () => {
|
|
34
|
-
// Prevent re-initialization
|
|
35
|
-
const { isInitialized: alreadyInitialized } = get();
|
|
36
|
-
if (alreadyInitialized) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const result = await LanguageInitializer.initialize();
|
|
42
|
-
|
|
43
|
-
set({
|
|
44
|
-
currentLanguage: result.languageCode,
|
|
45
|
-
isRTL: result.isRTL,
|
|
46
|
-
isInitialized: true,
|
|
47
|
-
});
|
|
48
|
-
} catch (error) {
|
|
49
|
-
// Set fallback state even on error
|
|
50
|
-
set({
|
|
51
|
-
currentLanguage: DEFAULT_LANGUAGE,
|
|
52
|
-
isRTL: false,
|
|
53
|
-
isInitialized: true,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Change language
|
|
60
|
-
*/
|
|
61
|
-
setLanguage: async (languageCode: string) => {
|
|
62
|
-
try {
|
|
63
|
-
const result = await LanguageSwitcher.switchLanguage(languageCode);
|
|
64
|
-
|
|
65
|
-
set({
|
|
66
|
-
currentLanguage: result.languageCode,
|
|
67
|
-
isRTL: result.isRTL,
|
|
68
|
-
});
|
|
69
|
-
} catch (error) {
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
}));
|
|
12
|
+
interface LocalizationStore extends LocalizationState, LocalizationActions, LocalizationGetters {
|
|
13
|
+
// Additional properties can be added here if needed
|
|
14
|
+
}
|
|
74
15
|
|
|
75
16
|
/**
|
|
76
|
-
*
|
|
77
|
-
* Provides current language, RTL state, language switching, and translation function
|
|
17
|
+
* Create localization store with proper dependency injection
|
|
78
18
|
*/
|
|
79
|
-
export const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
19
|
+
export const createLocalizationStore = () => {
|
|
20
|
+
return create<LocalizationStore>()(
|
|
21
|
+
(set, get) => ({
|
|
22
|
+
// State
|
|
23
|
+
currentLanguage: 'en-US',
|
|
24
|
+
isRTL: false,
|
|
25
|
+
isInitialized: false,
|
|
26
|
+
supportedLanguages: languageRegistry.getLanguages(),
|
|
27
|
+
|
|
28
|
+
// Actions
|
|
29
|
+
initialize: async () => {
|
|
30
|
+
const { isInitialized: alreadyInitialized } = get();
|
|
31
|
+
if (alreadyInitialized) {
|
|
32
|
+
if (__DEV__) {
|
|
33
|
+
console.log('[Localization] Already initialized');
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = await LanguageInitializer.initialize();
|
|
40
|
+
|
|
41
|
+
set({
|
|
42
|
+
currentLanguage: result.languageCode,
|
|
43
|
+
isRTL: result.isRTL,
|
|
44
|
+
isInitialized: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (__DEV__) {
|
|
48
|
+
console.log(`[Localization] Initialized with language: ${result.languageCode}`);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Set fallback state even on error
|
|
52
|
+
set({
|
|
53
|
+
currentLanguage: 'en-US',
|
|
54
|
+
isRTL: false,
|
|
55
|
+
isInitialized: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (__DEV__) {
|
|
59
|
+
console.error('[Localization] Initialization failed, using fallback:', error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
setLanguage: async (languageCode: string) => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await LanguageSwitcher.switchLanguage(languageCode);
|
|
67
|
+
|
|
68
|
+
set({
|
|
69
|
+
currentLanguage: result.languageCode,
|
|
70
|
+
isRTL: result.isRTL,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (__DEV__) {
|
|
74
|
+
console.log(`[Localization] Language changed to: ${result.languageCode}`);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (__DEV__) {
|
|
78
|
+
console.error('[Localization] Language change failed:', error);
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
reset: () => {
|
|
85
|
+
set({
|
|
86
|
+
currentLanguage: 'en-US',
|
|
87
|
+
isRTL: false,
|
|
88
|
+
isInitialized: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (__DEV__) {
|
|
92
|
+
console.log('[Localization] Store reset');
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
// Getters
|
|
97
|
+
getCurrentLanguage: () => {
|
|
98
|
+
const { currentLanguage } = get();
|
|
99
|
+
return languageRegistry.getLanguageByCode(currentLanguage);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
isLanguageSupported: (code: string) => {
|
|
103
|
+
return languageRegistry.isLanguageSupported(code);
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
getSupportedLanguages: () => {
|
|
107
|
+
return languageRegistry.getLanguages();
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
);
|
|
105
111
|
};
|
|
112
|
+
|
|
113
|
+
// Create singleton instance
|
|
114
|
+
export const useLocalizationStore = createLocalizationStore();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localization State Interface
|
|
3
|
+
* Defines the shape of localization state management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface Language {
|
|
7
|
+
code: string;
|
|
8
|
+
name: string;
|
|
9
|
+
nativeName: string;
|
|
10
|
+
flag?: string;
|
|
11
|
+
isRTL?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LocalizationState {
|
|
15
|
+
currentLanguage: string;
|
|
16
|
+
isRTL: boolean;
|
|
17
|
+
isInitialized: boolean;
|
|
18
|
+
supportedLanguages: Language[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LocalizationActions {
|
|
22
|
+
initialize: () => Promise<void>;
|
|
23
|
+
setLanguage: (languageCode: string) => Promise<void>;
|
|
24
|
+
reset: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LocalizationGetters {
|
|
28
|
+
getCurrentLanguage: () => Language | undefined;
|
|
29
|
+
isLanguageSupported: (code: string) => boolean;
|
|
30
|
+
getSupportedLanguages: () => Language[];
|
|
31
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Item Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a single language item in the language selection list
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
Text,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import type { Language } from '../../infrastructure/storage/types/LocalizationState';
|
|
15
|
+
|
|
16
|
+
interface LanguageItemProps {
|
|
17
|
+
item: Language;
|
|
18
|
+
isSelected: boolean;
|
|
19
|
+
onSelect: (code: string) => void;
|
|
20
|
+
customStyles?: {
|
|
21
|
+
languageItem?: any;
|
|
22
|
+
languageContent?: any;
|
|
23
|
+
languageText?: any;
|
|
24
|
+
flag?: any;
|
|
25
|
+
nativeName?: any;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const LanguageItem: React.FC<LanguageItemProps> = ({
|
|
30
|
+
item,
|
|
31
|
+
isSelected,
|
|
32
|
+
onSelect,
|
|
33
|
+
customStyles,
|
|
34
|
+
}) => {
|
|
35
|
+
return (
|
|
36
|
+
<TouchableOpacity
|
|
37
|
+
testID="language-item-test"
|
|
38
|
+
style={[
|
|
39
|
+
styles.languageItem,
|
|
40
|
+
customStyles?.languageItem,
|
|
41
|
+
isSelected && styles.selectedLanguageItem,
|
|
42
|
+
]}
|
|
43
|
+
onPress={() => onSelect(item.code)}
|
|
44
|
+
activeOpacity={0.7}
|
|
45
|
+
>
|
|
46
|
+
<View style={[styles.languageContent, customStyles?.languageContent]}>
|
|
47
|
+
<Text style={[styles.flag, customStyles?.flag]}>
|
|
48
|
+
{item.flag || '🌐'}
|
|
49
|
+
</Text>
|
|
50
|
+
<View style={[styles.languageText, customStyles?.languageText]}>
|
|
51
|
+
<Text style={[styles.nativeName, customStyles?.nativeName]}>
|
|
52
|
+
{item.nativeName}
|
|
53
|
+
</Text>
|
|
54
|
+
<Text style={[styles.languageName, customStyles?.nativeName]}>
|
|
55
|
+
{item.name}
|
|
56
|
+
</Text>
|
|
57
|
+
</View>
|
|
58
|
+
</View>
|
|
59
|
+
{isSelected && (
|
|
60
|
+
<Text style={[styles.checkIcon, customStyles?.flag]}>✓</Text>
|
|
61
|
+
)}
|
|
62
|
+
</TouchableOpacity>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const styles = StyleSheet.create({
|
|
67
|
+
languageItem: {
|
|
68
|
+
flexDirection: 'row',
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
justifyContent: 'space-between',
|
|
71
|
+
padding: 16,
|
|
72
|
+
borderRadius: 12,
|
|
73
|
+
borderWidth: 1,
|
|
74
|
+
borderColor: '#e0e0e0',
|
|
75
|
+
marginBottom: 8,
|
|
76
|
+
backgroundColor: '#fff',
|
|
77
|
+
},
|
|
78
|
+
selectedLanguageItem: {
|
|
79
|
+
borderColor: '#007AFF',
|
|
80
|
+
backgroundColor: '#f0f8ff',
|
|
81
|
+
},
|
|
82
|
+
languageContent: {
|
|
83
|
+
flexDirection: 'row',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
flex: 1,
|
|
86
|
+
},
|
|
87
|
+
flag: {
|
|
88
|
+
fontSize: 24,
|
|
89
|
+
marginRight: 16,
|
|
90
|
+
},
|
|
91
|
+
languageText: {
|
|
92
|
+
flex: 1,
|
|
93
|
+
},
|
|
94
|
+
nativeName: {
|
|
95
|
+
fontSize: 16,
|
|
96
|
+
fontWeight: '600',
|
|
97
|
+
color: '#333',
|
|
98
|
+
marginBottom: 2,
|
|
99
|
+
},
|
|
100
|
+
languageName: {
|
|
101
|
+
fontSize: 14,
|
|
102
|
+
color: '#666',
|
|
103
|
+
},
|
|
104
|
+
checkIcon: {
|
|
105
|
+
fontSize: 18,
|
|
106
|
+
color: '#007AFF',
|
|
107
|
+
fontWeight: 'bold',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Input Component
|
|
3
|
+
*
|
|
4
|
+
* Renders search input for language filtering
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
TextInput,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
Text,
|
|
13
|
+
StyleSheet,
|
|
14
|
+
} from 'react-native';
|
|
15
|
+
|
|
16
|
+
interface SearchInputProps {
|
|
17
|
+
value: string;
|
|
18
|
+
onChange: (value: string) => void;
|
|
19
|
+
placeholder: string;
|
|
20
|
+
customStyles?: {
|
|
21
|
+
searchContainer?: any;
|
|
22
|
+
searchInput?: any;
|
|
23
|
+
searchIcon?: any;
|
|
24
|
+
clearButton?: any;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const SearchInput: React.FC<SearchInputProps> = ({
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
placeholder,
|
|
32
|
+
customStyles,
|
|
33
|
+
}) => {
|
|
34
|
+
return (
|
|
35
|
+
<View style={[styles.searchContainer, customStyles?.searchContainer]}>
|
|
36
|
+
<Text style={[styles.searchIcon, customStyles?.searchIcon]}>🔍</Text>
|
|
37
|
+
<TextInput
|
|
38
|
+
style={[styles.searchInput, customStyles?.searchInput]}
|
|
39
|
+
placeholder={placeholder}
|
|
40
|
+
placeholderTextColor="#666"
|
|
41
|
+
value={value}
|
|
42
|
+
onChangeText={onChange}
|
|
43
|
+
autoCapitalize="none"
|
|
44
|
+
autoCorrect={false}
|
|
45
|
+
/>
|
|
46
|
+
{value.length > 0 && (
|
|
47
|
+
<TouchableOpacity
|
|
48
|
+
onPress={() => onChange('')}
|
|
49
|
+
style={[styles.clearButton, customStyles?.clearButton]}
|
|
50
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
51
|
+
>
|
|
52
|
+
<Text style={[styles.clearIcon, customStyles?.searchIcon]}>✕</Text>
|
|
53
|
+
</TouchableOpacity>
|
|
54
|
+
)}
|
|
55
|
+
</View>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const styles = StyleSheet.create({
|
|
60
|
+
searchContainer: {
|
|
61
|
+
flexDirection: 'row',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
marginHorizontal: 20,
|
|
64
|
+
marginBottom: 24,
|
|
65
|
+
paddingHorizontal: 16,
|
|
66
|
+
paddingVertical: 12,
|
|
67
|
+
backgroundColor: '#f5f5f5',
|
|
68
|
+
borderRadius: 12,
|
|
69
|
+
borderWidth: 1,
|
|
70
|
+
borderColor: '#e0e0e0',
|
|
71
|
+
},
|
|
72
|
+
searchIcon: {
|
|
73
|
+
marginRight: 12,
|
|
74
|
+
fontSize: 16,
|
|
75
|
+
},
|
|
76
|
+
searchInput: {
|
|
77
|
+
flex: 1,
|
|
78
|
+
fontSize: 16,
|
|
79
|
+
padding: 0,
|
|
80
|
+
fontWeight: '500',
|
|
81
|
+
color: '#333',
|
|
82
|
+
},
|
|
83
|
+
clearButton: {
|
|
84
|
+
padding: 4,
|
|
85
|
+
},
|
|
86
|
+
clearIcon: {
|
|
87
|
+
fontSize: 14,
|
|
88
|
+
color: '#666',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Item Component Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
7
|
+
import { LanguageItem } from '../LanguageItem';
|
|
8
|
+
import type { Language } from '../../infrastructure/storage/types/LocalizationState';
|
|
9
|
+
|
|
10
|
+
const mockLanguage: Language = {
|
|
11
|
+
code: 'en-US',
|
|
12
|
+
name: 'English',
|
|
13
|
+
nativeName: 'English',
|
|
14
|
+
flag: '🇺🇸',
|
|
15
|
+
isRTL: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('LanguageItem', () => {
|
|
19
|
+
const mockOnSelect = jest.fn();
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
mockOnSelect.mockClear();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should render language information correctly', () => {
|
|
26
|
+
const { getAllByText } = render(
|
|
27
|
+
<LanguageItem
|
|
28
|
+
item={mockLanguage}
|
|
29
|
+
isSelected={false}
|
|
30
|
+
onSelect={mockOnSelect}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(getAllByText('English')).toHaveLength(2); // nativeName and name
|
|
35
|
+
expect(getAllByText('🇺🇸')).toHaveLength(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should show check icon when selected', () => {
|
|
39
|
+
const { getByText } = render(
|
|
40
|
+
<LanguageItem
|
|
41
|
+
item={mockLanguage}
|
|
42
|
+
isSelected={true}
|
|
43
|
+
onSelect={mockOnSelect}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(getByText('✓')).toBeTruthy();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should not show check icon when not selected', () => {
|
|
51
|
+
const { queryByText } = render(
|
|
52
|
+
<LanguageItem
|
|
53
|
+
item={mockLanguage}
|
|
54
|
+
isSelected={false}
|
|
55
|
+
onSelect={mockOnSelect}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(queryByText('✓')).toBeFalsy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should call onSelect when pressed', () => {
|
|
63
|
+
const { getByTestId } = render(
|
|
64
|
+
<LanguageItem
|
|
65
|
+
item={mockLanguage}
|
|
66
|
+
isSelected={false}
|
|
67
|
+
onSelect={mockOnSelect}
|
|
68
|
+
testID="language-item-test"
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
fireEvent.press(getByTestId('language-item-test'));
|
|
73
|
+
expect(mockOnSelect).toHaveBeenCalledWith('en-US');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should use default flag when none provided', () => {
|
|
77
|
+
const languageWithoutFlag = { ...mockLanguage, flag: undefined };
|
|
78
|
+
const { getByText } = render(
|
|
79
|
+
<LanguageItem
|
|
80
|
+
item={languageWithoutFlag}
|
|
81
|
+
isSelected={false}
|
|
82
|
+
onSelect={mockOnSelect}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
expect(getByText('🌐')).toBeTruthy();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should apply custom styles', () => {
|
|
90
|
+
const customStyles = {
|
|
91
|
+
languageItem: { backgroundColor: 'red' },
|
|
92
|
+
flag: { fontSize: 30 },
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const { getByTestId } = render(
|
|
96
|
+
<LanguageItem
|
|
97
|
+
item={mockLanguage}
|
|
98
|
+
isSelected={false}
|
|
99
|
+
onSelect={mockOnSelect}
|
|
100
|
+
customStyles={customStyles}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(getByTestId('language-item-test')).toBeTruthy();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Input Component Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
7
|
+
import { SearchInput } from '../SearchInput';
|
|
8
|
+
|
|
9
|
+
describe('SearchInput', () => {
|
|
10
|
+
const mockOnChange = jest.fn();
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mockOnChange.mockClear();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should render with placeholder', () => {
|
|
17
|
+
const { getByPlaceholderText } = render(
|
|
18
|
+
<SearchInput
|
|
19
|
+
value=""
|
|
20
|
+
onChange={mockOnChange}
|
|
21
|
+
placeholder="Search languages..."
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(getByPlaceholderText('Search languages...')).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should call onChange when text changes', () => {
|
|
29
|
+
const { getByPlaceholderText } = render(
|
|
30
|
+
<SearchInput
|
|
31
|
+
value=""
|
|
32
|
+
onChange={mockOnChange}
|
|
33
|
+
placeholder="Search languages..."
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
fireEvent.changeText(getByPlaceholderText('Search languages...'), 'test');
|
|
38
|
+
expect(mockOnChange).toHaveBeenCalledWith('test');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should show clear button when text is present', () => {
|
|
42
|
+
const { getByText } = render(
|
|
43
|
+
<SearchInput
|
|
44
|
+
value="test"
|
|
45
|
+
onChange={mockOnChange}
|
|
46
|
+
placeholder="Search languages..."
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(getByText('✕')).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should not show clear button when text is empty', () => {
|
|
54
|
+
const { queryByText } = render(
|
|
55
|
+
<SearchInput
|
|
56
|
+
value=""
|
|
57
|
+
onChange={mockOnChange}
|
|
58
|
+
placeholder="Search languages..."
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(queryByText('✕')).toBeFalsy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should clear text when clear button is pressed', () => {
|
|
66
|
+
const { getByText } = render(
|
|
67
|
+
<SearchInput
|
|
68
|
+
value="test"
|
|
69
|
+
onChange={mockOnChange}
|
|
70
|
+
placeholder="Search languages..."
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
fireEvent.press(getByText('✕'));
|
|
75
|
+
expect(mockOnChange).toHaveBeenCalledWith('');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should apply custom styles', () => {
|
|
79
|
+
const customStyles = {
|
|
80
|
+
searchContainer: { backgroundColor: 'red' },
|
|
81
|
+
searchInput: { fontSize: 20 },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const { getByPlaceholderText } = render(
|
|
85
|
+
<SearchInput
|
|
86
|
+
value=""
|
|
87
|
+
onChange={mockOnChange}
|
|
88
|
+
placeholder="Search languages..."
|
|
89
|
+
customStyles={customStyles}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(getByPlaceholderText('Search languages...')).toBeTruthy();
|
|
94
|
+
});
|
|
95
|
+
});
|