How to correctly mock React Navigation's getParam method using Jest - reactjs

I have a React Native app in which I'm trying to write some integration tests using Jest & Enzyme. My situation is as follows, I have a component which fetches a navigation param being passed to it from the previous screen using getParam - which works fine normally, I'm just struggling to successfully get a value in there using mock data. My code looks like this:
In my container I have this in the render method:
const tickets = navigation.getParam('tickets', null);
Then in my test I have the following:
const createTestProps = (testProps: Object, navProps: any = {}) =>
({
navigation: {
navigate: jest.fn(),
getParam: jest.fn(),
...navProps,
},
...testProps,
} as any);
let props = createTestProps(
{},
{
state: {
// Mock navigation params
params: {
tickets: [
{
cellNumber: '123456789',
ticketId: 'xxx',
},
{
cellNumber: '123456789',
ticketId: 'xxx',
},
],
},
},
}
);
const container = mount(
<MockedProvider mocks={mocks} addTypename={false}>
<ThemeProvider theme={theme}>
<TicketSummaryScreen {...props} />
</ThemeProvider>
</MockedProvider>
);
As you can see I've attempted to mock the actual navigation state, which I've checked against what's actually being used in the real component, and it's basically the same. The value for tickets is still undefined each time I run the test. I'm guessing it has to do with how I've mocked the getParam function.
Anyone have any ideas? Would be much appreciated!

I just successfully fixed this problem on my project. The only advantage that I had is that I had the render method being imported from a file I created. This is a great because all my tests can be fixed by just changing this file. I just had to merge some mocked props into the component that render was receiving.
Here's what it looked like before:
/myproject/jest/index.js
export { render } from 'react-native-testing-library'
After the fix:
import React from 'react'
import { render as jestRender } from 'react-native-testing-library'
const navigation = {
navigate: Function.prototype,
setParams: Function.prototype,
dispatch: Function.prototype,
getParam: (param, defaultValue) => {
return defaultValue
},
}
export function render(Component) {
const NewComponent = React.cloneElement(Component, { navigation })
return jestRender(NewComponent)
}
This setup is great! It just saved me many refactoring hours and probably will save me more in the future.

Maybe try returning the mock data from getParam

Try bellow example code.
const parameters = () => {
return "your value"
}
.....
navigation: {
getParam: parameters,
... navProps
},
... testProps
});

Give it a try
const navState = { params: { tickets: 'Ticket1', password: 'test#1234' } }
const navigation = {
getParam: (key, val) => navState?.params[key] ?? val,
}
here navState values will be params that you are passing.

Related

Writing a TypeScript Interface for React Context, how to describe the interface/ different types to be passed as values?

I am trying to write a React Context for my application
This is a follow up from my previous question:
How to pass value to Context Provider in TypeScript?
I would please like some help describing different value types to be passed through the provider.
Overview:
I am currently trying to construct a context provider that can be used across the scope of my application in TypeScript.
It contains some useState hooks and and an asynchronous function to be passed through as values the provider to all the child components.
ResultConextProvider.tsx
export const ResultContextProvider = () => {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [greenStatus, setGreenStatus] =
useState(new Set<MyEnum>());
const [redStatus, setRedStatus] =
useState(new Set<MyEnum>());
const [searchTerm, setSearchTerm] = useState<string>('')
// objects to be passed values
const greenValue = {greenStatus, setGreenStatus};
const redValue = {redStatus, setRedStatus};
const searchValue = {searchTerm, setSearchTerm};
// api function coming from tested API spec (external)
const getResults = async () => {
setIsLoading(true)
myAPI.myGet(greenStatus, redStatus).then((result) => {
setResults(result.data);
})
setIsLoading(false)
}
return (
<ResultContext.Provider value={{getResults, greenValue, redValue, searchValue}}>
{children}
</ResultContext.Provider>
}
export const useResultContext = () => useContext(ResultContext);
As you can see above, I would like to pass the getResults function, my greenValues, redValus and searchValues to all my child components, the Provider implentation will look something like this:
index.tsx
import { ResultContextProvider } from "./contexts/ResultContextProvider";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ResultContextProvider
<Router>
<App />
</Router>
</ResultContextProvider>
);
When it comes to writing the interface, I am struggling to understand what needs to be represented prior to my component.
So far my interface looks something like this:
ResultConextProvider.tsx
interface ContextParametersType {
greenValue: { greenStatus: Set<MyEnum>, setGreenStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
redValue: { redStatus: Set<MyEnum>, setRedStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
///INCORRECT //
// searchValue: {searchTerm: <string>(''), setSearchTerm:
Dispatch<SetStateAction<string> };
///UNSURE
// getResults : {
// What to write as interface for getResults
// }
}
I have worked out what needs to be declared for my greenValue and redValue, based on the answer to my previous question however struggling to fill out the rest of the interface specification
The Search Term:
///INCORRECT //
// searchValue: {searchTerm: (''), setSearchTerm:
Dispatch<SetStateAction };
I have tried to follow the pattern of the Enum state, particularly in declaring the state as type string and rerferecning the Dispatch<SetStateAction as the same type to change the state however this produces error
getResults
This is an asynchronous function with an Axios function inside, to be truthful i do not know where to begin within this one.
My confusion on these two steps persists when creating the ResultContext
const ResultContext = createContext<ContextParametersType>({
greenValue: {
greenStatus: new Set<FirmStatusEnum>(), setGreenStatus: () => {
}
},
redValue: {
redStatus: new Set<FirmStatusEnum>(), setRedStatus: () => {
}
},
// searchValue: {
// searchTerm: <string>(''), setSearchTerm: () => {
// }
// },
getResults()
// Not Sure how to initalsie this?
});
Does this make sense? Am I understanding the issues here correctly?
I would really like some help in understanding how to configure the context interfaces for this.
It is essentially a typescript adaptation of this tutorial
Here:
Thanks, please let me know if I need to provide clarity

Setting state in React context provider with TypeScript, functional components and hooks

I'm starting with React and TypeScript at the same time and I across a problem while implementing some basic authentication in my application. I've been using Ryan Chenkie's Orbit App and his course on React security as an example to start from.
Right now I'm stuck with compiler complaining about TS2722 error (Cannot invoke object which is possibly undefined) in SignIn.tsx. My suspicion is that all I have to do is to set proper types on all the data structures and function calls, but how and where to set them, somewhat alludes me. So, here's the code:
App.tsx: Nothing fancy here, just an App wrapped in context provider.
import { AuthContext, authData } from "./AuthContext"
const defAuth:authData = {
userId: 0,
name: '',
token: '',
expiresAt: ''
}
const App = () => {
return (
<AuthContext.Provider value={{ authData:defAuth }}>
<Main />
</AuthContext.Provider>
);
}
AuthContext.tsx: When calling createContext() I tried with various default parameters, the general idea is that I could call authContext.setState() and pass the data to it. I am using Partial prefix so that I don't have to pass the setState() to the Provider element.
export interface authData {
userId: number
name: string
token: string
expiresAt: string
}
interface IAuthContext {
authData: authData,
setState: (authInfo:authData) => void
}
const AuthContext = createContext<Partial<IAuthContext>>(undefined!)
const { Provider } = AuthContext
const AuthProvider: React.FC<{}> = (props) => {
const [authState, setAuthState] = useState<authData>()
const setAuthInfo = (data:authData) => {
console.log('Called setAuthInfo')
setAuthState({
userId: data!.userId,
name: data!.name,
token: data!.token,
expiresAt: data!.expiresAt
})
}
return (
<Provider
value={{
authData: authState,
setState: (authInfo:authData) => setAuthInfo(authInfo)
}} {...props}
/>
)
}
export { AuthContext, AuthProvider }
SignIn.tsx: This is again, just a basic sign in component with a form and an onSubmit handler. This is all working as it should until I add the authContext to it. I included only relevant code.
interface loginType extends Record<string, any>{
email: string,
password: string,
remember: boolean
}
const SignIn = () => {
const authContext = useContext(AuthContext)
const { register, handleSubmit, errors } = useForm<loginType>()
const onSubmit = async (data:loginType) => {
const ret = await apiFetch.post('process_login/', formData )
console.log(ret.data)
console.log('Printing context')
authContext.setState(ret.data)
console.log(authContext)
}
/* ... ... */
}
As mentioned before, compiler complains in SignIn.tsx at authContext.setState(ret.data) telling me that it might be undefined. I tried calling createContext() with various parameters, trying to pass it some defaults which would tell the compiler where that setState() will be defined later on in the runtime. I tried calling setState in a few different ways, but nothing really worked.
This is something that clearly works in plain JSX and I'd really like to find a way to make it work in TSX.
Here's what you need to do:
First in App.tsx, you have to use AuthProvider instead of AuthContext.Provider. This way you get rid of the value property.
<AuthProvider>
<Main />
</AuthProvider>
Then, in AuthContext.tsx there's no need to use Partial prefix when creating context. So, a little tweak to the IAuthContext and then pass some default data when creating context.
interface IAuthContext {
authData: authData | undefined,
setState: (authInfo:authData) => void
}
const AuthContext = createContext<IAuthContext>( {
authData: defaultAuthData,
setState: () => {}
})
Now you can call authContext.setState(), passing data as authData type.

React FC Context & Provider Typescript Value Issues

Trying to implement a global context on an application which seems to require that a value is passed in, the intention is that an API will return a list of organisations to the context that can be used for display and subsequent API calls.
When trying to add the <Provider> to App.tsx the application complains that value hasn't been defined, whereas I'm mocking an API response with useEffect().
Code as follows:
Types types/Organisations.ts
export type IOrganisationContextType = {
organisations: IOrganisationContext[] | undefined;
};
export type IOrganisationContext = {
id: string;
name: string;
};
export type ChildrenProps = {
children: React.ReactNode;
};
Context contexts/OrganisationContext.tsx
export const OrganisationContext = React.createContext<
IOrganisationContextType
>({} as IOrganisationContextType);
export const OrganisationProvider = ({ children }: ChildrenProps) => {
const [organisations, setOrganisations] = React.useState<
IOrganisationContext[]
>([]);
React.useEffect(() => {
setOrganisations([
{ id: "1", name: "google" },
{ id: "2", name: "stackoverflow" }
]);
}, [organisations]);
return (
<OrganisationContext.Provider value={{ organisations }}>
{children}
</OrganisationContext.Provider>
);
};
Usage App.tsx
const { organisations } = React.useContext(OrganisationContext);
return (
<OrganisationContext.Provider>
{organisations.map(organisation => {
return <li key={organisation.id}>{organisation.name}</li>;
})}
</OrganisationContext.Provider>
);
Issue #1:
Property 'value' is missing in type '{ children: Element[]; }' but required in type 'ProviderProps<IOrganisationContextType>'.
Issue #2:
The list is not rendering on App.tsx
Codesandbox: https://codesandbox.io/s/frosty-dream-07wtn?file=/src/App.tsx
There are a few different things that you'll need to look out for in this:
If I'm reading the intention of the code properly, you want to render OrganisationProvider in App.tsx instead of OrganisationContext.Provider. OrganisationProvider is the custom wrapper you have for setting the fake data.
Once this is fixed, you're going to run into an infinite render loop because in the OrganisationProvider component, the useEffect sets the organisations value, and then runs whenever organisations changes. You can probably set this to an empty array value [] so the data is only set once on initial render.
You're trying to use the context before the provider is in the tree above it. You'll need to re-structure it so that the content provider is always above any components trying to consume context. You can also consider using the context consumer component so you don't need to create another component.
With these suggested updates, your App.tsx could look something like the following:
import * as React from "react";
import "./styles.css";
import {
OrganisationContext,
OrganisationProvider
} from "./contexts/OrganisationContext";
export default function App() {
return (
<OrganisationProvider>
<OrganisationContext.Consumer>
{({ organisations }) =>
organisations ? (
organisations.map(organisation => {
return <li key={organisation.id}>{organisation.name}</li>;
})
) : (
<div>loading</div>
)
}
</OrganisationContext.Consumer>
</OrganisationProvider>
);
}
And the updated useEffect in OrganisationsContext.tsx:
React.useEffect(() => {
setOrganisations([
{ id: "1", name: "google" },
{ id: "2", name: "stackoverflow" }
]);
}, []);

How to mock i18next and react-i18next for testing a component/view using typescript

I basically have a react website and I'm using typescript for both the source and test code. I have integrated the i18next and react-i18next libraries to my project for supporting translations and modified one of my react components. However, I'm having issues when I try to unit test my components that are extending withTranslation. The first problem i'm having is i'm getting a compilation error, saying that my props are missing: i18n, tReady and t. And yes that is true they are not there, but I'm not sure how should I handle that.
I have tried including the mock below on my tests, which would supposedly pass the required props to my component:
jest.mock("react-i18next", () => ({
withTranslation: () => (Component: any) => {
Component.defaultProps = {
...Component.defaultProps,
t: () => "",
};
return Component;
},
}));
This is how my code looks like:
interface DemoViewProps extends WithTranslation {
name: string;
}
const DemoView = (props: DemoViewProps) => {
const { t, name } = props;
return <div>{t("string id")} {name}</div>
}
This is how my test code looks like:
describe("<DemoView>", () => {
const props = {
name: "NAME",
};
const component = shallow(<DemoView {...props} />);
it("should do something", () => {
...some testing here
});
});
I would like not to get compilation errors and be able to run my unit tests. I would also like my code not to become to cumbersome, since I will be doing this across different components that are using withTranslation()

How To Handle conditional Routing or Component Navigation Without React Router

I need to navigate between components based on several conditions, and I do not want routes to be displayed in the browser, say localhost:3000/step1 or localhost:3000/step2. The whole application is guided so that a user have to answer all the steps to reach the final result.
ATM I have a main container which handles the component rendering based on the Redux store value.
import React, { Component } from "react";
class Home extends Component {
renderComponent = screen => {
switch (screen) {
case 'SCREEN_A':
return <ScreenA />;
case 'SCREEN_B':
return <ScreenB />;
case 'SCREEN_C':
return <ScreenC />;
case 'SCREEN_D':
return <ScreenD />;
default:
return <ScreenA />;
}
};
render() {
return <div>{this.renderComponent(this.props.currentScreen)}</div>;
}
}
function mapStateToProps(storeData) {
return {
store: storeData,
currentScreen: storeData.appState.currentScreen,
userData: storeData.userData
};
}
export default connect(mapStateToProps)(Home);
The problem is I have to use dispatch to trigger navigation
navigateTo(screens[destination])
navigateBackward(currentScreen)
navigateForward(currentScreen)
in almost all components. I do have a predefined JSON for each component which contains the destination for each screen.
screens : {
SCREEN_A:{
id: 1,
name: 'SCREEN_A',
next: 'SCREEN_B',
back: 'WELCOME_SCREEN',
activeLoader: true,
},
SCREEN_B:{
id: 2,
name: 'SCREEN_B',
next: 'SCREEN_C',
back: 'WELCOME_SCREEN',
activeLoader: true,
},
SCREEN_C:{
id: 3,
name: 'SCREEN_C',
next: 'SCREEN_D',
back: 'SCREEN_A',
activeLoader: true,
},
SCREEN_D:{
id: 4,
name: 'SCREEN_D',
next: 'SCREEN_E',
back: 'SCREEN_D',
activeLoader: true,
},
}
And there are protected screens which makes things way more complicated. Is there a better way of doing this with redux? or should I create a middleware and intercept each state change and calculate the next screen.
I would change a few things:
Make your steps/screens dynamic. By putting them into an Array and using the index to determine the current step it removes a lot of code and will make it easier to add/move steps.
Store the steps/screens config in the redux store.
Optionally, you can pass the nextStep and previousStep to the StepComponent. e.g. <StepComponent nextStep={nextStep} previousStep={previousStep} />.
In your last step, you probably want to call a different action instead of nextStep.
Here's what my solution would look like:
// Home.jsx
import React, { Component } from 'react';
import * as types from '../../redux/Actor/Actor.types';
class Home extends Component {
stepComponents = [
ScreenA,
ScreenB,
ScreenC,
ScreenD,
];
render() {
const { step, steps } = this.props;
const StepComponent = this.stepComponents[step];
return (
<div>
<StepComponent {...steps[step]} />
</div>
);
}
}
// store.jsx
export default {
step : 0,
steps: [
{
id : 1,
name : 'SCREEN_A',
activeLoader: true,
},
....
],
};
// actions.jsx
export const nextStep = () => ({ type: 'NEXT_STEP' });
export const previousStep = () => ({ type: 'PREVIOUS_STEP' });
// reducers.jsx
export const nextStep = state => ({ ...state, step: state.step + 1 });
export const previousStep = state => ({ ...state, step: state.step - 1 });

Resources