How to test the style of a div within a React component - reactjs

I have a toggle button that shows and hides a div in my component. Rather new to testing and not sure how to test the toggle. Essentially when studentDisplay is false I have a style that sets the display of a div to none. When studentDisplay is true the style is set to display block. Any suggestions and or ideas would be greatly appreciated.
Method in component I would like to test
import React, {useState} from 'react';
export default function App() {
const [studentDisplay, setStudentDisplay] = useState(false);
function handleStudentDisplay() {
setStudentDisplay(!studentDisplay);
}
return (
<div className="App">
<button onClick={handleStudentDisplay}>
<span>Student Name</span>
</button>
<div style={studentDisplay ? {display:'block'} : {display:'none'}}>
Student
</div>
</div>
);
}
Test example
import React from 'react';
import renderer from 'react-test-renderer';
import { render, fireEvent, act } from '#testing-library/react';
describe('Student Toggle', () => {
it('should display student', () => {
const eventHandler = jest.fn();
const { getByRole } = render(<button onClick={eventHandler}/>);
act(() => {
const button = getByRole('button');
fireEvent.click(button);
});
expect(eventHandler).toHaveBeenCalled();
//how do I test? <div style={studentDisplay ? {display:'block'} : {display:'none'}}>
});
});

You can use expect from RTL. Here is an example to test the display property of an element, you can add a className or fetch using id or element,
const TargetElement = document.getElementsByClassName('button-class-name');
const style = window.getComputedStyle(TargetElement[0]);
expect(style.display).toBe('none');
expect(style.display).toBe('block');
For more information on testing style of react elements, maybe even using styled components, here is a very informative article by Ilya Zykin.

Related

How can I add a class to an element when the scroll exceeds some value?

I am trying to add a class to an element when the scroll pixel exceeds 2px, but it isn't working. Please what is it not working?
className={`arrowup ${window.pageYOffset >= 2 ? "showscroll":null}`}
Add an onScroll event to your div that you want to make scrollable and attach a reference to it using useRef hook. Everytime your div scrolls, fire a function handleScroll and set your scroll state accordingly. You will need to set some height and give overflow-y:scroll to your scrollable div to actually see it. This should help you out.
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [scroll, setScroll] = useState(false);
const divRef = React.useRef();
const handleScroll = (e) => {
const scrolledFromTop = divRef.current.scrollTop;
console.log(scrolledFromTop);
setScroll(scrolledFromTop > 2);
};
return (
<div className="App">
<div className={scroll ? "showscroll" : ""}>toggle class here</div>
<div className="area" onScroll={handleScroll} ref={divRef}>
</div>
</div>
);
}

Shallow from Enzyme not rendering the jxs component and just returns an empty object

I have this unit test here :
import React from 'react';
import { shallow } from 'enzyme';
import Header from '../components/Header';
import FilterBtn from '../components/FilterBtn';
import SearchBar from '../components/SearchBar';
describe('Render Header', () => {
const props = {
restaurantData: {},
showSideMenu: false,
}
it('should render Header correctly', () => {
const wrapper = shallow(<Header {...props} />);
expect(wrapper.find('header__container')).toHaveLength(1);
expect(wrapper.find('img')).toHaveLength(1);
expect(wrapper.find(<FilterBtn />)).toHaveLength(1);
expect(wrapper.find(<SearchBar />)).toHaveLength(1);
});
});
Whenever I run and npm run test this is what I get back:
The shallow keeps just returning and empty {}
This is what the header comp looks like :
export default function Header(props) {
return (
<div className="header__container">
<img src={logo} alt="logo" className="header__logo" />
<div className="header__search">
<FilterBtn
toggleSort={props.toggleSort}
sortRestaurants={props.sortRestaurants}
restaurantData={props.restaurantData}
/>
<SearchBar
handleResults={props.handleResults}
showSideMenuToggle={props.showSideMenuToggle}
showSideMenu={props.showSideMenu}
/>
</div>
</div>
)
};
Why is it just returning an empty {} and not the component? I've tried mount as well doesn't work either. Any insight would be great.
You're missing a dot in your selector. In this case, the .head__container refers to a class name. See examples below:
const Header = () => (
<div
data-testid="header_data_id"
id="header_id"
className="header_class"
>
test
</div>
);
const wrapper = shallow(<Header />);
// className
expect(wrapper.find(".header_class")).toHaveLength(1);
// id
expect(wrapper.find("#header_id")).toHaveLength(1);
// HTML element
expect(wrapper.find("div")).toHaveLength(1);
// HTML attribute
expect(wrapper.find("[data-testid='header_data_id']")).toHaveLength(1);
// React component
expect(wrapper.find(Header)).toHaveLength(1);
// React component display name
// this is not very consistent and may/may not have a different display name
// use the above selectors first before using this one
expect(wrapper.find("Header")).toHaveLength(1);
Do not use JSX as selectors! View the enzyme selector docs for supported queries.

How to focus and select a checkbox using React ref?

I have been looking around a method to correctly focus and select a checkbox in React code.
The methods focus() and select() that I'm using in the example below are not working :
import React, { useRef } from "react";
export const HelloWorld = () => {
const checkboxref = useRef(null);
const handleOnClick = () => {
checkboxref.current.focus();
checkboxref.current.select();
};
return (
<div>
<button onClick={handleOnClick}>Focus</button>
<input type="checkbox" ref={checkboxref} />
</div>
);
};
When I click on the button, my checkbox is not focused and not selected...
Any solution please ?
Thank you so much.
You don't need to create a separate function to handle onChange event
const checkboxref = useRef(null);
You can simply get the current value of the checkbox with:
checkboxref.current.checked
// which returns a boolean
use this one it might help you. here I am using createRef instead of useRef and also uses the callback hook which ensures the availability of ref when you click the button.
import React,{createRef, useCallback} from 'react';
export const HelloWorld = () => {
const checkboxref = createRef();
const handleOnClick = useCallback(() => {
const node = checkboxref.current;
if(node){
node.focus();
node.select();
}
}, [checkboxref]);
return (
<div>
<button onClick={handleOnClick}>Focus</button>
<input type="checkbox" ref={checkboxref} />
</div>
);
};
The select methods selects text in elements such as text inputs and text areas, so I'm not sure what effect you expect it to have on the checkbox. As for focus, it can focus, but again, there is not much you can do with a focused checkbox. I can only think of styling it https://jsfiddle.net/4howanL2/1/

How to properly export a component from a React custom hook and a function to control it?

What I want to do is to create a reusable and convenient way of showing an alert or a confirmation modal.
Using library modals usually require you to import a Modal component and create a state variable and pass it as a prop to the imported component to control its visibility.
What I want to do is to create a custom hook that exports a modal component with all the customization (maybe a wrapper around a Modal component from a library) and a function to toggle the visibility.
Something like below.
This is the hook code:
import React, {useState} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const useModal = () => {
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
const Modal = ({onOK, ...rest}) => (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
return {
on,
toggleModal,
Modal,
}
}
export default useModal
And this is how I use it:
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import useModal from './useModal'
import {Button} from 'antd'
const App = () => {
const {toggleModal, Modal} = useModal()
return (
<div>
<Button type="primary" onClick={toggleModal}>
Open Modal
</Button>
<Modal title="Simple" onOK={() => alert('Something is not OK :(')}>
<p>Modal content...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Here is a sandbox to see it in action and test it out. There are two buttons, one which shows a Modal which is normally imported from the library (here antd) and one that is from a custom hook useModal.
The one form the hook works except it seems something is wrong with it. The appearing transition is working but when you close the modal it suddenly disappears with no transition. It seems the component is immediately destroyed before transitioning out. What am I doing wrong?
If I understand it correct, you want to render a Component and also need a function which can control it (toggle it's visibility).
Though it is not possible the way you are trying to achieve with the react hooks, because on state change you are actually updating your Modal too and that is causing an unmount of the Dialogue from DOM.
You can use below solution to achieve the same result. The Solution uses a component with forwardRef and useImperativeHandle and will achieve a decoupled function which you can use to toggle your dialogue using button click:
NOTE: You need to upgrade to react and react-dom from v-16.7.0-alpha (as in your sandbox code) to latest (16.14.0) [I have not tried other intermediate versions]
Modal Component:
import React, {useState, forwardRef, useImperativeHandle} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const Modal = forwardRef(({onOK, ...rest}, ref) => {
useImperativeHandle(ref, () => ({
toggleModal: toggleModal
}));
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
return (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
});
export default Modal;
And this is how to use it:
import React, {useState, useRef} from 'react'
import ReactDOM from 'react-dom'
import Modal from './ModalWrapper'
import {Button, Modal as AntdModal} from 'antd'
const App = () => {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
const modalRef = useRef()
return (
<div>
<Button type="warning" onClick={() => setOn(true)}>
Normal Import
</Button>
<br />
<br />
<Button type="primary" onClick={() => modalRef.current.toggleModal()}>
From Modal Component
</Button>
<AntdModal visible={on} onOk={toggle} onCancel={toggle}>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
</AntdModal>
<Modal
title="Simple"
ref={modalRef}
onOK={() => alert('Things are now OK :)')}
>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
I hope it will help your use case.
Thanks.

ShallowWrapper is empty when running test

I'm new to testing so I'm trying to add Enzyme to one of my projects. My problem is that when using find(), the ShallowWrapper is empty. Also I'm using Material UI, so I don't know if this is part of the problem.
The component I'm testing
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
const styles = theme => ({
root: {
flexGrow: 1
},
background: {
backgroundColor: "#2E2E38"
},
title: {
color: "#FFE600",
flexGrow: 1
}
});
const NavBar = ({ classes }) => {
return (
<div className={classes.root} data-test="nav-bar">
<AppBar className={classes.background}>
<Toolbar>
<Typography variant="h5" className={classes.title}>
App
</Typography>
</Toolbar>
</AppBar>
</div>
);
};
export default withStyles(styles)(NavBar);
The test
import React from "react";
import { shallow } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
let component = shallow(<NavBar />);
let navbar = component.find("data-test", "nav-bar");
console.log("Log is", component);
expect(navbar).toBe(1);
});
});
Try changing your selector in find(selector) to the following to target the element with data-test="nav-bar". You may need to use dive() to be able to access the inner components of the style component:
import React from "react";
import { shallow } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
const component = shallow(<NavBar />);
// Use dive() to access inner components
const navbar = component.dive().find('[data-test="nav-bar"]');
// Test that we found a single element by targeting length property
expect(navbar.length).toBe(1);
});
});
You can also use an object syntax if you prefer:
const navbar = component.find({'data-test': 'nav-bar'});
Alternatively to using dive(), you could instead mount() the component instead of shallow(), but it depends on your use case:
import React from "react";
import { mount } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
const component = mount(<NavBar />);
// Use dive() to access inner components
const navbar = component.find('[data-test="nav-bar"]');
// Test that we found a single element by targeting length property
expect(navbar.length).toBe(1);
});
});
Hopefully that helps!
I ran into this issue for a different reason where I could not find a SingleDatePicker element. The example in 2. A React Component Constructor from the documentation fixed it for me.
https://enzymejs.github.io/enzyme/docs/api/selector.html#1-a-valid-css-selector
using
wrapper.find(SingleDatePicker).prop('onDateChange')(now);
instead of
wrapper.find('SingleDatePicker').prop('onDateChange')(now);
did the trick for me.

Resources