how to write a test for below component in react? - javascript

could you please help me in writing the test for below component?
import { string, node } from "prop-types"
import * as Styled from "./Banner.styled"
const Banner = ({ heading, button }) => (
<Styled.Banner>
<Styled.Heading>{heading}</Styled.Heading>
{button && button}
</Styled.Banner>
)
Banner.propTypes = {
heading: string.isRequired,
button: node,
}
Banner.defaultProps = {
button: null,
}
export default Banner
I need to write a test in react library to see if the imported component(button) in rendering.
Could you please help me ? I tried the following but I think this is wrong :) The first test passes, but the second for the button itself is wrong:(
import { render, screen } from "../../../test-utils"
import Banner from "../Banner"
const heading = "heading"
const button = "button"
describe(`Banner`, () => {
it(`renders Banner with default properties`, () => {
render(<Banner heading={heading} />)
expect(screen.getByText(heading)).toBeInTheDocument()
})
it(`renders Banner with default properties`, () => {
render(<Banner button={button} />)
expect(screen.getByText(button)).toBeInTheDocument()
})
})

The second test case fails because your Banner expects heading props as required.
describe(`Banner`, () => {
it(`renders Banner without default properties`, () => {
render(<Banner heading={heading} />)
expect(screen.getByText(heading)).toBeInTheDocument()
})
it(`renders Banner with default properties`, () => {
render(<Banner heading={heading} button={button} />)
expect(screen.getByText(button)).toBeInTheDocument()
})
})
Try giving heading props in the second one.

Related

useStyles is not dynamically assigning properties based on variable change

Based on a boolean state change, I'm trying to change the style of a component, but for some reason, it's only displaying the change after a page refresh.
In my parent App component, I do the following:
import React from "react";
import Layout from "./Layout";
export default function App() {
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
setTimeout(() => {
setLoading(!loading);
}, 2000);
}, [loading]);
return <Layout loading={loading} />;
}
And my Layout component catches this loading variable and send it to the makeStyles hook,
import React from "react";
import { makeStyles } from "#material-ui/core";
const useStyles = makeStyles((theme) => ({
root: {},
wrapper: ({ loading }) => ({
paddingTop: 64,
[theme.breakpoints.down("lg")]: {
paddingLeft: loading ? 0 : 100
}
})
}));
const Layout = React.memo(({ loading }) => {
const classes = useStyles({ loading });
return (
<div className={classes.root}>
<div>side</div>
<div className={classes.wrapper}>
is loading: {JSON.stringify(loading)}
</div>
</div>
);
});
export default Layout;
Doing a console.log after wrapper: ({ loading }) => ({ prints the correct value of loading, but the style is not changing.
What's going on here?
there is two issues in your code , the first one is that you use object destructuring two times :
instead of
const Layout = React.memo(({ loading }) => { const classes = useStyles({ loading });
you should :
const Layout = React.memo(({ loading }) => { const classes = useStyles(loading );
because in your first level you have access to the property loading, second issue is that you're invoking the parameter in the wrong place, you must invoke the loading directly in the css property, like this :
paddingLeft: (loading) => (loading ? 0 : 100)
here is a link with the two corrections ,hope that what you are expecting
https://codesandbox.io/s/goofy-microservice-y2gql?fontsize=14&hidenavigation=1&theme=dark

Enzyme mount wrapper is empty after simulate('click') in ReactApp

I'm trying to test a registration component that has a Vertical Stepper with Jest/Enzyme and I keep hitting a wall when trying to simulate the user clicking "Next" .
expected behavior is to do nothing if the "Required" input fields are empty, however after doing the .simulate('click') following assertions fail with not finding any html in the wrapper.
The component is passed through react-redux connect() so I don't know if that would be related.
UserRegistration.js
import React from 'react';
import { connect } from 'react-redux';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step;
import StepLabel from '#material-ui/core/StepLabel;
import StepContent from '#material-ui/core/StepContent'
class UserRegistration extends React.Component {
constructor(props){
this.state = {
activeStep: 0,
inputData: {},
...
}
}
getStepContent = () => {
switch(this.state.activeStep)
case '...':
return
(<>
<input test-data="firstName"/>
...
</>);
...
}
render () {
const steps = ['Personal Info', 'Corporate Details', ...]
return (
<Stepper activeStep={this.state.activeStep} orientation="vertical">
{steps.map((label, index) => {
return (
<Step key={index}/>
<StepLabel>{label}</StepLabel>
<StepContent>
{this.getStepContent()}
<button data-test="btn-next" onClick={() => this.goNext()}> NEXT </button>
<button onClick={() => this.goBack()}> BACK </button>
)
}
}
</Stepper>
)
}
}
const mapStateToProps = () => {...}
const mapDispatchToProps = () => {...}
export default connect(mapStateToProps, mapDispatchToProps)(UserRegistration)
UserRegistration.test.js
const wrapper = mount(
<Provider store={store}
<UserCreate/>
</Provider>
)
it('Confirm REQUIRED fields rendered', () => {
expect(wrapper.find("input[data-test='firstName']").length).toEqual(1);
// PASS
});
it('Check if still on same step clicked NEXT with no user data', () => {
wrapper.find("button[data-test='btn-next']").simulate('click');
expect(wrapper.find("input[data-test='firstName']").length).toEqual(1);
// Expected value to equal: 1, Received: 0
})
Same outcome regardless of the element I'm looking up.
Any suggestions will be greatly appreciated.
You need to update. So you would change it:
it('Check if still on same step clicked NEXT with no user data', () => {
wrapper.find("button[data-test='btn-next']").simulate('click');
// Add this line
wrapper.update();
const button = wrapper.find("input[data-test='firstName']");
expect(button.length).toEqual(1);
// Expected value to equal: 1, Received: 0
});
Then the test should work as you intend.

useEffect hook misbehaves with setTimeout and state

I created a custom toast component in my exercise React application. It is working correctly until the moment I try to introduce an auto dismiss timeout functionality. Basically when you load a new toast it needs to dismiss itself after let say 5000ms.
If you want check the full code in my Github Repo that also have a live preview.
Easiest way to create toast is put invalid mail / password.
I believe I am doing something wrong with the useEffect hook or I am missing something. The problem is that when I am creating multiple toasts they disappear all at the same time. Also React is complaining that I didn't include remove as a dependency of the useEffect hook but when I do it becomes even worse. Can someone demystify why this is happening and how it can be fixed. I am a bit new to React.
Here is the file that creates a HOC around my main App component:
import React, { useState } from 'react';
import { createPortal } from 'react-dom';
import ToastContext from './context';
import Toast from './Toast';
import styles from './styles.module.css';
function generateUEID() {
let first = (Math.random() * 46656) | 0;
let second = (Math.random() * 46656) | 0;
first = ('000' + first.toString(36)).slice(-3);
second = ('000' + second.toString(36)).slice(-3);
return first + second;
}
function withToastProvider(Component) {
function WithToastProvider(props) {
const [toasts, setToasts] = useState([]);
const add = (content, type = 'success') => {
const id = generateUEID();
if (toasts.length > 4) {
toasts.shift();
}
setToasts([...toasts, { id, content, type }]);
};
const remove = id => {
setToasts(toasts.filter(t => t.id !== id));
};
return (
<ToastContext.Provider value={{ add, remove, toasts }}>
<Component {...props} />
{ createPortal(
<div className={styles.toastsContainer}>
{ toasts.map(t => (
<Toast key={t.id} remove={() => remove(t.id)} type={t.type}>
{t.content}
</Toast>
)) }
</div>,
document.body
) }
</ToastContext.Provider>
);
}
return WithToastProvider;
}
export default withToastProvider;
And the Toast component:
import React, { useEffect } from 'react';
import styles from './styles.module.css';
function Toast({ children, remove, type }) {
useEffect(() => {
const duration = 5000;
const id = setTimeout(() => remove(), duration);
console.log(id);
return () => clearTimeout(id);
}, []);
return (
<div onClick={remove} className={styles[`${type}Toast`]}>
<div className={styles.text}>
<strong className={styles[type]}>{type === 'error' ? '[Error] ' : '[Success] '}</strong>
{ children }
</div>
<div>
<button className={styles.closeButton}>x</button>
</div>
</div>
);
}
export default Toast;
Searching today for the solution I found it here
You will need to use useRef and its current property
Here is how I transformed the Toast component to work:
import React, { useEffect, useRef } from 'react';
import styles from './styles.module.css';
function Toast({ children, remove, type }) {
const animationProps = useSpring({opacity: .9, from: {opacity: 0}});
const removeRef = useRef(remove);
removeRef.current = remove;
useEffect(() => {
const duration = 5000;
const id = setTimeout(() => removeRef.current(), duration);
return () => clearTimeout(id);
}, []);
return (
<div onClick={remove} className={styles[`${type}Toast`]}>
<div className={styles.text}>
<strong className={styles[type]}>{type === 'error' ? '[Error] ' : '[Success] '}</strong>
{ children }
</div>
<div>
<button className={styles.closeButton}>x</button>
</div>
</div>
);
}
export default Toast;

How to use HOC and recompose properly

I just started React recently and read about HOC.
I would like to do something similar to: making a container editable.
My 'solution' (as it's not working properly yet.)
editableRow (HOC):
import React from 'react'
import { withStateHandlers, withHandlers, compose } from 'recompose'
const editableRow = () =>
compose(
withStateHandlers(
{ isEditing: false, editingId: null },
{
toggleEditing: ({ isEditing, editingId }) => entryId => ({
isEditing: !isEditing,
editingId: isEditing ? null : entryId
})
}
),
withHandlers({
handleSave: ({
isEditing,
editingId,
onEdit,
onCreate,
list
}) => values => {
console.log('handling...')
if (isEditing) {
const entry = list && list.find(entry => entry.id === editingId)
return onEdit(entry.id, values)
} else return onCreate(values)
}
})
)
export default editableRow
My DataRow:
import React from 'react'
import { Button, Checkbox, Icon, Table, Input } from 'semantic-ui-react'
import PropTypes from 'prop-types'
import editableRow from 'hoc/editableRow'
const DataRow = props =>
<Table.Row>
{
props.children
}
</Table.Row>
export default editableRow()(DataRow)
My component will receive the functions and the states I made with the HOC,
but for some reason I can't pass it anything (like calling the callbacks [onEdit, onCreate]). And isn't there a nicer way to call the handleSave instead of onSubmit={()=>props.handleSave(props1, props2, ...)}
UPDATE:
Well my problem is that I can't send any 'handler' to my component in any way. I tried it like:
<Table.Row onClick={()=>props.handleSave(
false,
false,
props.onCreate,
props.onEditing,
props.list
)}>
{
props.children
}
</Table.Row>
But my HOC's handleSave is just using it's own default values. I can't reach them, so I can't pass any handler to it.
My guess is that I making a very basic error somewhere, but don't know where :D
[Like when I save the field. That why I got those onEditing, onCreating event, BUT I even if I pass them my HOC is just using its OWN DEFAULTs instead of the parameters I passed to it]
HELP me guys please to understand how these are working... :D
import React from 'react'
import {compose} from 'recompose';
import { Button, Checkbox, Icon, Table, Input } from 'semantic-ui-react'
import PropTypes from 'prop-types'
import editableRow from 'hoc/editableRow'
const DataRow = props => {
const values = {
isEditing: false,
editingId: false,
onEdit: props.onCreate,
onCreate: props.onEditing,
list: props.list,
};
return (
<Table.Row onClick={() => {props.handleSave(values)}}>
{props.children}
</Table.Row>
);
}
export default compose(editableRow)(DataRow);
Whenever you'll compose your component with HOC then your HOC will have the props which you provided to this component as you're exporting the composed component.
So, in your HOC, you can access the props passed like this:
import { withStateHandlers, withHandlers, compose } from 'recompose'
const editableRow = () =>
compose(
withStateHandlers(
{ isEditing: false, editingId: null },
{
toggleEditing: ({ isEditing, editingId }) => entryId => ({
isEditing: !isEditing,
editingId: isEditing ? null : entryId
}),
handleSave: (state, props) => values => {
console.log('handling...')
if (isEditing) {
const list = values.list;
const entry = list && list.find(entry => entry.id === editingId)
return props.onEdit(entry.id, values)
}
return props.onCreate(values)
}
}
),
)
export default editableRow;
You don't have to use withHandlers explicitly when you're using withStateHandler which can be used for both state and handlers. I hope this helps, let me know if you're still stuck.
withStateHandler(arg1: an object or a function to set initial state, {
callback: (state, props) => callbackValues => {
//call any callback on props
props.handleSave(); // or props.onCreate() etc.
//return desired state from here
}
})

Prevent Double tap in React native

How to prevent a user from tapping a button twice in React native?
i.e. A user must not be able tap twice quickly on a touchable highlight
https://snack.expo.io/#patwoz/withpreventdoubleclick
Use this HOC to extend the touchable components like TouchableHighlight, Button ...
import debounce from 'lodash.debounce'; // 4.0.8
const withPreventDoubleClick = (WrappedComponent) => {
class PreventDoubleClick extends React.PureComponent {
debouncedOnPress = () => {
this.props.onPress && this.props.onPress();
}
onPress = debounce(this.debouncedOnPress, 300, { leading: true, trailing: false });
render() {
return <WrappedComponent {...this.props} onPress={this.onPress} />;
}
}
PreventDoubleClick.displayName = `withPreventDoubleClick(${WrappedComponent.displayName ||WrappedComponent.name})`
return PreventDoubleClick;
}
Usage
import { Button } from 'react-native';
import withPreventDoubleClick from './withPreventDoubleClick';
const ButtonEx = withPreventDoubleClick(Button);
<ButtonEx onPress={this.onButtonClick} title="Click here" />
Use property Button.disabled
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, View, Button } from 'react-native';
export default class App extends Component {
state={
disabled:false,
}
pressButton() {
this.setState({
disabled: true,
});
// enable after 5 second
setTimeout(()=>{
this.setState({
disabled: false,
});
}, 5000)
}
render() {
return (
<Button
onPress={() => this.pressButton()}
title="Learn More"
color="#841584"
disabled={this.state.disabled}
accessibilityLabel="Learn more about this purple button"
/>
);
}
}
// skip this line if using Create React Native App
AppRegistry.registerComponent('AwesomeProject', () => App);
Here is my simple hook.
import { useRef } from 'react';
const BOUNCE_RATE = 2000;
export const useDebounce = () => {
const busy = useRef(false);
const debounce = async (callback: Function) => {
setTimeout(() => {
busy.current = false;
}, BOUNCE_RATE);
if (!busy.current) {
busy.current = true;
callback();
}
};
return { debounce };
};
This can be used anywhere you like. Even if it's not for buttons.
const { debounce } = useDebounce();
<Button onPress={() => debounce(onPressReload)}>
Tap Me again and adain!
</Button>
Agree with Accepted answer but very simple way , we can use following way
import debounce from 'lodash/debounce';
componentDidMount() {
this.onPressMethod= debounce(this.onPressMethod.bind(this), 500);
}
onPressMethod=()=> {
//what you actually want on button press
}
render() {
return (
<Button
onPress={() => this.onPressMethod()}
title="Your Button Name"
/>
);
}
I use it by refer the answer above. 'disabled' doesn't have to be a state.
import React, { Component } from 'react';
import { TouchableHighlight } from 'react-native';
class PreventDoubleTap extends Component {
disabled = false;
onPress = (...args) => {
if(this.disabled) return;
this.disabled = true;
setTimeout(()=>{
this.disabled = false;
}, 500);
this.props.onPress && this.props.onPress(...args);
}
}
export class ButtonHighLight extends PreventDoubleTap {
render() {
return (
<TouchableHighlight
{...this.props}
onPress={this.onPress}
underlayColor="#f7f7f7"
/>
);
}
}
It can be other touchable component like TouchableOpacity.
If you are using react navigation then use this format to navigate to another page.
this.props.navigation.navigate({key:"any",routeName:"YourRoute",params:{param1:value,param2:value}})
The StackNavigator would prevent routes having same keys to be pushed in the stack again.
You could write anything unique as the key and the params prop is optional if you want to pass parameters to another screen.
The accepted solution works great, but it makes it mandatory to wrap your whole component and to import lodash to achieve the desired behavior.
I wrote a custom React hook that makes it possible to only wrap your callback:
useTimeBlockedCallback.js
import { useRef } from 'react'
export default (callback, timeBlocked = 1000) => {
const isBlockedRef = useRef(false)
const unblockTimeout = useRef(false)
return (...callbackArgs) => {
if (!isBlockedRef.current) {
callback(...callbackArgs)
}
clearTimeout(unblockTimeout.current)
unblockTimeout.current = setTimeout(() => isBlockedRef.current = false, timeBlocked)
isBlockedRef.current = true
}
}
Usage:
yourComponent.js
import React from 'react'
import { View, Text } from 'react-native'
import useTimeBlockedCallback from '../hooks/useTimeBlockedCallback'
export default () => {
const callbackWithNoArgs = useTimeBlockedCallback(() => {
console.log('Do stuff here, like opening a new scene for instance.')
})
const callbackWithArgs = useTimeBlockedCallback((text) => {
console.log(text + ' will be logged once every 1000ms tops')
})
return (
<View>
<Text onPress={callbackWithNoArgs}>Touch me without double tap</Text>
<Text onPress={() => callbackWithArgs('Hello world')}>Log hello world</Text>
</View>
)
}
The callback is blocked for 1000ms after being called by default, but you can change that with the hook's second parameter.
I have a very simple solution using runAfterInteractions:
_GoCategoria(_categoria,_tipo){
if (loading === false){
loading = true;
this.props.navigation.navigate("Categoria", {categoria: _categoria, tipo: _tipo});
}
InteractionManager.runAfterInteractions(() => {
loading = false;
});
};
Did not use disable feature, setTimeout, or installed extra stuff.
This way code is executed without delays. I did not avoid double taps but I assured code to run just once.
I used the returned object from TouchableOpacity described in the docs https://reactnative.dev/docs/pressevent and a state variable to manage timestamps. lastTime is a state variable initialized at 0.
const [lastTime, setLastTime] = useState(0);
...
<TouchableOpacity onPress={async (obj) =>{
try{
console.log('Last time: ', obj.nativeEvent.timestamp);
if ((obj.nativeEvent.timestamp-lastTime)>1500){
console.log('First time: ',obj.nativeEvent.timestamp);
setLastTime(obj.nativeEvent.timestamp);
//your code
SplashScreen.show();
await dispatch(getDetails(item.device));
await dispatch(getTravels(item.device));
navigation.navigate("Tab");
//end of code
}
else{
return;
}
}catch(e){
console.log(e);
}
}}>
I am using an async function to handle dispatches that are actually fetching data, in the end I'm basically navigating to other screen.
Im printing out first and last time between touches. I choose there to exist at least 1500 ms of difference between them, and avoid any parasite double tap.
You can also show a loading gif whilst you await some async operation. Just make sure to tag your onPress with async () => {} so it can be await'd.
import React from 'react';
import {View, Button, ActivityIndicator} from 'react-native';
class Btn extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
}
}
async setIsLoading(isLoading) {
const p = new Promise((resolve) => {
this.setState({isLoading}, resolve);
});
return p;
}
render() {
const {onPress, ...p} = this.props;
if (this.state.isLoading) {
return <View style={{marginTop: 2, marginBottom: 2}}>
<ActivityIndicator
size="large"
/>
</View>;
}
return <Button
{...p}
onPress={async () => {
await this.setIsLoading(true);
await onPress();
await this.setIsLoading(false);
}}
/>
}
}
export default Btn;
My implementation of wrapper component.
import React, { useState, useEffect } from 'react';
import { TouchableHighlight } from 'react-native';
export default ButtonOneTap = ({ onPress, disabled, children, ...props }) => {
const [isDisabled, toggleDisable] = useState(disabled);
const [timerId, setTimerId] = useState(null);
useEffect(() => {
toggleDisable(disabled);
},[disabled]);
useEffect(() => {
return () => {
toggleDisable(disabled);
clearTimeout(timerId);
}
})
const handleOnPress = () => {
toggleDisable(true);
onPress();
setTimerId(setTimeout(() => {
toggleDisable(false)
}, 1000))
}
return (
<TouchableHighlight onPress={handleOnPress} {...props} disabled={isDisabled} >
{children}
</TouchableHighlight>
)
}

Categories

Resources