I'm trying to test a react component and use expect(elm).not.toBeVisible() without success.
Update 3
I have cut down the code into this simpler form:
// ./TestItem.js
import React from 'react'
import './TestItem.css'
export default ({ hide }) => {
return <div className={hide ? 'shouldHide' : ''}>Text</div>
}
// ./__tests__/TestItem.test.js
import React from 'react'
import { render } from 'react-testing-library'
import TestItem from '../TestItem'
import 'jest-dom/extend-expect'
import 'react-testing-library/cleanup-after-each'
test.only('TestItem should render correctly', async () => {
const { getByText, debug } = render(<TestItem hide={true} />)
const itemNode = getByText('Text')
debug()
expect(itemNode).not.toBeVisible()
})
// ./TestItem.css
.shouldHide {
display: none;
}
Test result:
TestItem should render correctly
expect(element).not.toBeVisible()
Received element is visible:
<div class="shouldHide" />
7 | const itemNode = getByText('Text')
8 | debug()
> 9 | expect(itemNode).not.toBeVisible()
| ^
10 | })
11 |
debug() log:
console.log node_modules/react-testing-library/dist/index.js:58
<body>
<div>
<div
class="shouldHide"
>
Text
</div>
</div>
</body>
Update 2:
Okay it's getting pretty weird because I got the test to pass on codesanbox but still find no luck on my local machine.
My original question:
I use React, semantic-ui-react and react-testing-library.
Here is the code:
// ComboItem.test.js
import React from 'react'
import ComboItem from '../ComboItem'
import { render } from 'react-testing-library'
import comboXoi from '../images/combo-xoi.jpg'
import 'path/to/semantic/semantic.min.css'
describe('ComboItem', () => {
test('should render', async () => {
const { getByText, debug } = render(
<ComboItem image={comboXoi} outOfStock={false} />
)
const outOfStockNotice = getByText('Out of stock')
debug()
expect(outOfStockNotice).not.toBeVisible()
})
})
// ComboItem.js
import React from 'react'
import { Card, Image } from 'semantic-ui-react'
export default ({ image, outOfStock = false }) => {
return (
<Card>
<Image
src={image}
dimmer={{
active: outOfStock,
inverted: true,
'data-testid': 'combo-item-dimmer',
content: (
<span style={{ marginTop: 'auto', color: 'black' }}>
Out of stock
</span>
),
}}
/>
</Card>
)
}
What i get is the result here:
ComboItem › should render
expect(element).not.toBeVisible()
Received element is visible:
<span style="margin-top: auto; color: black;" />
at Object.test (src/app/screens/App/screens/SaleEntries/screens/CreateSaleEntry/screens/StickyRiceComboSelect/__tests__/ComboItem.test.js:14:34)
at process._tickCallback (internal/process/next_tick.js:68:7)
I have tried to see the component render result on the browser and the node outOfStockNotice in the test code is actually hidden because its parent, which is a div with class dimmer has style display: none.
According to jest-dom doc (which is used by testing-react-library:
toBeVisible
An element is visible if all the following conditions are met:
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
it does not have its css property opacity set to 0
its parent element is also visible (and so on up to the top of the DOM tree)
Please help. I really don't know what could go wrong here.
Update:
I include the result of debug() here:
console.log node_modules/react-testing-library/dist/index.js:58
<body>
<div>
<div
class="ui card"
>
<div
class="ui image"
>
<div
class="ui inverted dimmer"
data-testid="combo-item-dimmer"
>
<div
class="content"
>
<span
style="margin-top: auto; color: black;"
>
Out of stock
</span>
</div>
</div>
<img
src="combo-xoi.jpg"
/>
</div>
</div>
</div>
</body>
Here is the answer according to the author of react-testing-library himself:
Probably a JSDOM limitation (in codesandbox it runs in the real browser). Actually, the problem is that the css isn't actually loaded into the document in JSDOM. If it were, then that would work. If you can come up with a custom jest transform that could insert the css file into the document during tests, then you'd be set.
So this would work if you were using CSS-in-JS.
So basically the import './TestItem.css' part in the test will not works because JSDOM doesn't load it, therefore jest-dom could not understand the class shouldHide means display: none.
Update:
According to this Stack Overflow thread, you can insert css into jsdom:
import React from 'react'
import { render } from 'react-testing-library'
import TestItem from '../TestItem'
import fs from 'fs'
import path from 'path'
test.only('TestItem should render correctly', async () => {
const cssFile = fs.readFileSync(
path.resolve(__dirname, '../TestItem.css'),
'utf8'
)
const { container, getByText, debug } = render(<TestItem hide={true} />)
const style = document.createElement('style')
style.type = 'text/css'
style.innerHTML = cssFile
container.append(style)
const itemNode = getByText('Text')
debug()
expect(itemNode).not.toBeVisible()
})
And then the test should pass.
Related
I've created a React (functional) Accordion component that has its contents fed in through the parent. The code is as follows:
import React, {useEffect, useRef, useState} from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faChevronUp, faTrashAlt } from '#fortawesome/free-solid-svg-icons';
import './Accordion.css';
const Accordion = ({title, content, active}) => {
const [isActive, setIsActive] = useState(active);
const [expanderStyle, setExpanderstyle] = useState({maxHeight: 0});
const contentDiv = useRef();
useEffect(() => {
setExpanderstyle(isActive ? {maxHeight: contentDiv.current.scrollHeight + 1} : {maxHeight: 0});
}, [isActive]);
const toggle = () => {
setIsActive(!isActive);
}
return (
<div className="accordion">
<div className="accordion-title">
<div>
<button className="btn btn-icon round small" onClick={toggle}><FontAwesomeIcon icon={faChevronUp} className={`icon rotator ${isActive ? "active" : ""}`}/></button>
</div>
<div className="accordion-title--title">
{title}
</div>
</div>
<div ref={contentDiv} className="accordion-content" style={expanderStyle}>
{content}
</div>
</div>
);
}
export default Accordion;
And I have set the styling for my .accordion-content as follows
.accordion-content {
...
overflow: hidden;
transition: max-height 0.4s linear;
}
Which creates a sliding animation for when the user expands / collapses the accordion.
The issue I'm facing is that, since the content is fed in through the parent, if I add in anything that changes the height of the accordion, the height doesn't get updated automatically.
Obviously, I could change the max-height to a large number to show everything, but, then, the animation effect changes dramatically, so I do need to calculate the content's height correctly.
I tried updating my useEffect to include content or contentDiv as part of its dependencies, but it seems the useEffect fires too soon and still doesn't size correctly.
How can I fix this?
I'm currently trying to animate a div so that it slides from bottom to top inside a card.
The useMeasure hook is supposed to give me the height of the wrapper through the handler I attached to it : <div className="desc-wrapper" {...bind}>
Then I am supposed to set the top offset of an absolutely positionned div to the height of its parent and update this value to animate it.
The problem is that when logging the bounds returned by the useMeasure() hook, all the values are at zero...
Here is a link to production exemple of the panel not being slided down because detected height of parent is 0 : https://next-portfolio-41pk0s1nc.vercel.app/page-projects
The card component is called Project, here is the code :
import React, { useEffect, useState } from 'react'
import './project.scss'
import useMeasure from 'react-use-measure';
import { useSpring, animated } from "react-spring";
const Project = (projectData, key) => {
const { project } = projectData
const [open, toggle] = useState(false)
const [bind, bounds] = useMeasure()
const props = useSpring({ top: open ? 0 : bounds.height })
useEffect(() => {
console.log(bounds)
})
return (
<div className="project-container">
<div className="img-wrapper" style={{ background: `url('${project.illustrationPath}') no-
repeat center`, backgroundSize: project.portrait ? 'contain' : 'cover' }}>
</div>
<div className="desc-wrapper" {...bind} >
<h2 className="titre">{project.projectName}</h2>
<span className="description">{project.description}</span>
<animated.div className="tags-wrapper" style={{ top: props.top }}>
</animated.div>
</div>
</div>
);
};
export default Project;
Is this a design issue from nextJS or am I doing something wrong ? Thanks
I never used react-use-measure, but in the documentations, the first item in the array is a ref and you are suppose to use it this way.
function App() {
const [ref, bounds] = useMeasure()
// consider that knowing bounds is only possible *after* the view renders
// so you'll get zero values on the first run and be informed later
return <div ref={ref} />
}
You did...
<div className="desc-wrapper" {...bind} >
Which I don't think is correct...
I've been trying to use gatsby-plugin-scroll-reveal which uses Sal.js to animate a hero section on my site. I'm trying to make it so that the text in the hero fades in then fades out as you scroll down the page. Right now, I can only get it to fade in. How can I make that happen with Sal.js or another way?
I also tried a different way by creating a component that uses IntersectionObserver DOM API but I couldn't get that to work really.
Here's the component:
import React from 'react';
import ReactDOM from 'react-dom';
function FadeInSection(props) {
const [isVisible, setVisible] = React.useState(true);
const domRef = React.useRef();
React.useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => setVisible(entry.isIntersecting));
});
observer.observe(domRef.current);
return () => observer.unobserve(domRef.current);
}, []);
return (
<div
className={`fade-in-section ${isVisible ? 'is-visible' : ''}`}
ref={domRef}
>
{props.children}
</div>
);
}
export default FadeInSection
I figured out a solution from this article:
https://markoskon.com/scroll-reveal-animations-with-react-spring/
So, I'm using the react-spring to create reveal animations on scroll and react-visibility-sensor to see if the I want animated element is visible.
// App.js
import React from "react";
import { Spring } from "react-spring/renderprops";
import VisibilitySensor from "react-visibility-sensor";
<VisibilitySensor once>
{({ isVisible }) => (
<Spring delay={100} to={{ opacity: isVisible ? 1 : 0 }}>
{({ opacity }) => <h1 style={{opacity}}>Title</h1>}
</Spring>
)}
</VisibilitySensor>
Here is my code:
import React, {Component} from 'react';
import * as mobx from 'mobx';
import * as mobxReact from 'mobx-react';
import classNames from 'classnames';
import './CssClassApp.css';
#mobxReact.observer
export class CssClassApp extends Component {
#mobx.observable.ref clapping: boolean = false;
#mobx.action.bound
startAnimate() {
this.animating = true;
setTimeout(() => this.stopAnimate(), 2000);
};
#mobx.action.bound
stopAnimate() {
console.log(`Stopping animation`)
this.animating = false;
};
render() {
return (
<div>
<input
className="button"
type="button"
value="Test"
onClick={this.startAnimate}
/>
<div style={{transition: `border 1500ms ease-out`}}
className={classNames('normal',
{'on': this.animating})}>
Testing timeout
</div>
</div>
);
}
}
and related css
.on {
border: 5px solid red;
}
.normal {
height: 100px;
widows: 100px;
}
It works fine.
But if I change render method to render = () => the border does not fade in at all.
Why? What is causing this error here: react, mobx or typescript?
Using render = () => {} your this in {'on': this.animating})}> isn't bound to the same scope anymore! Thus, your UI behaves differently.
You have to find out how to bind this correctly according to your needs. Or you just leave the syntax as is, as there shouldn't be any need to change the method's syntax at all.
This could be a good source of information:
https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881
The basic idea is to produce jQuery's slideToggle() animation in reactjs.
Hiding an element and showing it based on its state is fairly straightforward, but actually animating the height, so it looks like it's sliding up and down, seems to be more complex than I thought in reactjs. I've googled around for this type of animation and cannot find anything.
The closest I've found is people saying use the "max-height" css property and animate with that, however, that requires you to set a max-height on all divs you want to animate. And with responsive content this is just not the right way to go. On one screen the max height needed might be 200, but on mobile maybe 500!
Here is where I am so far, I can easily collapse/expand a component with the state like I said, but how do I expand this to actually animate? And handle mid animation clicks, so it goes back when needed?
The height-0 css class is just this:
.height-0 {
overflow: hidden;
max-height: 0;
}
import React, { Component, PropTypes } from 'react';
export default class CollapsableComponent extends Component {
constructor(props) {
super(props);
this.state = {
collapsed: false
};
}
toggleCollapse(){
this.setState({
...this.state,
collapsed: this.state.collapsed ? false : true;
});
}
render() {
return (
<div class="row">
<div class="col-sm-12">
<h2>Some Title....
<button class="btn btn-default pull-right" onClick={this.toggleCollapse}>
<span class={`fa fa-${collapsed ? 'expand' : 'compress'}`} aria-hidden="true"/>
</button>
</h2>
<div class={`animation-holder${collapsed ? ' height-0' : ''}`} ref={(div) => { this.holderDiv = div;}}>
<p>content here......</p>
</div>
</div>
</div>
);
}
}
The simplest way I can think of is the following: Sandbox
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [isOpen, setIsOpen] = useState(false);
const style = {
overflow: "hidden",
height: isOpen ? 50 : 0,
transition: "2s"
};
return (
<div className="App">
<div style={style}>
<p> Let me slide in and out!</p>
</div>
<button onClick={() => setIsOpen(prev => !prev)}>Slide Toggle</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If the want the actual height of the component you could retrieve it with the use of the useRef hook like this: ref.current.clientHeight.