Check if navigation.navigate is called in react native testing - react-native

I've been using 'react-test-renderer' for testing react-native, Mostly on it's functions. Lately I've found out that there is a testing library for react-native which is #testing-library/react-native. What I wanted to do is to check if navigation.navigate or alert has been called in a function. I don't know which of these libraries can achieve that and how.
onSubmitPress = () => {
if (true) {
this.props.navigation.navigate('MainPage');
else {
showAlert( "Not Allowed);
}

You can use 'react-test-renderer':
like this:
const fakeNavigation = {
navigate: jest.fn(),
};
const tree = renderer.create(
<YourComponent navigation={fakeNavigation} />
);
after that you can use jest expect function to test if mock navigation.navigate is called someThing like this:
const instance = tree.getInstance();
instance.onSubmit();
expect(fakeNavigation.navigate).toBeCalledWith('MainPage')
for react-native-testing-library it's a bit different as you test mostly what user sees depending of the behavior of your component.
Basically you look for your button that calls onSubmit and then use fireEvent from react-native-testing-library to press it, after that you expect the text of your alert to be shown in screen or not.
If you have a button like this in your component=
<TouchableOpacity onPress={onSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
You can test the behavior of you function like this:
const {getByText} = render(<YourComponent/>)
let buttonText = getByText('Submit');
fireEvent(buttonText.parent, 'press'); //parent to get to TouchableOpacity
alertText = getByText('Not Allowed');
expect(alertText).toBeDefined();

Related

How to create an rxjs Observable from TextInput (either onChange or onTextChange)

I want to create an observable from a change event that gets fired on a React Native TextInput component. TextInput comes with 2 change props that I'm aware of (onChangeText and onChange). From what I gather, you need to use onChange if you want access to the native event you need to use onChange.
I don't know much about the native event object. I am trying to create an rxjs observable using fromEvent.
First I created a ref in my functional component like this:
const sqftRef = useRef().current
Then I attached this ref to the TextInput component like this:
<TextInput
ref={sqftRef} // attach a ref
label='Sqft'
mode='flat'
textContentType='none'
autoCapitalize='none'
keyboardType='numeric'
autoCorrect={false}
value={String(formValues.sqft)}
dense
underlineColor={colors.colorOffWhite}
onChangeText={(text) => setText(text)}
onChange={e => {
// somehow create an observable from this event ???
}}
style={styles.inputStyles}
theme={inputTheme}
/>
I tried to create an Observable using fromEvent like this but it doesn't work. I get undefined is not an object (evaluating target.addEventListener):
fromEvent(sqftRef, 'onChange').subscribe(value => console.log(value))
I know my approach is all wrong. Hoping someone can point me in the correct direction.
I would emit events you need into a subject, then subscribe to the subject in other parts of your code.
Here's a simple React example that should get you started
function App() {
const textChange = new Subject<string>();
useEffect(() => {
// subscribe to
const subscription = textChange.asObservable().subscribe(console.log)
return () => subscription.unsubscribe()
}, [])
// Emit events with a subject
return <textarea onChange={(e) => {
textChange.next(e.target.value)
}}>
</textarea>
}
render(<App />, document.getElementById('root'));
Check out the example here: https://stackblitz.com/edit/react-ts-akoyfv
I think the problem is with assigning the current directly to the sqftRef. Try to define it without current, but use current when creating the Observable, like the following:
const sqftRef = useRef();
Then create the Observable within useEffect to make sure that the DOM is ready:
useEffect(() => {
fromEvent(sqftRef.current, 'onChange').subscribe((value) =>
console.log(value)
);
});
OK, I was able to figure it out with the help of Amer Yousuf and Alex Fallenstedt.
I did something similar to what Alex suggested, modifying his solution for React Native. One reason his solution wasn't working for me is that it is important to use the useRef hook to prevent the Observable from being re-created on each render. If the observable is recreated (on a re-render) and useEffect doesn't run again, then we won't have an active subscription to the newly (re-created) observable (useEffect never runs again). That's why my call to sqft$.next was originally only being called once (the first time until we re-render).
My solution looks like this:
let sqft$ = useRef(new BehaviorSubject(0)).current
useEffect(() => {
const sub = sqft$.subscribe({
next: (val) => {
// just testing stuff out here
updateForm('sqft', val)
updateForm('lot', val * 2)
}
})
// this is only relevant to my use case
if (activeReport) sqft$.next(activeReport.sqft)
return () => sub.unsubscribe()
}, [activeReport])
and of course I call this in onChangeText:
onChangeText={(text) => {
sqft$.next(text)
}}
So this is working right now. I still feel like there may be a better way using onChange(e => ...stuff). I will leave this question open for a little bit in case anyone can break down how to do this using nativeEvent or explain to me how I can access an event off the TextInput component.

Query in react native about sliding up panel

In React Native iOS, I would like to slide in and out of a like in the following picture.
So I installed this https://github.com/octopitus/rn-sliding-up-panel for ease.
but this error is showing =>
i cant understand whats wrong, I am new to react native. Please Help!
You cannot access variable called _panel from this object because you are inside a function itself. besides you are using function based react, in order to create a reference check useRef() hook or switch to class based component and then you can use this._panel;
smthg like this:
function AccessingElement() {
const elementRef = useRef();
const onPress = () => {
// e.g
elementRef.current.show();
}
return (
<View ref={elementRef}>
...child views
</View>
);
}

Trigger Topbar buttons in test

I'm trying to test navigation events on a screen using react-native-testing-library.
I'm listening to the events globally using Navigation.events().registerNavigationButtonPressedListener with the following hook inside my Functional component:
const useTopBarBtnPress = function (
componentId: string,
onTopBtnPressed: OnTopBtnPressed) {
useEffect(() => {
const topBtnListener = Navigation.events().registerNavigationButtonPressedListener((event) => {
if (event.componentId === componentId)
onTopBtnPressed(event, BtnIds)
})
return () => topBtnListener.remove()
}, [onTopBtnPressed])
}
Is it possible to simulate a topBar button for the test ? I guess using the testID but I can't find it in the doc.
Or do I need to mock registerNavigationButtonPressedListener ? Or use Detox ?
Also, is there a way to test the layout ? (eg. Icon color)
There's no need to actually mock TopBar buttons to test navigationButtonPressed. Simply invoke navigationButtonPressed yourself with the correct NavigationButtonPressedEvent parameter simulating a button press.

React Native onPress being called automatically

I am having trouble with react-native onPress Feature. The onPress should only work when it is actually been triggered by a touch event (i suppose) , that is when i press the button on the screen. But it seems the onPress gets triggered itself when the render function is called. When i try to press manually, it doesn't work.
import React, { Component } from 'react';
import { PropTypes, Text, View ,Alert } from 'react-native';
import { Button } from 'react-native-material-design';
export default class Home extends Component {
render() {
return (
<View style={{flex:1}}>
<Button value="Contacts" raised={true} onPress={this.handleRoute('x')} />
<Button value="Contacts" raised={true} onPress={this.handleRoute('y')} />
<Button value="Contacts" raised={true} onPress={this.handleRoute('z')} />
</View>
);
}
handleRoute(route){
alert(route) // >> x , y, z
}
}
module.exports = Home;
What am i missing ? Is there something wrong with the way i have assigned or this is some bug ?
Any suggestion is highly appreciated.
Video
try to change
onPress={this.handleRoute('x')} // in this case handleRoute function is called as soon as render happen
to
onPress={() => this.handleRoute.bind('x')} //in this case handleRoute doesn't called as soon as render happen
You can change to this:
onPress={this.handleRoute.bind(this, 'x')}
or this:
onPress={() => this.handleRoute('x')}
The reason is that onPress takes a function as an argument. In your code, you are calling the function and returning the result immediately (when render is called) rather than referencing the function for React to call later on the press event.
The reason you need the bind(this) is because the function loses it's bound instance when you just do (this.handleRoute) and you have to tell it which this to use. The bind function takes the other arguments to call on the function later. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind for more descriptive info on bind.
There is another way in which you can bind in the constructor. You can read about ways to handle this in React here: https://facebook.github.io/react/docs/handling-events.html
onPress={this.handleevent.bind(this, 'A')}
or use this:
onPress={() => this.handleevent('B')}
Change
onPress={this.handleRoute('x')}
to
onPress={()=>this.handleRoute('x')}
Otherwise, the function gets invoked as soon as the render method gets called.
The reason for such behaviour is on every render, reference to the function is created.
So, to avoid that, use bind function OR arrow function to call on onPress

Spying on React components using Enzyme (and sinon?) to check arguments

I'm wanting to assert that a component gets called from within another component with the correct arguments.
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
I'm pretty sure I want to use a sinon spy and do something like this
const titleSpy = sinon.spy(Title, render)
expect(titleSpy).to.be.calledWith( '< some title >' )
but with regards to React and Enzyme, I'm not really sure what I should be spying on. (Because apparently it's not render!)
In my spec file I am importing Title and console.loging it's value to find a function to spy on and I get:
function _class() {
_classCallCheck(this, _class);
return _possibleConstructorReturn(this, Object.getPrototypeOf(_class).apply(this, arguments));
}
Any ideas on how I can do this? Is it a case of going through and finding the element and checking it's attributes? If so that seems a bit...messy and seems like it goes against the principle of the Shallow render ("Shallow rendering is useful to constrain yourself to testing a component as a unit").
If you're just checking the value of properties passed to the component, you don't need sinon. For example, given the following component:
export default class MyComponent extends React.Component {
render() {
return (
<MyComponent myProp={this.props.myProp} />)
}
}
Your test might look like this:
describe('MyComponent ->', () => {
const props = {
myProp: 'myProp'
}
it('should set myProp from props', () => {
const component = shallow(<MyComponent {...props} />)
expect(component.props().myProp).to.equal(props.myProp)
})
})
You can achieve it with the help of .contains() method, without messing up with spies.
If you have a component:
<Foo>
<Title title="A title" url="http://google.com" />
</Foo>
You can make such an assertion:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A title" url="http://google.com" />)).to.equal(true);
Such will fail:
const wrapper = shallow(<Foo />);
expect(wrapper.contains(<Title title="A wrong title" url="http://youtube.com" />)).to.equal(true);
This is an older question, but my approach is a little different than the existing answers:
So within the component that I am testing there is a Title component that gets called with properties title & url. I'm trying to assert that it gets called with the correct arguments.
ie. You're wanting to check that the component being tested renders another component, and passes the correct prop(s) to it.
So if the component being tested looks something like:
const MyComp = ({ title, url }) => (
<div>
<Title title={title} url={url} />
</div>
)
Then the test could look something like:
import Title from 'path/to/Title';, u
it('renders Title correctly', () => {
const testTitle = 'Test title';
const testUrl = 'http://example.com';
const sut = enzyme.shallow(<MyComp title={testTitle} url={testUrl} />);
// Check tested component rendered
expect(sut.exists).toBeTruthy();
// Find the Title component in the subtree
const titleComp = sut.find(Title); // or use a css-style selector string instead of the Title import
// Check that we found exactly one Title component
expect(titleComp).toHaveLength(1);
// Check that the props that were passed were our test values
expect(titleComp.prop('title')).toBe(testTitle);
expect(titleComp.prop('url')).toBe(testUrl);
});
I generally find Enzyme's functions to be very useful for all kinds of checks about components, without needing other libraries. Creating Sinon mocks can be useful to pass as props to components, to (for example) test that a callback prop is called when a button is clicked.