React native #5Navigation react testing library - react-native

I am new to React Native Testing Library. For my React native app I am using styled components and Typescript. I fetched the data and pass to my flatList. In Flat-list's render item I have created one global component where it display all the data which is wrap with one touchable container. When user will click the that touchable opacity it will go to single product details screen.
For testing the component I have created one mock container. And I wrap my touchable opacity component. I followed this article to create mocked navigator. I want to test the touchable opacity component and it navigate to the next screen. But I am getting error and it says:
The action 'NAVIGATE' with payload
{"name":"ProductDetails","params":{"product":{"__typename":"Product","id":"6414893391048","ean":"6414893391048","name":"T
shirt","brandName":"Denim","price":13.79 }} was not handled by any
navigator.
This my component
const navigation = useNavigation();
const onPress = () => {
trackProductView({
id: item.id ,
name: item.name,
});
navigation.navigate(Routes.ProductDetails, { product: item });
};
return (
<TouchableOpacity
accessible={true}
{...a11y({
role: 'menuitem',
label: item.name,
})}
onPress={onPress} // This is my onPress function
>
<ItemContainer>
<ProductTitleText ellipsizeMode={'tail'} numberOfLines={2}>
{item.name}
</ProductTitleText>
<QuantityModifierWrapper>
<QuantityModifier item={item!} />
</QuantityModifierWrapper>
</ItemContainer>
</TouchableOpacity>
);
This is my mocked container
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import 'react-native-gesture-handler';
import { MockedProvider } from '#apollo/client/testing';
type Props = {
screen: any;
params?: any;
};
const Stack = createStackNavigator();
const MockedNavigator = ({ screen, params = {} }: Props) => {
return (
<MockedProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name='MockedScreen'
component={screen}
initialParams={params}
/>
</Stack.Navigator>
</NavigationContainer>
</MockedProvider>
);
};
export default MockedNavigator;
This is my mocked screen
import React from 'react';
import { View } from 'react-native';
type Props = {
children: React.ReactNode;
};
const MockedScreen = ({ children }: Props) => {
return <View>{children}</View>;
};
export default MockedScreen;
This is my test suite where I am getting failed test
import React from 'react';
import { fireEvent, render, cleanup } from 'skm/utils/testing_utils';
import Touchablecomponent from './Touchable';
import MockedNavigator from './MockNav';
import MockedScreen from './Mockscreen';
describe('<Touchablecomponent/> ', () => {
test("render with invalid data", async () => {
const screenName = 'ProductDetails';
const component = (
<MockedNavigator
screen={() => (
<MockedScreen>
<ProductItemSmall item={mockData} />
</MockedScreen>
)}
// params={{data: mockData }}
/>
);
const { getByA11yRole, debug, toJSON } = render(component);
const item = getByA11yRole('menuitem');
console.log(fireEvent.press(item));
});
})

Related

Why useEffect is triggering without dependency change?

I wanted only when I setCart to trigger useEffect. But this is not happening:
import React from 'react'
import { View } from 'react-native'
const CartScreen = () => {
const [cart, setCart] = React.useState([])
React.useEffect(() => {
console.log('test');
}, [cart])
return (
<View>
</View>
)
}
export default CartScreen;
Output: test
it fires without even having touched the cart state
useEffect will always run the first time when your component is rendered. If you only want some code to run after you change the state you can just have an if statement to check that
import React from 'react'
import { View } from 'react-native'
const CartScreen = () => {
const [cart, setCart] = React.useState([])
React.useEffect(() => {
if(cart.length > 0)
console.log('test')
}, [cart])
return (
<View>
</View>
)
}
export default CartScreen;

React navigation. Render extra views on top of navigator

I'm trying to create a React Native app that renders an area with interchangeable screens and fixed view with some additional data/menu. I tried this solution form official React Navigation, but I can't make it work.
import React, { Component } from "react";
import {createStackNavigator} from 'react-navigation';
import { Text, View,} from 'react-native';
import Details from './DetailsScreen';
import MainScreen from './MainScreen';
const RootStack = createStackNavigator({
Main: {screen: MainScreen,
navigationOptions: {
header: null
}
},
Details: {
screen: Details,
},);
class App extends Component {
static router = {
...RootStack.router,
getStateForAction: (action, lastState) => {
return MyStack.router.getStateForAction(action, lastState);
}
};
render() {
const { navigation } = this.props;
return(
<View>
<Text>Foo</Text> //this is rendering
<RootStack navigation={navigation}/> //this is not
</View>);
}
}
export default App;
Article form link suggests that I can wrap object created with createStackNavigator() in a parent View, but it renders only when it is the only thing returned from render(), otherwise it seems to be ignored. Example from link claims that it is possible to "render additional things", but I can't make it work. Is it possible to do this way?
You can try this:
//wrapper.js
function wrapNavigator(Navigator, Wrapper, wrapperProps = {}) {
const WrappedComponent = props => (
<Wrapper { ...props, ...wrapperProps}>
<Navigator {...props} />
</Wrapper>
);
WrappedComponent.router = Navigator.router;
WrappedComponent.navigationOptions = Navigator.navigationOptions;
return WrappedComponent;
};
//app.js
const App = (props) => <View>
<Text>Some text...</Text>
{children}
</View>
export default wrapNavigator(Stack, App);
But this will not work if there are no parent navigator for App component.

Passing props to a component using StackNavigator in React Native

import React, {Component} from 'react'
import { StackNavigator } from 'react-navigation'
import {connect} from 'react-redux'
import {getAllUsers} from '../actions'
import {List, ListItem} from 'react-native-elements'
import { StyleSheet, Text, View, FlatList } from 'react-native'
const UserDetail = () => {
<View>
<Text>User Detail</Text>
</View>
}
const Home = ({ navigation }) => (
<List>
{typeof users === 'string' &&
<FlatList
data={users}
/>
}
</List>
)
const Stack = StackNavigator({
Home: {
screen: Home
},
UserDetail: {
screen: UserDetail
}
})
class MainScreen extends Component {
componentDidMount(){
this.setState({ users : this.props.getAllUsers() })
}
render() {
const users = typeof this.props.decks === 'string'
? Object.values(JSON.parse(this.props.users)) : ''
return(
<Stack />
)
}
}
function mapStateToProps(users) {
return {
users: users,
}
}
function mapDispatchToProps(dispatch) {
return {
getAllUsers: () => dispatch(getAllUsers()),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MainScreen)
I have two questions:
1) I want to pass the users props from MainScreen component to Home component and later on also to UserDetail component. But I could not see any example on that on React Navigation documentation except for ScreenProps.
2) If I go to UserDetail and let's say it would be a separate component. And let's say it would call another component. How could it go back to Home for example?
I'm using Redux by the way also here and there are some who suggest to do it in Redux but still I could not make it work also. I am fairly new to React Native or React for that matter, but I could not find a definitive answer so far on this above questions.

can react-native-root-siblings work with react-redux

in a handleClick function, update the rootSiblings like this,
handleClick() { this.progressBar.update( <ProgressBar /> ); }
and in ProgressBar component,
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
const getFinishedWidth = progress => ({ width: progress * totalWidth });
const getUnfinishedWidth = progress => ({ width: (1 - progress) * totalWidth });
function CustomerReassignProgressBar(props) {
const { progress } = props;
return (
<View style={styles.bar}>
<View style={getFinishedWidth(progress)} />
<View style={getUnfinishedWidth(progress)} />
</View> );
}
CustomerReassignProgressBar.propTypes = { progress: PropTypes.number, };
const mapStateToProps = state => ({ progress: state.batchReassignProgress, });
export default connect(mapStateToProps)(ProgressBar);
then, when calling handleClick(), the app crushed, the error is, 'Could not find "store" in either the context or props of "Connect(ProgressBar)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ProgressBar)".'
if I don't use connect in component, it works well. So, I guess, maybe rootSiblings can not work with react-redux. But does anyone knows this problem?
Upgrade to react-native-root-siblings#4.x
Then
import { setSiblingWrapper } from 'react-native-root-siblings';
import { Provider } from 'react-redux';
const store = xxx;// get your redux store here
// call this before using any root-siblings related code
setSiblingWrapper(sibling => (
<Provider store={store}>{sibling}</Provider>
));

Testing component that uses react-navigation with Jest

I'm working on a React Native application that also use Redux and I want to write tests with Jest. I'm not able to mock the "navigation" prop that is added by react-navigation.
Here is my component:
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Text, View } from 'react-native';
const Loading = (props) => {
if (props.rehydrated === true) {
const { navigate } = props.navigation;
navigate('Main');
}
return (
<View>
<Text>Loading...</Text>
</View>
);
};
Loading.propTypes = {
rehydrated: PropTypes.bool.isRequired,
navigation: PropTypes.shape({
navigate: PropTypes.func.isRequired,
}).isRequired,
};
const mapStateToProps = state => ({
rehydrated: state.rehydrated,
});
export default connect(mapStateToProps)(Loading);
The Loading component is added as a screen to a DrawerNavigator.
And here is the test:
import React from 'react';
import renderer from 'react-test-renderer';
import mockStore from 'redux-mock-store';
import Loading from '../';
describe('Loading screen', () => {
it('should display loading text if not rehydrated', () => {
const store = mockStore({
rehydrated: false,
navigation: { navigate: jest.fn() },
});
expect(renderer.create(<Loading store={store} />)).toMatchSnapshot();
});
});
When I run the test, I get the following error:
Warning: Failed prop type: The prop `navigation` is marked as required in `Loading`, but its value is `undefined`.
in Loading (created by Connect(Loading))
in Connect(Loading)
Any idea on how to mock the navigation property?
Try to pass navigation directly via props:
it('should display loading text if not rehydrated', () => {
const store = mockStore({
rehydrated: false,
});
const navigation = { navigate: jest.fn() };
expect(renderer.create(<Loading store={store} navigation={navigation} />)).toMatchSnapshot();
});