How can I check value of the refreshControl prop in jest for my react-native app? - react-native

I am trying to do a jest unit test which simply confirms that the refreshControl prop is active in my ScrollView.
I currently have a component that essentially renders the following
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
/>
}
{children}
/>
It works fine in practice, and in my test, I am checking to confirm that the prop is as expected.
import { RefreshControl } from 'react-native'
const layout = shallow(<Element />)
const refreshControl = <RefreshControl
onRefresh={jest.fn()}
refreshing={false}
/>
expect(layout.prop('refreshControl')).toEqual(refreshControl)
I then get this error when running my test:
Expected value to equal:
<RefreshControlMock onRefresh={[Function mockConstructor]} refreshing={false} />
Received:
<RefreshControlMock onRefresh={[Function mockConstructor]} refreshing={false} />
I assume it's because the instance of the onRefresh function differs, but not sure how to control that. Does anyone perhaps know?

Please find below steps to call Pull to Refresh to cover coverage.
First, get access to the scrollView component through testID
Fetch refresh control present in the scroll view component
call onRefresh from test case
I have added the below Example for reference.
test('refresh control', async () => {
const props = createTestProps();
const component = render(
<Provider store={store}>
<ScrollViewComponent {...props} />
</Provider>
);
const scrollView = component.getByTestId('refreshControl');
expect(scrollView).toBeDefined();
const { refreshControl } = scrollView.props;
await act(async () => {
refreshControl.props.onRefresh();
});
});

Just realised that I need to check that the function is the same one I pass into the component, in the test, so basically, the function in the component that matches.
this._onRefresh

MW UMESH's answer helped me quite a lot (and I therefore upvoted it) but since you get the refreshControl prop synchronously, you can remove the async test here.
So replace
await act(async () => {
refreshControl.props.onRefresh();
});
With
refreshControl.props.onRefresh();
And the reload mechanism will trigger.

Related

Getting error while using useHeaderHeight() in unit-testing

Using #react-navigation/stack library in my project, while doing unit testing (using jest) I am getting this error
Couldn't find the header height. Are you inside a screen in Stack?
index.spec.js
it('should have a title and subtitle', () => {
const { getByText } = render(<Otp {...props} />); });
Otp component
<SafeAreaView style={styles.mainContainer}>
<KeyboardAvoidingView
keyboardVerticalOffset={useHeaderHeight()} --> getting error at this line
>
<Text>...</Text>
</KeyboardAvoidingView>
</SafeAreaView>
Also tried a solution in which I mocked a navigator but got no success.
I came across the same issue today. What worked for me was to mock the entire module to return an object with useHederHeight. I think this is fine for unit testing and if you need to mock extra features you can do that too.
jest.mock('#react-navigation/elements', () => ({
useHeaderHeight: jest.fn().mockImplementation(() => 200)
}))

React native flatlist rerender

I'm working on a flatlist that has complex child that cause expensive rerender, I need to optimize that but I'm not able to stop the rerendering with useMemo, please help me to go through this.
Here my list code:
<FlatList
data={thePosts}
extraData={thePosts}
keyExtractor={(item, index) => index.toString()}
removeClippedSubviews={true}
maxToRenderPerBatch={5}
updateCellsBatchingPeriod={30}
initialNumToRender={11}
windowSize={5}
refreshing={isRefreshing}
onRefresh={handleOnRefresh}
onEndReached={isLoading ? () => null : () => getPosts("more")}
onEndReachedThreshold={0.1}
renderItem={memoizedPost}
//renderItem={renderThePost}
ItemSeparatorComponent={renderThePostSep}
ListFooterComponent={renderThePostListFooter}
/>
here the renderPost:
const renderThePost = (props) => {
let post = props.item;
if (post[0].type == "share") {
return (
<TheSharedPost thePost={post} />
);
} else {
return <ThePost thePost={post} />;
}
};
I've tried to use memoization like this:
const memoizedPost = useMemo(() => renderThePost, []);
Now the problem is, the empty array as useMemo argument I think that only accept the first render but not working, I've tried to use [item.someProperty] but I'm not able to recognize item in the argument (item is not defined)
I've also used useCallback but still no luck, a lot o rerendering happen. Please help me to fix this. Tnz
you can use React.memo to avoid rendering of flatlist items
function TheSharedPost(props) {
/* render using props */
}
export default React.memo(TheSharedPost);
function ThePost(props) {
/* render using props */
}
export default React.memo(ThePost);

Trouble updating Picker component from Async Storage in React Native

I'm using a Picker component to let the user set a value for how frequently they want to be reminded about something. In the code below, I'm saving the result in the component's state as well as saving it to the device with Async Storage:
const [frequency, setFrequency] = useState('year');
...
<Picker
selectedValue={frequency}
style={styles.picker}
itemStyle={styles.pickerItem}
onValueChange={(itemValue, itemIndex) => {
(async () => {
await AsyncStorage.setItem(`#circle_${circle.name}_frequency`, itemValue)
})();
setFrequency(itemValue);
}}
mode={'dropdown'}
prompt={'Reminder every:'}
>
<Picker.Item label="Never" value="never" />
<Picker.Item label="Day" value="day" />
<Picker.Item label="Week" value="week" />
<Picker.Item label="Year" value="year" />
etc...
</Picker>
I'd also like to have the component grab the saved data and set that as the state when first rendering.
useEffect(() => {
const fetchFrequency = async () => {
let storedFrequency = await AsyncStorage.getItem(`#circle_${circle.name}_frequency`);
if (storedFrequency != null) {
setFrequency(storedFrequency);
};
}
fetchFrequency();
}, []);
Based on the limited amount I know about Async Storage, this seems to make sense. I think it's
awaiting the result of grabbing the value from storage
setting the state
rendering the component (this could be happening before setting state as well, but I figure it would render again when the state changes)
updating both storage and state when the user chooses a new option
However, this doesn't work. If I navigate away and then back to the page, the state has been reset.
UPDATE:
If I console.log the itemValue in the onValueChange async function this is what I get:
onValueChange={(itemValue, itemIndex) => {
(async () => {
await AsyncStorage.setItem(`#circle_${circle.name}_frequency`, itemValue)
console.log(itemValue)
})();
setFrequency(itemValue);
}}
When changing the value to 'never', it prints
never
never
When I navigate away and then come back, without even touching the compnent it prints out:
week
week
never
never
year
year
or
year
never
year
year
or some other long string of values which shows that there's a feedback loop going on somewhere.
Your expression AsyncStorage.setItem() is not firing because you forget to invoke Self-Invoking Functions inside the callback function of onValueChange.
onValueChange={(itemValue, itemIndex) => {
(async () => {
await AsyncStorage.setItem(`#circle_${circle.name}_frequency`, itemValue)
})(); // I will invoke myself
setFrequency(itemValue);
}}
UPDATED (following the updated question):
I didn't spot any more bugs on your given snippet code and I don't know what's going on with your full source code. Anyway, I have created a super simple working snippet code following by the code in your question, so you can just copy into your project.
import React, {useState, useEffect} from 'react';
import {Picker, AsyncStorage} from 'react-native';
export default function App() {
const [frequency, setFrequency] = useState('year');
useEffect(() => {
const fetchFrequency = async () => {
let storedFrequency = await AsyncStorage.getItem('#circle_circle_name_frequency');
if (storedFrequency != null) {
setFrequency(storedFrequency);
}
};
fetchFrequency();
}, []);
return (
<Picker
selectedValue={frequency}
onValueChange={(itemValue, itemIndex) => {
(async () => {
await AsyncStorage.setItem('#circle_circle_name_frequency', itemValue);
})();
setFrequency(itemValue);
}}
mode={'dropdown'}
prompt={'Reminder every:'}>
<Picker.Item label="Never" value="never" />
<Picker.Item label="Day" value="day" />
<Picker.Item label="Week" value="week" />
<Picker.Item label="Year" value="year" />
</Picker>
);
}
Hope this can help!
PS: I see you put expo tag on your question and I just wanna remind that, if you preview the project on the web browser, your storedFrequency inside useEffect will always be null because the browser doesn't support AsyncStorage.
It looks like the problem was an issue with the Picker itself and how it calls onValueChange every render rather than only when changed. I found a temporary solution in this thread for until it gets fixed: https://github.com/lawnstarter/react-native-picker-select/issues/112#issuecomment-634038287

Testing value change of TextInput in React Native with Jest

I have a TextInput component, which calls a custom function when its value changes through its onValueChange property.
Is it possible to simulate the change of the text of this TexInput with Jest, so that I test if that the custom function (which is passed as a prop) is called with correct parameters and produces correct results?
Yes its possible with jest and enzyme. Below is the definition of SearchBar component which receives a function as a prop. The function is called with the text typed.
const SearchBar = ({onSearch}) => {
return (
<View>
<TextInput
onChangeText={text => onSearch(text)}
/>
</View>
);
};
The testing can be carried as follows
const onSearch = jest.fn();
const wrapper = shallow(<SearchBar onSearch={onSearch} />);
wrapper.find('TextInput').simulate('changeText', 'test search text');
expect(onSearch).toHaveBeenCalledWith('test search text');

Opening context menu on long press in React Native

I'd like to have a context menu triggered on long press different places using React Native.
I.e. in a dialer like the default dailer. You can long-click on any contact and get a 'copy number' menu. And also you can long-click on the name of the person once you've opened their 'contact card'.
The straight-forward way needs a lot of copy-pasted boilerplate, both components and handlers.
Is there a better pattern for doing this?
All Touchable components (TouchableWithoutFeedback, TouchableOpacity etc.) has a property called onLongPress. You can use this prop to listen for long presses and then show the context menu.
To eliminate code mess and doing lots of copy paste you can separate your context menu as a different component and call it when the long press happen. You can also use an ActionSheet library to show the desired options. React native has a native API for iOS called ActionSheetIOS. If you get a little bit more experience in react and react-native you can create a better logic for this but I'm going to try to give you an example below.
// file/that/contains/globally/used/functions.js
const openContextMenu = (event, user, callback) => {
ActionSheetIOS.showActionSheetWithOptions({
options: ['Copy Username', 'Call User', 'Add to favorites', 'Cancel'],
cancelButtonIndex: [3],
title: 'Hey',
message : 'What do you want to do now?'
}, (buttonIndexThatSelected) => {
// Do something with result
if(callback && typeof callback === 'function') callback();
});
};
export openContextMenu;
import { openContextMenu } from './file/that/contains/globally/used/functions';
export default class UserCard extends React.Component {
render() {
const { userObject } = this.props;
return(
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done')}>
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done'))}>
<Text>{userObject.name}</Text>
<Image source={{uri: userObject.profilePic }} />
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
);
}
}
Similarly as the previous answer combine onLongPress with imperative control for popup menu - something like
<TouchableWithoutFeedback onLongPress={()=>this.menu.open()}>
<View style={styles.card}>
<Text>My first contact name</Text>
<Menu ref={c => (this.menu = c)}>
<MenuTrigger text="..." />
<MenuOptions>
// ...
</MenuOptions>
</Menu>
</View>
</TouchableWithoutFeedback>
When it comes to a lot of boilerplate - in React you can do your own components that you can reuse everywhere thus reducing boilerplate (and copy&paste)
See full example on https://snack.expo.io/rJ5LBM-TZ