How to do jest testing in react native? - react-native

I have a simple code which I have written to test.
This is my App.js:
const App: () => React$Node = () => {
function sum(a, b) {
alert(a + b);
return a + b;
}
return (
<>
<Button
title="Sum"
onPress={() => {sum(1,2)}}
/>
);
This is my App-test.js in the __tests__ folder:
import 'react-native';
const sum = require('../App');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
When I run npm test I get this error TypeError: sum is not a function. Can anyone help.
Note: When I run it on my android phone it works properly and shows 3 in the alert box.

It's because you are importing sum from your App file but it is not an exported method the way you wrote it.
You can do something like this:
export const sum = (a, b) => {
alert(a + b);
return a + b;
}
export const App = () => (
<Button
title="Sum"
onPress={() => {sum(1,2)}}
/>
);
And use it like this in your test file:
import { sum } from '../App'
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
It's not particularly linked to React Native since you're only trying to test a specific method, which can be use with any framework.

Related

Export child components and use individually

I have a case where I need to export two child components and use individually.
Much Desired outcome (Extremely simplified):
Controls.js:
const Controller = ( props ) => {
const ControlBoxes = () => {
return(<Button>Move around!</Button>)
}
const MoveableBox = () => {
return(<View>I will be moved! </View>)
}
return {ControlBoxes, MoveableBox}
}
export default Builder
Canvas.js:
import Controller from './controls'
const boxScaleMove = boxes.map((box, index) => {
return (
<Bulilder.MoveableBox key={box.id} box={box}/>
)
}
const boxController = boxes.map((box, index) => {
return (
<Bulilder.ControlBoxes key={box.id} box={box}/>
)
}
return (
...
{boxController}
...
...
{boxScaleMove}
...
)
Any idea how I can achieve this or am I missing something fundamental? The main issue is that I want to avoid resorting to useContext (due to performance reasons in the case of a lot of boxes rendered) and be able to share variables and states between MoveableBox-component and ControlBoxes-component via Controller -parent.
Any help would be greatly appreciated.
You could use the compound component and use a lower level context to avoid re-rendering of the whole tree and share states across your components that way, below I would ilustrate a basic example of how that would work.
const RandomContext = createContext();
export default function Controller({children, ...rest}) {
const [randomState, setRandom] = useState(0);
return (
<RandomContext.Provider value={{ randomState, setRandom }}>
<div {...rest}>{children}</div>
</RandomContext.Provider>
);
}
Controller.ControlBoxes = function (props) {
const { setRnadom } = useContext(RandomContext);
return (
<Button onClick={() => setRandom(2)} {...props}>Move around!</Button>
);
};
Controller.MoveableBox = function (props) {
const { randomState } = useContext(RandomContext);
return randomState ? <View {...props}>I will be moved!</View> : null;
};
And you would use it as:
<Controller>
<Controller.ControlBoxes />
<Controller.MoveableBox />
<Controller>
In the compound components pattern we are leveraging the fact that in javascript when you declare a function you create a function/object combo. Therefor Controller function is both a function and an object, so we can assign properties the the object part of that combo, properties which are in our case ControlBoxes and MoveableBox which are functions themselves.
NOTE you should probably assign named function the the properties of that object, it's easier to debug if the case needed.
Example.Function = function ExampleFunction(props) {
return "Example";
};

React Native how to set the time and date as the local device?

I was trying to get the time and date and set as the local device format .
I have tried this api "https://www.npmjs.com/package/react-native-device-time-format?activeTab=readme",
But dosen't work at all ..
Here is my code ,could you please take a look ?
Thank you so much ?
import React,{useEffect,useState} from 'react';
import { StyleSheet, View } from 'react-native';
import { is24HourFormat } from 'react-native-device-time-format';
import moment from 'moment';
function TryTime(props) {
const [currentTime, setCurrentTime] = useState("");
const getCurrentHourFormat = async (date) => {
const is24Hour = await is24HourFormat();
return moment(date).format(is24Hour ? 'HH:mm' : 'h:mm A');
}
useEffect(() => {
(async () => {
const timeNow = await TryTime(Date.now);
setCurrentTime(getCurrentHourFormat(timeNow));
})();
}, []);
return (
<View style={styles.container}>
<Text>{currentTime}</Text>
</View>
);
}
const styles = StyleSheet.create({
container : {
flex : 1,
justifyContent : 'center',
alignItems : 'center',
}
})
export default TryTime;
First, to get the local time of device by JS, you can use a built-in function of js: toLocaleString()
Note:
For this question: Do you know how to get more simple ? Something like 1 sec ago ,10 min ago ,1 hour ago
=> i guess you have the previous date time, and you want to compare it to current time
something like:
a = ... // your previous time
b = new Date().toLocaleString()
Just take let c = b - a as the difference between 2 date, then
c / 1000 => get seconds
c / 1000 * 60 => get minutes
c / 1000 * 3600 => get hours

Test case for child components onClick function

I want to test the onClick functionality of MenuPopover.Item id={3} if it was called once or not after clicking on it.
import React from 'react';
import copy from 'copy-to-clipboard';
const TableMenu = ({show, target, onClick, onHide, addedType, disable, readonly, rowId, supportRestore, supportDelete, isRestored}) => (
<MenuPopover
onClick={onClick}
onHide={onHide}>
{!readonly && (addedType ?
<MenuPopover.Item id={1} label='Delete' disabled=true/> :
<MenuPopover.Item id={2} label='Restore' disabled=false/>
)}
<MenuPopover.Item id={3} onClick={() => copy(rowId)} label='Copy'/>
</MenuPopover>
);
Test case written so far
const onCopySpy = sinon.spy();
const props = {
///
onCopy: onCopySpy,
///
};
it('check method onCopy called', () => {
const wrapper = shallow(<TableMenu {...props}/>);
expect(wrapper.find('MenuPopover').children()).to.have.lengthOf(2);
wrapper.find(MenuPopover.Item).... //Test case to call the onClick function
expect(onCopySpy.calledOnce).to.eql(true);
});
copy needs to be mocked in tests:
import copy from 'copy-to-clipboard';
jest.mock('copy-to-clipboard', () => sinon.spy());
...
const wrapper = shallow(<TableMenu {...props}/>);
wrapper.find(MenuPopover.Item).props().onClick();
expect(copy.calledOnce).to.eql(true);
This can be alternatively done with simulate but it does the same thing internally.

Check if first child renders nothing in Native Testing Library

Suppose I have the following component:
const MyComponent = () => null;
In React Testing Library (RTL), the following, AFAIK, should work:
const { container } = render(<MyComponent />);
expect(container.firstChild).toBeEmpty();
where .toBeEmpty is a custom matcher from jest-dom. However, in NTL, container.firstChild is not defined so I can't use .toBeEmpty matcher from jest-native. After some experimentation, I got it to work as follows:
expect(container.children[0]).toBeUndefined();
Is there any other, possibly better, way to do this?
In my case I had a situation like so:
function PhotoComponent({policy}) {
if (policy === 'unavailable')
return null;
return (<View />);
}
Tested like this:
it("should not render if policy is 'unavailable'", () => {
const {toJSON} = render(<PhotoComponent policy={'unavailable'} />);
const children = toJSON().children;
expect(children.length).toBe(0);
});
Some times you will want to filter the children like, for instance:
const children = toJSON().children.filter(
(child) => child.type === 'View' || child.type === 'Modal'
);
Keep calm and Happy coding!

Dynamic build component with AsyncStorage

I am trying to build my sub-component in function _buildComponent, and put result into render(), just have a look at my code below
the problem I met was the AsyncStorage.getItem() is running async, causing it render nothing there in render() method
...react
_buildComponent = async (key) => {
let val = await AsyncStorage.getItem(key)
console.log(key + ' : ' +val);
debugger;//code will run to here after ScrollableTabView finish rendering. but I need to build Arr first.
if(val == 1) return <PopularView tabLabel={key}>{key}</PopularView>
}
render() {
let Arr = Constants.TABS.map(item =
return this._buildComponent(item).done();
})
debugger;//code will run into here directly without waiting building Arr above, making Arr was null when rendering ScrollableTabView
return (
<ScrollableTabView
tabBarBackgroundColor='#2196F3'
tabBarInactiveTextColor='mintcream'
tabBarTextStyle={{marginTop:27}}
initialPage={0}
renderTabBar={() => <ScrollableTabBar/>}
>
{Arr}// Arr is null here because the _buildComponent method was not finish yet.
{/* <PopularView tabLabel='Java'>Java</PopularView>
<PopularView tabLabel='IOS'>IOS</PopularView>
<PopularView tabLabel='Android'>Android</PopularView>
<PopularView tabLabel='Javascript'>Javascript</PopularView> */}
</ScrollableTabView>
)
}
...
I have explain my issue in comment, please check it, thanks guys. I do not know what's the best practise to prepare variable before running render().
Try this solution which dynamically add tabs (Updated)
_buildComponent = async () => {
let tabsData = [];
await AsyncStorage.multiGet(Constants.TABS).then(response => {
Constants.TABS.forEach((item,index) =>
{
if(response[index][1] == 1) {
tabsData.push(response[index][0]); // key
}
}
);
// This only render once
this.setState({ tabLabels: tabsData })
})
}
componentDidMount() {
this._buildComponent()
}
render() {
const tabLabelList = this.state.tabLabels.map((key) => {
return (
<PopularView tabLabel={key}>{key}</PopularView>
)
})
return (
<ScrollableTabView
tabBarBackgroundColor='#2196F3'
tabBarInactiveTextColor='mintcream'
tabBarTextStyle={{marginTop:27}}
initialPage={0}
renderTabBar={() => <ScrollableTabBar/>}
>
{tabLabelList}
</ScrollableTabView>
)
}