How to test condition rendered elements with react testing library - javascript

I'm trying to cover my functionality with test, i have the image input:
<input
data-testid="fileuploadtestid"
type="file"
accept="image/*"
capture="camera"
onChange={uploadImage}
/>
On change method checks if file provided and creates the link;
const uploadImage = (e) => {
const { files } = e.target;
if (files?.length) {
const url = URL.createObjectURL(files[0]);
setImageUrl(url);
} else {
setImageUrl("");
}
};
Based on imageUrl value image renders conditionally:
{imageUrl && (
<img
data-testid="prevtestid"
className="preview_img"
src={imageUrl}
alt={imageUrl}
crossOrigin="anonymous"
ref={imageRef}
/>
)}
And i want to check if it renders correctly using react testing library to get the image element I use data-testid="prevtestid":
test("renders learn react link", async () => {
const { container, rerender } = render(<Home />);
global.URL.createObjectURL = jest.fn();
const file = new File(["(⌐□_□)"], "pug.png", { type: "image/png" });
const input = screen.getByTestId("fileuploadtestid");
fireEvent.change(input, { target: { files: [file] } });
rerender(<Home />);
expect(screen.getByTestId("prevtestid")).toBeInTheDocument();
}
);
I attached the latest version what have i tried to implement, i tried to use waitFor, rerender, render in various ways but i'm getting the same error all time:
Unable to find an element by: [data-testid="prevtestid"].
Please tell me how can i test it ?

The mock that you are creating is not really returning anything. You should return a url so that the components get's an imageUrl and is then able to show the <img /> element.
global.URL.createObjectURL = jest.fn().mockReturnValueOnce('https://www.samplerandomdomain.com/images/123.jpg');

Related

How can I get URI from ApexCharts with React Js

I am trying to get the chart URI by using the Data URI method. I've seen a bunch of examples using Apexchart Js to get pdf like this CodePen from ApexChart, when trying to reproduce it on react I got TypeError: Cannot read property 'type' of undefined
Here is my component did mount look like this:
componentDidMount() {
console.log(this.state.loading);
if (this.state.loading === false) {
const {options} = this.state;
const node = ReactDOM.findDOMNode(this);
if (node instanceof HTMLElement) {
var chart = new ApexCharts(node.querySelector('.charty'), options);
chart.render().then(() => {
setTimeout(function() {
chart.dataURI().then(uri => {
console.log(uri);
});
}, 4000);
});
}
} else {
return;
}
}
Type is defined as well in my state and in the render like so :
<div className='rfe'>
<div className='rfea'>
From {this.state.AxisMonth[0]} to{' '}
{this.state.AxisMonth[this.state.AxisMonth.length - 1]}
</div>
<Chart
className='charty'
type='area'
options={this.state.options}
series={this.state.series}
width='1000'
height='380'
/>
</div>
</div>
here is the error I got
Really need help with this.
You can use the exec function to call any method of ApexCharts from a React component.
getDataUri() {
ApexCharts.exec("basic-bar", "dataURI").then(({ imgURI, blob }) => {
console.log(imgURI);
});
}
Here's a full codesandbox example

NextJS: Images loaded from cache don't trigger the onLoad event

I am currently in the process of converting a ReactJS client side rendered application to a NextJS application for search engine optimization and social media linking reasons.
One of the components converted, which is basically an image that waits until it's finished loading then fades in, is not working as expected after it is used in a NextJS environment.
It behaves in the following manner:
Cache Enabled:
The first time the image loads and the onLoad event triggers thus showing the image.
The second time the image stays hidden because the onLoad event doesn't trigger when the image is loaded from cache.
Cache Disabled using devtools:
The onLoad event always works because the image is never served from cache.
Expected behavior and the behavior previously achieved with using just ReactJS:
The onLoad event should trigger whether the image was loaded from the cache or not.
When not using React this problem is usually caused when someone sets the images src before defining a onload function:
let img = new Image()
img.src = "img.jpg"
img.onload = () => console.log("Image loaded.")
Which should be:
let img = new Image()
img.onload = () => console.log("Image loaded.")
img.src = "img.jpg"
Here is simplified code that causes the same problem in NextJS:
import React, { useState } from "react"
const Home = () => {
const [loaded, setLoaded] = useState(false)
const homeStyles = {
width: "100%",
height: "96vh",
backgroundColor: "black"
}
const imgStyles = {
width: "100%",
height: "100%",
objectFit: "cover",
opacity: loaded ? 1 : 0
}
const handleLoad = () => {
console.log("Loaded")
setLoaded(true)
}
return (
<div className="Home" style={homeStyles}>
<img alt=""
onLoad={handleLoad}
src="https://images.unsplash.com/photo-1558981001-5864b3250a69?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"
style={imgStyles}
/>
</div>
)
}
export default Home
I ended up using ImageObject.complete as a workaround thanks to someone's suggestion.
I used useRef to reference the image and checked if the image.current.complete === true on component mount.
Here is the code:
import React, { useEffect, useRef, useState } from "react"
const Home = () => {
const [loaded, setLoaded] = useState(false)
const image = useRef()
const homeStyles = {
width: "100%",
height: "96vh",
backgroundColor: "black"
}
const imgStyles = {
width: "100%",
height: "100%",
objectFit: "cover",
opacity: loaded ? 1 : 0
}
const handleLoad = () => setLoaded(true)
useEffect(() => {
if (image.current.complete) setLoaded(true)
}, [])
return (
<div className="Home" style={homeStyles}>
<img alt=""
ref={image}
onLoad={handleLoad}
src="https://images.unsplash.com/photo-1558981001-5864b3250a69?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"
style={imgStyles}
/>
</div>
)
}
export default Home
From Next.js v11.0.2-canary.4 onwards, one can directly use onLoadingComplete prop.
<Image src={src} onLoadingComplete={() => setLoaded(true)} />
How does this compare with other options??
You get to keep using the awesome next/image component instead of unoptimized img tag without any extra code or dealing with refs yourself.
If you are using the new Image component from nextjs 10, which does not support a forward ref, you can modify the workaround with
const [loaded, setLoaded] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if ((ref.current?.firstChild?.firstChild as HTMLImageElement | undefined)?.complete) {
setLoaded(true)
}
}, [])
return <div ref={ref}>
<Image src={src}
onLoad={() => setLoaded(true)}/>
</div>
I only want to add to #mohamed-seif-khalid answer that I had to add a check in useEffect if ref consist anything, because my render continued to break.
useEffect(() => {
if (image.current && image.current.complete) {
handleImageLoad()
}
}, []);

How to show an image when Chrome is offline

I'm trying to show an image when the Chrome browser is offline, and when it's online show the webpage.
I transferred the image to base64 data and tried to load it in the img tag, however the base64 data is too large.
Is there a way to show an image when the browser is offline?
import imageToBase64 from "image-to-base64";
const Home = () => {
const [isOnline, setIsOnline] = useState(true);
// Checks to see if the browser has internet connection or not
window.addEventListener("online", () => setIsOnline(true));
window.addEventListener("offline", () => setIsOnline(false));
//Link to the image
const idleImgUrl = `${window.location.href}${coffeeMachine}`;
//convert image to base64 and save to local storage
imageToBase64(idleImgUrl)
.then(res => {
window.localStorage.setItem("idleImgData", res);
})
.catch(err => {
console.log(err);
});
return (
isOnline
? (<div>The web page to show</div>)
:
// <p> tag shows
<p>The browser is offline now</p>
// img tag does not show
(<img src={window.localStorage.getItem("idleImgData"} />)
);
};
Any help would be appreciated...
The trick is to load the image while the user agent still has an internet connection. The image won't be downloaded until you render the <img> tag. The cached image can then be displayed without issue later.
I wrote a short create-react-app example to illustrate.
import React, { useState, useEffect, useCallback } from 'react';
const App = () => {
const [online, setOnline] = useState(true);
const onlineListener = useCallback(() => setOnline(true), [setOnline]);
const offlineListener = useCallback(() => setOnline(false), [setOnline]);
useEffect(() => {
window.addEventListener('online', onlineListener);
window.addEventListener('offline', offlineListener);
return () => {
window.removeEventListener('online', onlineListener);
window.removeEventListener('offline', offlineListener);
};
}, [onlineListener, offlineListener]);
return (
<div className="App">
<img
style={online ? { display: 'none' } : undefined}
src="TODO"
alt="no internet"
/>
</div>
);
};
export default App;
It displays an image when the user agent loses connection and hides it again when connection is restored. It obviously won't work if connection is cut to begin with, but in such a case how did the user load your application 🤔?
If you are offline, you might not even be able to load your react bundle.js file in the first place and there is nothing you can do about it.
Also, I don't see the advantage of keeping it in your localStorage in this case. Browsers are probably going to cache it anyway, if size matters here.
If the user was able to load your bundle, you can just store the b64 hardcoded as a variable directly in your bundle or initiate an ajax on componentDidMount (using useEffect since you use hooks).
const Home = () => {
const [base64Img, setBase64Img] = useState();
const [isOnline, setIsOnline] = useState(true);
const setOnline = () => setIsOnline(true);
const setOffline = () => setIsOnline(false);
useEffect(() => {
initiateB64Retrieval().then((b64) => setBase64Img(b64));
window.addEventListener('online', setOnline);
window.addEventListener('offline', setOffline);
return () => {
window.removeEventListener('online', setOnline);
window.removeEventListener('offline', setOffline);
}
}, [])
...
}
Always good practice to remove your event listeners. Note that you cannot remove event listeners if passed with anonymous functions or when using .bind() as it creates another reference for the function.

How do I store a state that I have set to use in other components (so that I can then send that component's value to a database)

I'm trying to store the this.state.url that I'm getting back from firebase after I successfully store an image to firebase. Right now now I am console logging the response and am able to render the response url as an image on the page. I want to be able to some how store that url as a string so that I can send it to a separate SQL database I have set up. Thanks for your help ! :)
I must not understand the way props work because I created another prop but must be unsuccessfully linked them. I really just need the string to be sent to the SQL database via axios so if there is a way to do that in this component that would be great - or if the only way to do it is via another component then that works too. Right now Im trying to set the this.state.url as a const so that I can refer to it in another component and it doesnt seem to be working.
import {storage} from '../firebase';
class ImageUpload extends Component {
constructor(props) {
super(props);
this.state = {
image: null,
url: '',
progress: 0
}
this.handleChange = this
.handleChange
.bind(this);
this.handleUpload = this.handleUpload.bind(this);
}
handleChange = e => {
if (e.target.files[0]) {
const image = e.target.files[0];
this.setState(() => ({image}));
}
}
handleUpload = () => {
const {image} = this.state;
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on('state_changed',
(snapshot) => {
// progrss function ....
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
this.setState({progress});
},
(error) => {
// error function ....
console.log(error);
},
() => {
// complete function ....
storage.ref('images').child(image.name).getDownloadURL().then(url => {
console.log(url);
this.setState({url});
})
});
}
render() {
const style = {
height: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
};
return (
<div style={style}>
<progress value={this.state.progress} max="100"/>
<br/>
<input type="file" onChange={this.handleChange}/>
<button onClick={this.handleUpload}>Upload</button>
<br/>
<img UserUpload={this.state.url || 'http://via.placeholder.com/300x300'} alt="Uploaded images" height="300" width="300"/>
</div>
// const UserUpload = this.state.url
)
}
}
export default ImageUpload;```
Right now I'm just rendering the url via an img src to the page and console.log'ing the url response i get from firebase.
I am super thankful for any help you all give - I'm extremely new to coding and appreciate any help I can get!

Testing changes to React component state and spying on instance methods using enzyme

I am working on a wrapper component for smoothly loading images in React. I use enzyme with mocha, chai and sinon to unit test my component. In the test here, I am trying to test that the:
component's state is updated when the image has loaded
the onLoad instance method on the component was called
const wrapper = shallow( );
const onLoad = wrapper.find('img').props().onLoad;
const onLoadSpy = sinon.spy(onLoad); wrapper.update();
const status = wrapper.state().status;
expect(onLoadSpy).to.have.been.called;
expect(status).to.equal('LOADED');
I find that neither the update to the state is reflected by enzyme or the call count of the onLoad spy is updated. This is the corresponding code for the test:
export default class Image extends Component {
constructor(props) {
super(props);
if (props.src != null && typeof props.src === 'string') {
this.state = {
status: LOADING,
};
} else {
this.state = {
status: PENDING,
};
}
this.onLoad = this.onLoad.bind(this);
}
onLoad() {
this.setState({
status: LOADED,
});
}
render() {
//lots of code above the part we care about
const defaultImageStyle = style({
opacity: 0,
transisition: 'opacity 150ms ease',
});
const loadedImageStyle = style({
opacity: 1,
});
let imageStyle = defaultImageStyle;
if (this.state.status === LOADED) {
imageStyle = merge(defaultImageStyle, loadedImageStyle);
} else {
imageStyle = defaultImageStyle;
}
let image;
if (alt != null) {
image = (<img
className={imageStyle}
src={src}
width={width}
height={height}
alt={alt}
onLoad={this.onLoad}
/>);
} else {
image = (<img
className={imageStyle}
src={src}
width={width}
height={height}
role="presentation"
onLoad={this.onLoad}
/>);
}
let statusIndicator = null;
if (this.state.status === LOADING) {
statusIndicator = (<div className={loadingStyle}></div>);
}
return (<div className={wrapperStyle}>
{statusIndicator}
{image}
</div>);
}}
To take a look at the full code for better context:
the source here
the test here
One can test this without relying on sinon. By expecting that the onLoad and onFire event listeners are invoked,the tests check if the img fires the load and error events.
Instead,simulate img's events using enzyme and check that the appropriate state transition occurs:
it('has a state of LOADED if a good src prop is supplied', () => {
const wrapper = shallow(<Image
src="anyString.jpg"
width={300}
height={300}
/>);
const img = wrapper.find('img');
img.simulate('load');
const status = wrapper.state().status;
expect(status).to.equal('LOADED');
});
This also eliminates the need to mount the component. The updated tests can be found here.
The main issue I see with this approach is that the state is an internal thing, and not something that should be known outside the component. Now you are leaking state information ("status" in this case) out into the tests.
Doing this means you are not doing "black-box testing", which is the most valuable type of tests. You are leaking the implementation details of the component. In other words "Encapsulation" should be highly considered.
There are perhaps better ways to test this. You could for instance export a presentational component as well, which takes the parts of the state you need to test as props. Or look for an element that would be rendered when status is "LOADED" with enzyme find method.

Categories

Resources