navigation.popToTop is not a function - react-native

I would like to test the navigation function in my code. On my actual navbar component, the functions works where by when I press the button, the user is directed to the home page.
Navbar.tsx
<Pressable
style={styles.leftArrow}
onPress={() => navigation.popToTop()}
testID="backButton">
<SvgIcon
id="arrow-left"
size={'21px'}
color={arrowColor()}
testID="leftArrow"
/>
</Pressable>
However, when I try to run the same function in my test case I get "navigation.popToTop is not a function". I have already tried to mocj the .popToTop function so Im not sure why this happens
Navbar.test.js
const mockedDispatch = jest.fn();
jest.mock('#react-navigation/native', () => {
const actualNav = jest.requireActual('#react-navigation/native');
return {
...actualNav,
useNavigation: () => ({
navigate: jest.fn(),
popToTop: jest.fn(),
dispatch: mockedDispatch,
}),
};
});
describe('Navbar unit tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('on button press, redure', async () => {
const {getByTestId} = render(
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<Navbar />
</NavigationContainer>
</Provider>,
);
fireEvent(getByTestId('backButton'), 'press');
expect(mockedDispatch).toHaveBeenCalledTimes(1);
expect(mockedDispatch).toHaveBeenCalledWith('Home');
});

Related

Flatlist inside tab navigator is scrolling to top on state change in react native

Here you can see the gif
Here is my whole Navigator functional component. I'm trying to implement two tabs using Tab Navigator. One to display the cryptos and the other to display the forex data.
The problem is, when I try to load more data on reaching the flatlist's end, the flatlist is scrolling to the top since I'm making a state change [page+1].
const Navigator = () => {
const Tab = createMaterialTopTabNavigator();
const renderItems = ({ item }) => (
<Text>{item.name}<Text>
);
const fetchMarketData = async () => {
console.log("Fetching");
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
const ids = data.map((item) => item.id);
let newData = marketData.filter((item) => !ids.includes(item.id));
setData([...data, ...newData]);
setFetching(false);
} else {
setFetching(false);
Alert.alert(marketData, "Sorry for the inconvenience");
}
};
useEffect(() => {
setFetching(true);
const data = async () => {
await fetchMarketData();
};
}, [page]);
const handleLoadMore = async () => {
setFetching(true);
setPage((page) => page + 1);
};
const ScreenA = () => (
<FlatList
data={data}
style={{ backgroundColor: "white" }}
keyExtractor={(item) => item.id}
renderItem={renderItems}
scrollEventThrottle={16}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
/>
);
return (
<Tab.Navigator
screenOptions={({ route }) => screenOptions(route)}
keyboardDismissMode="auto"
>
<Tab.Screen name="Crypto" component={ScreenA} />
<Tab.Screen name="Forex" component={ScreenC} />
</Tab.Navigator>
);
};
export default Navigator;
OnEndReached is firing the handleLoadMore function and after the state change on data, the Flatlist is scrolling to the top.
1st reason
you have typo in "fetchMarketData", how exactly u get "newData" because i cant see it anywhere, maybe it should be "marketData" if not then u adding SAME old data PLUS undefined[...data, ...undefined]
2nd reason
reason why is that u call setPage(page + 1) and then "fetchMarketData" this is bad why ? because setState is async and it can be changed instant or after 5 secound, so u dont know when its changed and this is why we have hooks, you can use "useEffect" to handle this
change your "handleLoadMore" for example like this
const handleLoadMore = () => {
setPage(page + 1);
};
add useEffect hook that runs when "page" state changes
React.useEffect(() => {
(async() => {
setFetching(true)
const marketData = await getCryptoMarketData({ page });
if (marketData != "Network Error") {
setData([...data, ...marketData]);
} else {
Alert.alert(marketData, "Sorry for the inconvenience");
}
setFetching(false)
})()
}, [page])

Hi can anyone explain how to test formik with jest in react native

Hi I created form builder with formik for react native. I'm using jest for testing, but when I test onSubmit is not calling can anyone please explain how to test.
function FormBuilder({data, onSubmit, initialValues, navigation}) {
const formik = useFormik({
enableReinitialize: true,
initialValues: initialValues,
onSubmit: data => {
onSubmit(data);
},
});
return (
<View>
{data.map((item,index) => {
switch (item.type) {
case 'text':
return (
<TextBox
key={index}
onChangeText={formik.handleChange(item.name)}
onBlur={formik.handleBlur(item.name)}
value={formik.values[item.name]}
label={item.name}
touched={formik.touched}
errors={formik.errors}
required={
!!item.validators &&
item.validators.find(valid => valid.type === 'required')
}
{...item}
/>
);
case 'button':
return (
<CustomButton key={index} testID={item.testID} title=
{item.name} onPress={formik.handleSubmit} />
);
}
})}
</View>
)
}
and I call this component like this in my screen. Can anyone explain how can we write test Integration test for this
<FormBuilder
initialValues={initialValues}
data={[
{
type: 'text',
name: 'whatsAppNumber',
testID: 'input',
},
{type: 'button', name: 'login', testID: 'button'},
]}
onSubmit={submitData}
/>
you can test your code by following method, please use msw for creating dummy server for api call.
import {fireEvent, render,} from '#testing-library/react-native';
describe('Login Screen', () => {
it('should validate form', async () => {
const {getByTestId} = render(<Login />);
const numberField = getByTestId('input');
const button = getByTestId('button');
expect(numberField).toBeDefined();
expect(button).toBeDefined();
fireEvent.changeText(numberField, '9876543215');
fireEvent.press(button)
await waitFor(() =>{
//expect your function to be called
})
});
});

react-query how to call mutate from custom useMutation hook in Jest test

I'm running a React Native project where I'm testing custom hooks written with react-query. I'm using Jest, #testing-library/react-hooks and #testing-library/react-native. In my testing I can't seem to find a way to call the mutate function returned by the hook.
Here's a look at the custom hook:
export default function useAuthentication() {
const { mutate, data, isSuccess, isError } = useMutation(
authenticationApi.authenticateUser
);
return { mutate, data, isSuccess, isError };
}
Following the documentation from react-query I am rendering the hook with renderHook() and awaiting the result of the mutation call:
const authBody: AuthBody = {
username: '111111',
password: '111111',
};
describe('Authentication Hook', () => {
const sanityCall = () =>
axios.post('http://localhost:4000/auth/authenticate');
console.log(sanityCall());
const queryClient = new QueryClient();
it('Gets authentication data', async () => {
const wrapper = ({ children }: any) => (
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{children}
</PersistGate>
</Provider>
</QueryClientProvider>
);
const { result, waitFor } = renderHook(() => useAuthentication(), {
wrapper,
});
await waitFor(() => {
result.current.mutate({
username: authBody.username,
password: authBody.password,
});
return result.current.isSuccess;
});
expect(result.current.data).toEqual({ answer: 42 });
});
});
It doesn't call the API. My local server's terminal window logs that I'm pinging the server when using the sanityCall() but remains silent when I comment out that call and rely on the hook. Does anyone have any ideas for how to go about testing a custom hook like this?
Wrapping it in Act and calling mutate there resulted in a call to my server:
const Wrapper = ({ children }: any) => (
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<NavigationContainer>{children}</NavigationContainer>
</PersistGate>
</Provider>
</QueryClientProvider>
);
it("Gets authentication data", async () => {
const { result } = renderHook(() => useAuthentication(), {
wrapper: Wrapper,
});
act(() => {
result.current.mutate({
username: authBody.username,
password: authBody.password,
});
});
const token = store.getState().token;
console.log("TOKEN:");
console.log(token);
});

How can I fire a function in headerRight using Formik?

I'm new to react-native and formik and I encountered this problem that I'm trying to build up.
How can I fire a function in headerRight using Formik? I have updateCorporation function that will do fire api, and formik will do the job to fire this function and after I press the Update button, but the results are undefined
I didn`t understand why its happening.
File_1.js
const CorporationContainer = (props) => {
const {
navigation,
} = props;
const updateCorporation = (values) => {
// do patch stuff with values
// but its undefined
};
useEffect(() => {
navigation.setParams({ updateCorporation: updateCorporation.bind() });
}, []);
return (
<Corporation
updateCorporation={updateCorporation} />
);
};
CorporationContainer.navigationOptions = ({ navigation }) => ({
headerRight: (
<EditBtn
onPress={() => navigation.state.params.updateCorporation()}
>
<EditText>Update</EditText>
</EditBtn>
),
});
export default CorporationContainer;
File_2.js
const Corporation = (props) => {
const {
updateCorporation,
} = props;
const emailField = useRef(null);
const validationSchema = yup.object().shape({
email: yup.string()
.ensure()
.email('Email must be valid')
.required('Email'),
});
return (
<Formik
initialValues={{
email,
}}
onSubmit={values => updateCorporation(values)}
validateOnBlur={false}
validateOnChange={false}
validationSchema={validationSchema}
>
{(formProps) => {
const {
errors,
setFieldValue,
values,
} = formProps;
return (
<Container>
<Input
name="email"
placeholder="Email Corporation"
textContentType="emailAddress"
keyboardType="email-address"
returnKeyType="done"
autoCapitalize="none"
autoCorrect={false}
ref={emailField}
value={values.email}
onChangeText={setFieldValue}
editable={!email}
error={errors.email}}
/>
</Container>
);
}}
</Formik>
);
};
export default Corporation;
In File_1.js I had to use withForm and remove all Formik things in File_2.js and use the props instead.
const CorporationContainer = (props) => {
const {
navigation,
handleSubmit,
errors,
setFieldValue,
values,
} = props;
useEffect(() => {
navigation.setParams({ updateCorporation: handleSubmit.bind() });
}, []);
return (
<ProfileProfessional
errors={errors}
setFieldValue={setFieldValue}
values={values}
/>
);
};
CorporationContainer.navigationOptions = ({ navigation }) => ({
headerRight: (
<EditBtn
onPress={() => navigation.state.params.updateCorporation()}
>
<EditText>Editar</EditText>
</EditBtn>
),
});
export default withFormik({
// ...
})(CorporationContainer);
Formik author here...
Haven't tried this out, and idk exactly how navigation binding works, but you want to bind Formik's submitForm() prop to the navigation and not the updateCorporation function. However, You will need to do this where you have access to Formik props/context (i.e. as a child of <Formik>).
import React from 'react'
import { connect } from 'formik'
const updateCorporation = (values) => {
// do patch stuff with values
// but its undefined
};
const BindSubmit = connect(({ formik, navigation }) => {
useEffect(() => {
navigation.setParams({ updateCorporation: submitForm.bind() });
}, []);
return null
})
// ... and then just render it somewhere under Formik
const Corporation = () => {
return (
<Formik>
<BindSubmit />
{/* ... same */}
</Formik>
)
}

Enzyme + React Native + Jest: How to `console.log()` the content of a `<Text />`?

I'm testing the content of a <Text /> tag in React Native using Enzyme and Jest. My problem is the that the test is failing (even though everything empirically works and even though I feel like I wrote the test correctly). Here is the test:
describe("when less than minimum mandatory chosen", () => {
it("should render a label saying choose at least X items", () => {
console.log(wrapper);
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
).toBe(true);
});
});
I would like to check what the actual string within the <Text /> tag is. How could I achieve this?
As per request by Brian, here is the full test code:
import React from "react";
import { shallow } from "enzyme";
import { Text, View } from "react-native";
import {
SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import { AccordionList } from "./AccordionList";
import styles from "./styles";
const createTestProps = props => ({
header: "Kekse",
items: [
{
uuid: "1057e751-8ef1-4524-a743-1b4ba7b33d7b",
name: "Haferkeks",
price: "2.00",
priceCurrency: "EUR"
},
{
uuid: "f41f8e1a-b526-490e-ba4a-3d6acb3f3c16",
name: "Schokosojakeks",
price: "1.50",
priceCurrency: "EUR"
}
],
chosenItems: [],
onItemPressed: jest.fn(),
...props
});
describe("AccordionList", () => {
describe("rendering", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
});
it("should render container", () => {
expect(
wrapper
.find(View)
.at(0)
.prop("style")
).toContain(styles.container);
});
it("should render a <Collapsible />", () => {
expect(wrapper.find("Collapsible")).toHaveLength(1);
});
it("should give the header's <TouchableOpacity /> the headerbutton style", () => {
expect(
wrapper
.find("TouchableOpacity")
.at(0)
.prop("style")
).toEqual(styles.headerButton);
});
it("should render a header", () => {
expect(
wrapper
.find(Text)
.at(0)
.contains(props.header)
).toBe(true);
});
it("should give the header the header style", () => {
expect(
wrapper
.find(Text)
.at(0)
.prop("style")
).toEqual(styles.header);
});
it("should render a subheader", () => {
expect(
wrapper
.find(Text)
.at(1)
.prop("style")
).toContain(styles.subHeader);
});
it("should render a <TouchableOpacity /> for each of it's items", () => {
expect(wrapper.find("TouchableOpacity")).toHaveLength(props.items.length + 1);
});
describe("folded", () => {
it("should render an arrow pointing to the right", () => {
expect(wrapper.find("Image").prop("source")).toEqual(
require("../../assets/icons/rightArrow.png")
);
});
it("should render the folded arrow with the default style", () => {
expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
});
describe("mandatory", () => {
beforeEach(() => {
props = createTestProps({ minChoices: 1 });
wrapper = shallow(<AccordionList {...props} />);
});
it("should render a mandatory label with the minimum number of mandatory items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${props.minChoices})`)
).toBe(true);
});
});
describe("optional", () => {
it("should render an optional label", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`)
).toBe(true);
});
});
});
describe("expanded", () => {
beforeEach(() => {
wrapper.setState({ collapsed: false });
});
it("should render an arrow pointing down", () => {
expect(wrapper.find("Image").prop("source")).toEqual(
require("../../assets/icons/downArrow.png")
);
});
it("should render the expanded arrow with the default style", () => {
expect(wrapper.find("Image").prop("style")).toEqual([styles.arrowIcon, styles.inActive]);
});
// FIXME: These tests should also work but don't for some reason.
describe("mandatory", () => {
beforeEach(() => {
props = createTestProps({ minChoices: 1 });
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
describe("when less than minimum mandatory chosen", () => {
it("should render a label saying choose at least X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${props.minChoices}.`)
).toBe(true);
});
});
describe("when more than minimum mandatory chosen", () => {
beforeEach(() => {
props = createTestProps({
minChoices: 1,
chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
});
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should render a label saying choose up to X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
).toBe(true);
});
});
});
describe("optional", () => {
it("should render a label saying choose up to X items", () => {
expect(
wrapper
.find(Text)
.at(1)
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
).toBe(true);
});
});
});
describe("item chosen", () => {
beforeEach(() => {
props = createTestProps({ chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"] });
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should render a checkmark for the item", () => {
expect(
wrapper
.find("Image")
.at(1)
.prop("source")
).toEqual(require("../../assets/icons/checkmark.png"));
});
it("should render the checkmark with the checkmarkIcon and active style", () => {
expect(
wrapper
.find("Image")
.at(1)
.prop("style")
).toEqual([styles.checkmarkIcon, styles.active]);
});
it("should render the folded arrow with the primary style", () => {
expect(
wrapper
.find("Image")
.at(0)
.prop("style")
).toContain(styles.active);
});
it("should render the expanded arrow with the primary style", () => {
wrapper.setState({ collapsed: false });
expect(
wrapper
.find("Image")
.at(0)
.prop("style")
).toContain(styles.active);
});
});
describe("max items chosen", () => {
beforeEach(() => {
props = createTestProps({
maxChoices: 1,
chosenItems: ["1057e751-8ef1-4524-a743-1b4ba7b33d7b"]
});
wrapper = shallow(<AccordionList {...props} />);
wrapper.setState({ collapsed: false });
});
it("should disable all items but the chosen", () => {
expect(
wrapper
.find("TouchableOpacity")
.at(2)
.prop("disabled")
).toEqual(true);
});
});
});
describe("interaction", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
});
// FIXME: This test does not work for some reason...
// describe("pressing the header", () => {
// beforeEach(() => {
// wrapper.instance().toggleExpanded = jest.fn();
// wrapper
// .find("TouchableOpacity")
// .first()
// .prop("onPress")();
// });
//
// it("should call the toggleExpanded() instance function", () => {
// expect(wrapper.instance().toggleExpanded).toHaveBeenCalledTimes(1);
// });
// });
describe("pressing an item", () => {
beforeEach(() => {
wrapper
.find("TouchableOpacity")
.at(1)
.prop("onPress")();
});
it("should call the onItemPressed callback", () => {
expect(props.onItemPressed).toHaveBeenCalledTimes(1);
});
});
});
describe("component methods", () => {
describe("toggleExpanded", () => {
let wrapper;
let props;
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<AccordionList {...props} />);
wrapper.instance().toggleExpanded();
});
it("should change the state of the component to collapsed=false", () => {
expect(wrapper.instance().state.collapsed).toBe(false);
});
});
});
});
And here is the full code of the component:
import React, { PureComponent } from "react";
import { Image, Text, TouchableOpacity, View } from "react-native";
import Collapsible from "react-native-collapsible";
import PropTypes from "prop-types";
import {
SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY,
SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO,
SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST
} from "../../config/constants/screenTexts";
import styles from "./styles";
export class AccordionList extends PureComponent {
static propTypes = {
header: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
chosenItems: PropTypes.array.isRequired,
onItemPressed: PropTypes.func.isRequired,
minChoices: PropTypes.number,
maxChoices: PropTypes.number,
borderTop: PropTypes.bool,
borderBottom: PropTypes.bool
};
static defaultProps = {
minChoices: 0,
maxChoices: 1,
borderTop: false,
borderBottom: false
};
state = { collapsed: true };
toggleExpanded = () => {
this.setState(state => ({ collapsed: !state.collapsed }));
};
renderContent = () => {
const { items, onItemPressed, chosenItems, maxChoices } = this.props;
return (
<View>
{items.map(item => {
const disabled =
!chosenItems.includes(item.uuid) &&
chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length ===
maxChoices;
return (
<View style={styles.itemContainer} key={item.uuid}>
<TouchableOpacity
onPress={() => onItemPressed(item.uuid)}
style={[styles.itemButton, disabled ? styles.opaque : null]}
disabled={disabled}
>
{item.name && <Text style={styles.itemText}>{item.name}</Text>}
{item.price && (
<View style={styles.priceContainer}>
<Text style={styles.sizeText}>{item.label ? `${item.label} ` : ""}</Text>
<Text style={styles.sizeText}>
{item.size ? `${item.size.size}${item.size.unit}: ` : ""}
</Text>
<Text style={styles.priceText}>{item.price} €</Text>
</View>
)}
<View style={styles.checkMarkContainer}>
{chosenItems.includes(item.uuid) ? (
<Image
source={require("../../assets/icons/checkmark.png")}
resizeMode="contain"
style={[styles.checkmarkIcon, styles.active]}
/>
) : null}
</View>
</TouchableOpacity>
</View>
);
})}
</View>
);
};
render() {
const {
header,
items,
maxChoices,
minChoices,
chosenItems,
borderTop,
borderBottom
} = this.props;
const { collapsed } = this.state;
return (
<View
style={[
styles.container,
borderTop ? styles.borderTop : null,
borderBottom ? styles.borderBottom : null
]}
>
<TouchableOpacity onPress={this.toggleExpanded} style={styles.headerButton}>
<Text style={styles.header}>{header}</Text>
<Text style={[styles.subHeader, minChoices > 0 ? styles.mandatory : styles.optional]}>
{minChoices > 0
? collapsed
? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_MANDATORY}, ${minChoices})`
: chosenItems.filter(item => items.map(item => item.uuid).includes(item)).length >=
maxChoices
? `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
: collapsed
? `(${SCREEN_TEXT_MENU_ITEM_DETAIL_OPTIONAL})`
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${maxChoices}.`}
</Text>
<Image
source={
collapsed
? require("../../assets/icons/rightArrow.png")
: require("../../assets/icons/downArrow.png")
}
resizeMode="contain"
style={[
styles.arrowIcon,
chosenItems.length > 0 &&
chosenItems.some(item => items.map(item => item.uuid).includes(item))
? styles.active
: styles.inActive
]}
/>
</TouchableOpacity>
<Collapsible collapsed={collapsed}>{this.renderContent()}</Collapsible>
</View>
);
}
}
export default AccordionList;
Looks like it's just a typo, you're missing a . in the component code.
Change this line:
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}`
...to this:
: `${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_AT_LEAST} ${minChoices}.`
One other thing I noticed, your component uses a default for maxChoices of 1, but in your test there are two spots where you are referencing props.maxChoices where it hasn't been set. You'll probably want to change the two lines like this:
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices}.`)
..to this:
.contains(`${SCREEN_TEXT_MENU_ITEM_DETAIL_CHOOSE_UP_TO} ${props.maxChoices || 1}.`)
to reflect the default assigned by the component.