Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __tests__/__snapshots__/App-test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ exports[`jest snapshot tests renders correctly 1`] = `
"backgroundColor": "#ffffff",
}
}
testID="homeScreen"
>
<View>
<View
Expand Down
5 changes: 5 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ apply plugin: "com.android.application"

import com.android.build.OutputFile

project.ext.vectoricons = [
iconFontNames: ['Feather.ttf'] // Name of the font files you want to copy
]
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
Expand Down
27 changes: 27 additions & 0 deletions app/components/CountriesAutocomplete/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import {Country} from 'countries-list';

import Text from 'theme/Text';
import TouchFeedback from 'theme/TouchFeedback';

import style from './style';

type ListItemProps = {
data: Country;
onPress: (...args: unknown[]) => void;
testID: string;
};

const ListItem: React.FC<ListItemProps> = props => (
<TouchFeedback
style={style.listItemContainer}
testID={props.testID}
onPress={props.onPress}>
<Text style={style.listItemName}>{props.data.emoji}</Text>
<Text style={style.listItemName}>
({props.data.phone}) {props.data.name}
</Text>
</TouchFeedback>
);

export default ListItem;
78 changes: 78 additions & 0 deletions app/components/CountriesAutocomplete/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
*
* CountriesAutocomplete
*
*/

import sortBy from 'lodash/sortBy';

import React from 'react';
import {FlatList, Text, View} from 'react-native';
import {countries, Country} from 'countries-list';

import Input from 'theme/Input';
import useAutocomplete from 'theme/Autocomplete';

import ListItem from './ListItem';
import style from './style';

interface ICountriesAutocompleteProps {
onSelect: (item: Country) => void;
testID?: string;
}

const countriesList: Country[] = Object.keys(countries).map(
key => countries[key],
);

const CountriesAutocomplete: React.FC<ICountriesAutocompleteProps> = props => {
const autocomplete = useAutocomplete({
data: countriesList,
filterKey: 'name',
});

return (
<>
<View
style={style.autocompleteContainer}
testID={props.testID || 'countriesAutocomplete'}>
<Input
{...autocomplete.inputProps}
placeholder="Search Countries"
clearButtonMode="never"
style={style.input}
autoFocus
testID="countriesAutocompleteInput"
/>
</View>
{autocomplete.focus && autocomplete.data ? (
<FlatList
testID="countriesAutocompleteList"
style={style.listContainer}
data={sortBy(autocomplete.data, 'name').slice(0, 5)}
bounces={false}
keyboardShouldPersistTaps="always"
keyboardDismissMode="on-drag"
keyExtractor={({name}) => name}
renderItem={({item}: {item: Country; index: number}) => (
<ListItem
onPress={() => {
props.onSelect(item);
autocomplete.clear();
}}
data={item}
testID={`listItem-${item.name}`}
/>
)}
ListEmptyComponent={
autocomplete.value ? (
<Text>No result for {autocomplete.value}.</Text>
) : null
}
/>
) : null}
</>
);
};

export default CountriesAutocomplete;
61 changes: 61 additions & 0 deletions app/components/CountriesAutocomplete/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {StyleSheet} from 'react-native';
import Colors from 'theme/Colors';
import Dimensions from 'theme/Dimensions';

const style = StyleSheet.create({
selectedItemContainer: {
margin: Dimensions.space2x,
marginTop: Dimensions.space1x,
flexDirection: 'row',
},
autocompleteContainer: {
flexDirection: 'row',
alignItems: 'center',
position: 'relative',
paddingHorizontal: Dimensions.space2x,
},
input: {
marginHorizontal: 0,
opacity: 1,
width: '100%',
},
inputCloseBtnHolder: {
backgroundColor: Colors.translucentBlackMinor,
height: 36,
width: 36,
borderRadius: 20,
position: 'absolute',
right: 16,
},
inputCloseBtn: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
noResult: {
flex: 1,
textAlign: 'center',
paddingVertical: Dimensions.space1x,
},
listContainer: {
zIndex: 1000,
width: '100%',
paddingHorizontal: Dimensions.space2x,
},
listItemContainer: {
flexDirection: 'row',
padding: Dimensions.space1x,
paddingVertical: Dimensions.space2x,
alignItems: 'center',
width: '100%',
backgroundColor: Colors.white,
marginVertical: Dimensions.space1x,
},
listItemName: {
fontSize: 14,
marginLeft: 8,
fontWeight: '500',
},
});

export default style;
66 changes: 66 additions & 0 deletions app/components/CountriesAutocomplete/tests/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'react-native';
import React from 'react';
import {render, fireEvent} from '@testing-library/react-native';

import CountriesAutocomplete from '../index';

const COUNTRY_NAME = 'Serbia';

// Describing a test suite
describe('<CountriesAutocomplete />', () => {
// Describing our test
it('Displays Searched Item', async () => {
// Mocking onPress method so we can check if its called or not
const onSelect = jest.fn();

// Rendering Button component using react-native-test-renderer.
const autocomplete = await render(
<CountriesAutocomplete onSelect={onSelect} />,
);

// Grabbing our input to perform actions on it.
const inputTestID = 'countriesAutocompleteInput';
const textInput = autocomplete.getByTestId(inputTestID);

/**
* RNTL gives us API to fire events on node
* Here we are firing on changeText event
*/
fireEvent(textInput, 'focus');
fireEvent.changeText(textInput, COUNTRY_NAME);
expect(textInput.props.value).toBe(COUNTRY_NAME);

// Grabbing our input to perform actions on it.
const listItemTestID = `listItem-${COUNTRY_NAME}`;
const firstListItem = autocomplete.getByTestId(listItemTestID);
expect(firstListItem).toBeTruthy();
});

it('onSelect is called when item is pressed', async () => {
// Mocking onPress method so we can check if its called or not
const onSelect = jest.fn();

// Rendering Button component using react-native-test-renderer.
const {getByTestId} = await render(
<CountriesAutocomplete onSelect={onSelect} />,
);

// Grabbing our input to perform actions on it.
const inputTestID = 'countriesAutocompleteInput';
const textInput = getByTestId(inputTestID);

/**
* RNTL gives us API to fire events on node
* Here we are firing on focus & changeText event
*/
fireEvent(textInput, 'focus');
fireEvent.changeText(textInput, COUNTRY_NAME);

// Grabbing our input to perform actions on it.
const listItemTestID = `listItem-${COUNTRY_NAME}`;
const firstListItem = getByTestId(listItemTestID);
fireEvent.press(firstListItem);

expect(onSelect).toHaveBeenCalledTimes(1);
});
});
26 changes: 26 additions & 0 deletions app/platform/LocalStorage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AsyncStorage from '@react-native-async-storage/async-storage';

class LocalStorage {
setItem(key: string, data: unknown) {
return AsyncStorage.setItem(key, JSON.stringify(data));
}

async getItem(key: string) {
const data = await AsyncStorage.getItem(key);
try {
return JSON.parse(data || '');
} catch (e) {
return data;
}
}

mergeItem(key, data) {
AsyncStorage.mergeItem(key, JSON.stringify(data));
}

removeItem(key) {
AsyncStorage.removeItem(key);
}
}

export default new LocalStorage();
2 changes: 1 addition & 1 deletion app/router/routeConfigs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HOME, SEARCH } from './routeNames';
import {HOME, SEARCH} from './routeNames';

const routeConfigs = {
[SEARCH]: {
Expand Down
4 changes: 2 additions & 2 deletions app/router/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type RootStackParamList = {
Home: {};
Search: {};
Home?: Record<string, unknown>;
Search?: Record<string, unknown>;
};
7 changes: 0 additions & 7 deletions app/screens/HomeScreen/Loadable.ts

This file was deleted.

10 changes: 8 additions & 2 deletions app/screens/HomeScreen/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/**
*
* Home Screen
*
*/

import React, {useState} from 'react';
// import Text from 'theme/Text';
import {ScrollView, Text, View, Switch} from 'react-native';

import {
Expand All @@ -23,7 +28,8 @@ function HomeScreen(props: HomeScreenProps): React.ReactChild {
return (
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
style={styles.scrollView}
testID="homeScreen">
<Header />
{global.HermesInternal == null ? null : (
<View style={styles.engine}>
Expand Down
2 changes: 1 addition & 1 deletion app/screens/HomeScreen/style.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StyleSheet } from 'react-native';
import {StyleSheet} from 'react-native';
import Colors from 'theme/Colors';
import Dimensions from 'theme/Dimensions';

Expand Down
6 changes: 3 additions & 3 deletions app/screens/HomeScreen/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from 'router/types';
import {RouteProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import {RootStackParamList} from 'router/types';

type HomeScreenRouteProp = RouteProp<RootStackParamList, 'Home'>;

Expand Down
7 changes: 0 additions & 7 deletions app/screens/SearchScreen/Loadable.ts

This file was deleted.

Loading