How goBack screen in test with detox - react-native

I make automatization react native test with detox, It has the next screen sequence A -> B -> C and i wish to go back to the screen B <- C.
Is there a solution for this?

There's a testId on the back button, so you can do this:
await element(by.id("header-back")).tap();

sometimes
await element(by.id("header-back")).tap();
does not work and
await element(by.traits(['button']))
.atIndex(0)
.tap();
selects another button. In that case, you can try to use swipe right on ios assuming that it is a stack navigator. Use the outer container view
await element(by.id('containerView')).swipe('right', 'fast', 0.1);

the solution was to use traits button as follows:
await element(by.traits(['button'])).atIndex(0).tap();

You could go ahead and create a utility
export const pressBack = async () => {
if (device.getPlatform() === 'android') {
await device.pressBack(); // Android only
} else {
await element(by.traits(['button']))
.atIndex(0)
.tap();
}
};
Android: device.pressBack()
iOS: go back last screen #828

If you are using react-native-navigation you can use:
const backButton = () => {
if (device.getPlatform() === 'ios') {
return element(by.type('_UIBackButtonContainerView'));
} else {
return element(by.label('Navigate Up'));
}
};
...
await backButton().tap();
For iOS in detox#17.3.6 & react-native-navigation#6.10.1 you can use:
return element(by.id('pop'));

Another way that works is
await element(by.id('header-back')).atIndex(0).tap()
This uses the built in testID that the default back button that comes with react-navigation v5. You may need to mess with the atIndex() number since for me it seems to match 2 back buttons but the first one was the one I was looking for.

Related

Execute a function based on a specific scroll position in React native

I am trying to call an api base on scroll View current position but not sure how to I achieve that.
This is my code
<ScrollView
ref={scrollViewRef}
scrollEventThrottle={0}
onScroll={({nativeEvent}) => {
console.log(
nativeEvent.contentSize.height -
nativeEvent.layoutMeasurement.height,
);
console.log(nativeEvent.contentOffset);
}}>
I tried to call the api inside onScroll but that didnt work well.
Try adding an event listener at the particular scroll location you want the function to execute.
useEffect(() => {
Window.addEventListener(‘scroll’_.debounce(setScroll,1000));},[]);
I have solved the issue by using adding an if check. If the api data exist then the function wont execute anymore.
here's the code
const [apiData, setApiData] = useState();
onScroll={({nativeEvent}) => {
if (!apiData) {
if (
nativeEvent.contentSize.height -
nativeEvent.layoutMeasurement.height -
nativeEvent.contentOffset.y <
250
) {
getApiDataHandler();
}
}
}}

Passing State in React-Navigation from TabsNavigation to Child StackNavigation

UPDATE: Now with a Snack Demo
I've created a demo on snack so you can see the issue first hand and help me demonstrate a solution in actual code.
Steps to duplicate
launch app
Tap "GO TO EVENTTABS" button
Tap each tab, noticing that the eventId is in scope for the first three tabs
Tap "More" tab
Tap "TEAM MEMBERS", noticing that eventId is no longer in scope. This is where the problem lies. How do I pass along eventId?
_____________________________
My App has the following navigation hierarchy, where every instance of <> is just a regular component
App <StackNavigator> {
EventList <>
EventTabs <BottomTabNavigator> {
Quests <>
Leaderboard <>
Gallery <>
More <StackNavigator> {
MoreList <>
TeamMembers <>
}
}
}
Upon entering the app, the user's first screen is EventList. They click a button to navigate into EventTabs, so I'm able to use the navigation.navigate() to transition while passing state like so...
EventList.navigation.navigate(EventTabs, passedParams);
To this point, everything makes sense. But TeamMembers also needs access to the passedParams. I'm confused how to pass those along. Hence my question...how do access passedParams from the TeamMembers component? They seem to be scoped just to the EventTabs.
If the answer is to use navigate.setParams(), then I'm not sure where I'd do that.
If the answer is to use NavigationActions.setParams(), then I'm also not sure where I'd do that.
Unfortunately we don't have good support for this, but you could use a function like this to recursively walk your navigation parents in search of the correct param.
function getParam(navigation, paramName) {
const { getParam, dangerouslyGetParent } = navigation;
let parent = dangerouslyGetParent();
let val = getParam(paramName);
while (val === undefined && parent && parent.getParam) {
val = parent.getParam(paramName);
parent = parent.dangerouslyGetParent();
}
return val;
}
The problem seems to continue for version 5.x of react navigation.
For me this works.
function getParentParam(navigation, paramName) {
const { dangerouslyGetParent } = navigation;
let paramValue = null;
const parent = dangerouslyGetParent();
const routes = parent.dangerouslyGetState().routes;
routes.some((r) => {
paramValue = r.params ? r.params[paramName] : null;
return paramValue !== null;
});
return(paramValue);
}
You can iterate over parent.dangerouslyGetParent() according to the depth level you have

How to test DatePickerIOS

I need to enter a day using the native IOS DatePicker. How do I spin the control wheel to a certain value that might not be in the view, since this starts out with the current date?
Detox 7.3.x supports interaction with iOS UIPickerView. Match UIPickerView by type, and interact with it.
await expect(element(by.type('UIPickerView'))).toBeVisible();
await element(by.type('UIPickerView')).setColumnToValue(1,"6");
await element(by.type('UIPickerView')).setColumnToValue(2,"34");
Docs are here:
https://github.com/wix/detox/blob/master/docs/APIRef.ActionsOnElement.md#setcolumntovaluecolumnvalue--ios-only
datetimepicker have Detox test you can check code here:
https://github.com/react-native-community/datetimepicker/blob/master/example/e2e/detoxTest.spec.js#L54
if (global.device.getPlatform() === 'ios') {
const testElement = await element(
by.type('UIPickerView').withAncestor(by.id('dateTimePicker')),
);
await testElement.setColumnToValue(0, 'November');
await testElement.setColumnToValue(1, '3');
await testElement.setColumnToValue(2, '1800');
await expect(dateTimeText).toHaveText('11/03/1800');
} else {
// for Android
}

How to handle deep linking in react native / expo

I have posted about this previously but still struggling to get a working version.
I want to create a sharable link from my app to a screen within my app and be able to pass through an ID of sorts.
I have a link on my home screen opening a link to my expo app with 2 parameters passed through as a query string
const linkingUrl = 'exp://192.168.0.21:19000';
...
_handleNewGroup = async () => {
try {
const group_id = await this.createGroupId()
Linking.openURL(`${linkingUrl}?screen=camera&group_id=${group_id}`);
}catch(err){
console.log(`Unable to create group ${err}`)
}
};
Also in my home screen I have a handler that gets the current URL and extracts the query string from it and navigates to the camera screen with a group_id set
async handleLinkToCameraGroup(){
Linking.getInitialURL().then((url) => {
let queryString = url.replace(linkingUrl, '');
if (queryString) {
const data = qs.parse(queryString);
if(data.group_id) {
this.props.navigation.navigate('Camera', {group_id: data.group_id});
}
}
}).catch(err => console.error('An error occurred', err));
}
Several issues with this:
Once linked to the app with the query string set, the values don't get reset so they are always set and therefore handleLinkToCameraGroup keeps running and redirecting.
Because the URL is not an http formatted URL, it is hard to extract the query string. Parsing the query string returns this:
{
"?screen": "camera",
"group_id": "test",
}
It doesn't seem right having this logic in the home screen. Surely this should go in the app.js file. But this causes complications not being able to use Linking because the RootStackNavigator is a child of app.js so I do not believe I can navigate from this file?
Any help clarifying the best approach to deep linking would be greatly appreciated.

Is it possible to change transitions in react native navigator?

I have 3 different react native components and I am using the Navigator to navigate between them. In my first view I define the navigator:
View 1
<Navigator
ref="nav"
renderScene={#renderScene}
initialRoute={#renderContent(I18n.t("Incidents"))}
configureScene={ ->
transition = Navigator.SceneConfigs.HorizontalSwipeJump
transition.gestures = null
transition
}
/>
As you can see the transition is HorizontalSwipeJump.
View 2
#props.navigator.push
component: IncidentScreen
incidentId: incident.id
sceneConfig: -> Navigator.SceneConfigs.FloatFromBottomAndroid
As you can see, I am trying move into view #3 using FloatFromBottomAndroid, however, it's not working.
By looking at the source code for RN I can see that the navigator.push method get's the animation from the props:
var nextAnimationConfigStack = activeAnimationConfigStack.concat([
this.props.configureScene(route),
]);
So what can I do?
Thanks a lot.
You have to go digging into the react-native source here for the list of SceneConfigs, but here's the current list as of writing:
PushFromRight
FloatFromRight
FloatFromLeft
FloatFromBottom
FloatFromBottomAndroid
FadeAndroid
HorizontalSwipeJump
HorizontalSwipeJumpFromRight
VerticalUpSwipeJump
VerticalDownSwipeJump
Example usage:
<Navigator
configureScene={(route) => {
if (someCondition) {
return Navigator.SceneConfigs.HorizontalSwipeJump;
} else {
return Navigator.SceneConfigs.PushFromRight;
}
}}
/>
Ok, I figure it out. I was missing this part in View 1:
configureScene={ (route) ->
if route.sceneConfig
route.sceneConfig
else
transition = Navigator.SceneConfigs.HorizontalSwipeJump
transition.gestures = null
transition
}
If anyone is still looking at this, you can push without animation by just reseting the routes to what you want them to end up being. This is assuming that you don't do anything special with your routes like save the forward routes or anything.
if( !shouldAnimate )
{
var routeStack = this.refs.mainNav.state.routeStack;
routeStack.push(newRoute);
this.refs.mainNav.immediatelyResetRouteStack(routeStack);
}
else
{
this.refs.mainNav.push(feature);
}
Where mainNav is the ref of my Navigator. Hope this helps.