How to test the value of TextInput in react-native - react-native

I followed the chosen answer in this thread, but I couldn't figure it out. I want to test the value of a TextInput component so I could check the length of it, what is the proper way to achieve this today?
My component looks something like this:
import React, {useState, useEffect} from 'react';
import {TextInput} from 'react-native';
export default function TextInputComponent(props) {
const [text, setText] = useState('');
useEffect(() => {
props.text ? setText(props.text) : setText('');
}, []);
const handleInputTextChange = text => {
setText(text);
};
return (
<TextInput
onChangeText={text => handleInputTextChange(text)}
value={text}
maxLength={maxLength}
testID="text-input"
/>
);
}
And the test file I constructed so far:
import React from 'react';
import renderer from 'react-test-renderer';
import {render} from 'react-native-testing-library';
import TextInputComponent from 'components/textInputComponent/textInputComponent';
describe('<TextInputComponent />', () => {
it('renders correctly', () => {
renderer.create(<TextInputComponent />);
});
it('should show "AAA" with text="AAAA" and maxLength="3" props', () => {
const props = {
text: 'AAAA',
maxLength: 3,
};
const {queryByTestId} = render(<TextInputComponent {...props} />);
const textInput = queryByTestId('text-input');
console.log(textInput);
});
});

I think what you're trying to do is to limit the initial text passed in props to the maxLength of characters passed.
in your component useEffect() ,
instead of:
props.text ? setText(props.text) : setText('');
slice the initial text:
props.text ? setText(props.text.slice(0, maxLength)) : setText('');
that should work for having the text length less than the maxLength too.

Related

react-native-wheel-selector how to set value and animate programmatically

using this package for wheel selector, and works fine, I just need to select value programmatically with animation(as happen user did).
Here is source code, which works cool, just need prop to select programmatically.
import React, { useRef, useMemo, useEffect, useState } from "react";
import { LinearGradient } from "expo-linear-gradient";
import WheelPickerExpo from "react-native-wheel-picker-expo";
const CITIES = "Jakarta,Bandung,Sumbawa,Taliwang,Lombok,Bima".split(",");
const ActionSheet = () => {
const [selectedRate, setSelectedRate] = useState(0);
useEffect(() => {
setTimeout(() => {
setSelectedRate(2);//changing initial select doesnt animate.
}, 4000);
}, []);
return (
<WheelPickerExpo
height={300}
width="100%"
initialSelectedIndex={selectedRate}
items={CITIES.map(name => ({ label: name, value: "" }))}
onChange={({ item }) => {
console.log(`item`, item);
}}
/>
);
};
export default ActionSheet;

test content of a Text element in a stateful component

I am using react-native-testing-library. My component is quite simple:
import React, {Component} from 'react';
import {Text, View} from 'react-native';
import {information} from './core/information';
export default class Logo extends Component {
constructor() {
super();
this.state = {
name: ''
};
information()
.then((details) => {
this.setState({
name: details['name']
});
})
.catch((e) => {
console.log(e);
});
}
render() {
return (
<>
<View>
<Text>{this.state.name}</Text>
</View>
</>
);
}
}
I want to make sure contains the right content. I tried the following but it is failing:
import * as info from "./lib/information";
it('displays correct text', () => {
const spy = jest.spyOn(info, 'information')
const data = {'name':'name'}
spy.mockResolvedValue(Promise.resolve(data));
const {queryByText, debug} = render(<Logo />);
expect(queryByText(data.name)).not.toBeNull();
expect(spy).toHaveBeenCalled();
});
I can confirm the function information() was spied on correctly but still debug(Logo) shows the Text element with empty string.
If it's correctly spying you can try this. I encourage you to use the testID props for the components
render() {
return (
<>
<View>
<Text testID="logo-text">{this.state.name}</Text>
</View>
</>
);
}
import * as info from "./lib/information";
import { waitForElement, render } from "react-native-testing-library";
it('displays correct text', () => {
const spy = jest.spyOn(info, 'information')
const data = {'name':'name'}
//this is already resolving the value, no need for the promise
spy.mockResolvedValue(data);
const {getByTestId, debug} = render(<Logo />);
//You better wait for the spy being called first and then checking
expect(spy).toHaveBeenCalled();
//Spy function involves a state update, wait for it to be updated
await waitForElement(() => getByTestId("logo-text"));
expect(getByTestId("logo-text").props.children).toEqual(data.name);
});
Also, you should move your information call inside a componentDidMount

React Native Date Range

import React, {useState} from "react";
import { StyleSheet, View, Text } from "react-native";
import { globalStyles } from "../styles/global";
import {Calendar, CalendarList, Agenda} from 'react-native-calendars';
import {LocaleConfig} from 'react-native-calendars';
import moment from "moment";
import DateRangePicker from "react-native-daterange-picker";
export default function About(){
const [endDate, setendDate] = useState(null)
const [startDate, setstartDate] = useState(null)
const [displayedDate, setdisplayedDate] = useState(moment())
state = {
endDate: null,
startDate: null,
displayedDate: moment()
};
const handleSubmit = (props) => {
console.log(props);
setendDate(props.endDate);
setstartDate(props.startDate);
setdisplayedDate(props.displayedDate);
// console.log(props.startDate);
// console.log(props.displayedDate);
}
return(
<View style={globalStyles.container}>
<DateRangePicker
onChange={ handleSubmit }
endDate={endDate}
startDate={startDate}
displayedDate={displayedDate}
range>
<Text>Click me!</Text>
</DateRangePicker>
</View>
)
}
1.not able to select date range.
2. undefined is not an object (evaluating displayedDate.format)
3. Using function component but most of the solutions are available with class component
You can call handleSubmit as follows
onChang={() => handleSubmit()}
And props param is needless in function prototype
const handleSubmit = (props) => {}
Because props is already declared and you don't need to set it as parameter.
If you want to use it as parameter then you should change like this
onChange={() => handleSubmit(props)}
Hope this helps you.
You can change your handleSubmit() function like this
const handleSubmit = (props) => {
if (props.startDate != undefined) {
setStartDate(props.startDate);
}
if (props.displayedDate != undefined) {
setDisplayedDate(props.displayedDate);
}
if (props.endDate != undefined) {
setEndDate(props.endDate);
}
}
Source: issues#15

What is the best practice for unit testing my functional component with hooks in React Native?

I've written a simple wrapper component around ScrollView that enables/disables scrolling based on the available height it's given:
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {Keyboard, ScrollView} from 'react-native';
import {deviceHeight} from '../../../platform';
export default function ScrollingForm({
availableHeight = deviceHeight,
children,
}) {
const [scrollEnabled, setScrollEnabled] = useState(true);
const [formHeight, setFormHeight] = useState(0);
const scrollingForm = useRef(null);
const checkScrollViewEnabled = useCallback(() => {
setScrollEnabled(formHeight > availableHeight);
}, [availableHeight, formHeight]);
const onFormLayout = async event => {
await setFormHeight(event.nativeEvent.layout.height);
checkScrollViewEnabled();
};
useEffect(() => {
Keyboard.addListener('keyboardDidHide', checkScrollViewEnabled);
// cleanup function
return () => {
Keyboard.removeListener('keyboardDidHide', checkScrollViewEnabled);
};
}, [checkScrollViewEnabled]);
return (
<ScrollView
ref={scrollingForm}
testID="scrollingForm"
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
scrollEnabled={scrollEnabled}
onLayout={onFormLayout}
onKeyboardDidShow={() => setScrollEnabled(true)}>
{children}
</ScrollView>
);
}
I need to write unit tests for this component. So far I have:
import React from 'react';
import {Keyboard} from 'react-native';
import {render} from 'react-native-testing-library';
import {Text} from '../..';
import ScrollingForm from './ScrollingForm';
describe('ScrollingForm', () => {
Keyboard.addListener = jest.fn();
Keyboard.removeListener = jest.fn();
let renderer;
describe('keyboard listener', () => {
it('renders text with default props', () => {
renderer = render(
<ScrollingForm>
<Text>Hello World!</Text>
</ScrollingForm>,
);
expect(Keyboard.addListener).toHaveBeenCalled();
});
it('should call listener.remove on unmount', () => {
renderer = render(
<ScrollingForm>
<Text>Goodbye World!</Text>
</ScrollingForm>,
);
renderer.unmount();
expect(Keyboard.removeListener).toHaveBeenCalled();
});
});
});
I want to also confirm that on layout if the available height is greater than the form height, that scrollEnabled is correctly being set to false. I've tried something like this:
it('sets scrollEnabled to false onFormLayout width 1000 height', () => {
mockHeight = 1000;
const {getByTestId} = render(
<ScrollingForm availableHeight={500}>
<Text>Hello World!</Text>
</ScrollingForm>,
);
const scrollingForm = getByTestId('scrollingForm');
fireEvent(scrollingForm, 'onLayout', {
nativeEvent: {layout: {height: mockHeight}},
});
expect(scrollingForm.props.scrollEnabled).toBe(false);
});

React-Native: pressing the button twice only updates the this.setState

The App is simple.. Conversion of gas.. What im trying to do is multiply the Inputed Amount by 2 as if it is the formula. So i have a index.js which is the Parent, and the Calculate.component.js who do all the calculations. What i want is, index.js will pass a inputed value to the component and do the calculations and pass it again to the index.js to display the calculated amount.
Index.js
import React, { Component } from 'react';
import { Text } from 'react-native';
import styled from 'styled-components';
import PickerComponent from './Picker.Component';
import CalculateAVGAS from './Calculate.component';
export default class PickerAVGAS extends Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam('headerTitle'),
headerStyle: {
borderBottomColor: 'white',
},
});
state = {
gasTypeFrom: 'Gas Type',
gasTypeTo: 'Gas Type',
input_amount: '',
pickerFrom: false,
pickerTo: false,
isResult: false,
result: '',
};
inputAmount = amount => {
this.setState({ input_amount: amount });
console.log(amount);
};
onResult = value => {
this.setState({
result: value,
});
console.log('callback ', value);
};
render() {
return (
<Container>
<Input
placeholder="Amount"
multiline
keyboardType="numeric"
onChangeText={amount => this.inputAmount(amount)}
/>
<ResultContainer>
<ResultText>{this.state.result}</ResultText>
</ResultContainer>
<CalculateContainer>
<CalculateAVGAS
title="Convert"
amount={this.state.input_amount}
total="total"
titleFrom={this.state.gasTypeFrom}
titleTo={this.state.gasTypeTo}
// isResult={this.toggleResult}
result={value => this.onResult(value)}
/>
</CalculateContainer>
</Container>
);
}
}
CalculateAVGAS / component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
export default class CalculateAVGAS extends Component {
static propTypes = {
amount: PropTypes.string.isRequired,
titleFrom: PropTypes.string.isRequired,
titleTo: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
state = {
totalVisible: true,
result: '',
};
onPressConversion = () => {
const formula = this.props.amount * 2;
const i = this.props.result(this.state.result);
this.setState({ result: formula });
// console.log(this.state.result);
console.log('func()');
}
render() {
return (
<ButtonContainer onPress={() => this.onPressConversion()}>
<ButtonText>{this.props.title}</ButtonText>
</ButtonContainer>
);
}
}
After doing this, the setState only updates when pressing the Convert button twice
your issue here is that you want to display information in the parent component, but you are saving that info in the child component's state.
Just pass amount, and result, to the stateless child component (CalculateAVGAS).
It's usually best to keep child components "dumb" (i.e. presentational) and just pass the information they need to display, as well as any functions that need to be executed, as props.
import React, {Component} from 'react';
import styled from 'styled-components';
export default class CalculateAVGAS extends Component {
onPressConversion = () => {
this.props.result(this.props.amount * 2);
};
render() {
return (
<ButtonContainer onPress={() => this.onPressConversion()}>
<ButtonText>{this.props.title}</ButtonText>
</ButtonContainer>
);
}
}
const ButtonContainer = styled.TouchableOpacity``;
const ButtonText = styled.Text``;
Parent component looks like:
import React, {Component} from 'react';
import {Text} from 'react-native';
import styled from 'styled-components';
import CalculateAVGAS from './Stack';
export default class PickerAVGAS extends Component {
static navigationOptions = ({navigation}) => ({
title: navigation.getParam('headerTitle'),
headerStyle: {
borderBottomColor: 'white',
},
});
state = {
gasTypeFrom: 'Gas Type',
gasTypeTo: 'Gas Type',
input_amount: null,
pickerFrom: false,
pickerTo: false,
isResult: false,
result: null,
};
inputAmount = amount => {
this.setState({input_amount: amount});
};
onResult = value => {
this.setState({
result: value,
});
};
render() {
return (
<Container>
<Input
placeholder="Amount"
multiline
keyboardType="numeric"
onChangeText={amount => this.inputAmount(amount)}
/>
<ResultContainer>
<ResultText>{this.state.result}</ResultText>
</ResultContainer>
<CalculateContainer>
<CalculateAVGAS
title="Convert"
amount={this.state.input_amount}
total="total"
titleFrom={this.state.gasTypeFrom}
titleTo={this.state.gasTypeTo}
result={value => this.onResult(value)}
/>
</CalculateContainer>
</Container>
);
}
}
const Container = styled.View``;
const ResultContainer = styled.View``;
const ResultText = styled.Text``;
const CalculateContainer = styled.View``;
const Input = styled.TextInput``;