Testing react-native-testing-library with react-navigation v5 - react-native

I'm looking to test react-navigation v5 with react-native-testing-library. The documentation says to do the following.
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { render, fireEvent } from '#testing-library/react-native';
describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
const { findByText, findAllByText } = render(component);
const header = await findByText('List of numbers from 1 to 20');
const items = await findAllByText(/Item number/);
expect(header).toBeTruthy();
expect(items.length).toBe(10);
});
});
https://callstack.github.io/react-native-testing-library/docs/react-navigation
https://medium.com/#dariaruckaolszaska/testing-your-react-navigation-5-hooks-b8b8f745e5b6
This medium article suggests I create a MockedNavigator component. would this be a mockedNavigator component for all of my screens? Is this reusable?
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
const Stack = createStackNavigator();
const MockedNavigator = ({component, params = {}}) => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="MockedScreen"
component={component}
initialParams={params}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default MockedNavigator;
The answer I'm looking for is. Am I to use the real AppNavigator component within all my unit tests or am I to use a MockedAppNavigator?
Furthermore, how am I to pass props?
The documentation is unclear and I am looking for clarity. Many of the new components we are working with use hooks and the react-navigation-v5 is unable to access certain props.

There are two recommended approaches for RNTL tests involving React Navigation.
Either you test the same navigators you use in your app. This does not have to be the whole AppNavigator if your tests contain nested navigators. It can be only the one you want to test.
Or you test single screen not connected to any navigator. This would probably require refactor of that screen into the React Navigation-aware XxxScreen that would not be tested and XxxScreenContent that would receive relevant props from XxxScreen but would be indenpendend of XxxScreen.
You can find more details in the official React Navigation example in RNTL repo.
The mocked navigator approach is possible but not really advised as it's mocking too much, as such tests frequently have to rely on implementation details not visible to the users like route names, navigator nesting, etc.

Related

Cannot create custom StackNavigator - "Couldn't register the navigator"?

I'm trying to create a custom StackNavigator and distribute it on a custom package. The problem is that when I want to apply my custom Stack I get the following error:
Error: Couldn't register the navigator. Have you wrapped your app with
'NavigationContainer'?
This is my App.tsx:
import { createThemedStack } from '#my-custom-package'
//this is the reference to my custom navigator
const ThemedStack = createThemedStack()
function App() {
return (
<NavigationContainer>
<ThemedStack.Navigator>
<ThemedStack.Screen name='Screen 1' component={Screen1} />
<ThemedStack.Screen name='Screen 2' component={Screen2} />
...
</ThemedStack.Navigator>
</NavigationContainer>
)
}
export default App
ThemedStack.tsx - Here I want to apply some custom common styles to the stacks:
import * as React from 'react'
import { useNavigationBuilder, createNavigatorFactory, StackRouter } from '#react-navigation/native'
import { StackView } from '#react-navigation/stack'
// #ts-ignore
function ThemedStack({ initialRouteName, children, screenOptions, ...rest }) {
const { state, descriptors, navigation } = useNavigationBuilder(StackRouter, {
initialRouteName,
children,
screenOptions,
})
return <StackView {...rest} state={state} navigation={navigation} descriptors={descriptors} />
}
export default createNavigatorFactory(ThemedStack)
What am I doing wrong? I dont get it. The ThemedStack navigator is inside a NavigationContainer.
I did this based on https://reactnavigation.org/docs/custom-navigators/ RN Navigation docs, although they don't say anything about doing it in external package structure.
Thanks in advance :)
Sounds like you have #react-navigation/native in dependencies in your package which results in multiple versions installed. You should move it to peerDependencies.

React Native Hook problem [ useNavigation ]

I am writing applications with React Native. I am using typescript. I am using Hook and getting an error in the application. When I searched, Hook is valid as of React-Native version 0.59.0 but I'm having trouble.
How can I solve it?
Hook Issue App
http://prnt.sc/vvkotk
import { useNavigation } from "#react-navigation/native";
import React from "react";
import { Dimensions, Image, StyleSheet } from "react-native";
import { Box, Header, Text } from "../../components";
import { useTheme } from "../../components/Theme";
const Drawer = () => {
const navigation = useNavigation();
const theme = useTheme();
return (
<Box flex={1}>
<Box flex={0.2} backgroundColor="white">
<Box
position="absolute"
top={0}
left={0}
right={0}
bottom={0}
borderBottomRightRadius="xl"
backgroundColor="secondary"
>
It looks like it doesn't recognise your custom useTheme hook. Please make sure the filename starts with use so useTheme.tsx of useTheme.jsx. Check the filename of your Drawer component as well to be sure. It should contain .jsx or .tsx.

How can you push a screen using only the navigation ref

Using the react-navigation library, when you're on a screen that's part of a stack navigator you can use props.navigation.push(route name, params). I want to achieve the same thing, being able to push a new screen but from the root level where I take the NavigationContainer ref.
So I have this code
<NavigationContainer ref={navigationRef}>
...
</NavigationContainer>
And I want to do something like
navigationRef.push(routeName, params);
Unfortunately, I don't have the push method here. Is there a workaround for this?
You could set up a RootNavigation.js file like this:
import * as React from 'react';
import { StackActions } from '#react-navigation/native';
export const navigationRef = React.createRef();
export function push(...args) {
navigationRef.current?.dispatch(StackActions.push(...args));
}
Then you can use it like this:
import {navigationRef} from './path/to/RootNavigation';
<NavigationContainer ref={navigationRef}>
{/* content */}
</NavigationContainer>
and this:
import * as RootNavigation from './path/to/RootNavigation';
RootNavigation.push('Screen', { data: '...' })
Source: https://reactnavigation.org/docs/navigating-without-navigation-prop/.

how to go back a page in react native navigation v3

I'm using the tabs template on expo react native.
I have a navigation in the AppNavigator.js
import React from 'react';
import { createAppContainer, createSwitchNavigator, createStackNavigator } from 'react-navigation';
import MainTabNavigator from './MainTabNavigator';
import GoalScreen from '../screens/GoalScreen';
const GoalStack = createStackNavigator({
Goal: GoalScreen
})
export default createAppContainer(createSwitchNavigator({
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
Main: MainTabNavigator,
Goal: GoalStack
}));
When i tap on the Goal I get to that page fine. but inside the goal screen i want to go back when i press the back button.
<Left>
<Button hasText transparent onPress={() => {
this.props.navigation.goBack(null);
}} >
<Text>Cancel</Text>
</Button>
</Left>
But for some reason is not working.
In switch navigator you have to switch navigation directly,
eg:
this.props.navigation.navigate("Main");
And if you push from one screen to another screen(In stack navigation) you can use the 'goBack' function.
It's working correctly There is no previous stack to go back.
The purpose of SwitchNavigator is to only ever show one screen at a time. By default, it does not handle back actions and it resets routes to their default state when you switch away
https://reactnavigation.org/docs/en/switch-navigator.html
You can perform goback action inside GoalStack if you have more than one screen. But both the GoalStack and MainTabNavigator is specified in switch navigator. Since switch navigator shows one screen at a time, you can't perform goback here.
if you want to go for MainTabNavigator from GoalStack, you need to use like below
this.props.navigation.navigate("Main")
Try this:
this.props.navigation.goBack();

React Navigation: "Cannot Add a child that doesn't have a YogaNode to a parent without a measure function" when using Custom Navigators

I'm trying to make the UI for my app in the below picture:
My App's UI
I follow the instruction of React Navigation to make the Custom Navigator according to the UI but it doesn't work in Android. The red screen appears with the message "Cannot Add a child that doesn't have a YogaNode to a parent without a measure function". Here is my code:
import React, { Component } from 'react';
import { createStackNavigator } from 'react-navigation';
import TabAboutScreen from './TabAbout';
import TabMyLessonScreen from './TabMyLesson';
import TabTeacherScreen from './TabTeacher';
import { ScrollView, View, Text } from '../../../components';
import TabNavigator from './TabNavigator';
import TopBar from './TopBar';
import styles from './styles';
import CourseHeader from './CourseHeader';
import theme from '../../../theme';
import i18n from '../../../i18n';
export const CourseDetailStackNavigator = createStackNavigator({
TabAbout: TabAboutScreen,
TabMyLesson: TabMyLessonScreen,
TabTeacher: TabTeacherScreen,
}, {
headerMode: 'none',
initialRouteName: 'TabAbout',
});
export default class TabCourseDetail extends Component {
static router = CourseDetailStackNavigator.router;
constructor(props) {
super(props);
this._handleOnBackButtonPress = this._handleOnBackButtonPress.bind(this);
}
_handleOnBackButtonPress() {
// do something
}
render() {
return (
<View style={styles.container}>
<TopBar textButton={i18n.t('CMBack')} title={i18n.t('CDCourseDetail')} onPress={this._handleOnBackButtonPress} />
<ScrollView
style={styles.scrollContainer}
stickyHeaderIndices={[1]}
showsVerticalScrollIndicator={false}
alwaysBounceVertical={false}
>
<CourseHeader />
<TabNavigator />
<View style={styles.test}>
<CourseDetailStackNavigator navigation={this.props.navigation} />
</View>
</ScrollView>
</View>
);
}
}
My evironment: react-navigation: 2.12.1, react-native: 0.55.4
I found out that the problem was that I put inside component by following the document of react navigation. It works well in iOS but doesn't work in Android.
Have you ever faced this problem. I'm looking forward to your solutions. Best regard.
Make sure you have not left any commented code in the return method and also have not left any text (string) without Text tag of react native.