Testing Style on React component - css

I have the following class with a function, that opens a modal (open_modal(...)) in a separate file to a component as I have a large number of modals that use this functionality.
import open from "open";
import $ from "jquery";
class ReactHelpers {
static open_webpage(page_url) {
open(page_url);
}
static open_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "block");
$(modal_id).css("display", "block");
}
static close_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "none");
$(modal_id).css("display", "none");
}
}
export default ReactHelpers;
I am trying to assert that the open_modal function has added css to the divs in question as below:
it('should close the modal', function () {
const wrapper = shallow(
<div id="overlay_id">
<div id="modal_id">
<p>modal</p>
</div>
</div>
)
const overlay = wrapper.find('#overlay_id')
const modal = wrapper.find('#modal_id')
ReactHelpers.open_modal(overlay, modal);
console.log('OVERLAY ', overlay);
expect(overlay.prop('style')).toHaveProperty('display', 'block');
expect(modal_style).toHaveProperty('display', 'block');
});
Further, I'm sure to how the open_webpage function would be tested as this is a library function. In my other tests in my other components, I'm mocking this so it's never actually been tested.
Any help is greatly appreciated.
Thanks

To test style of dom elements:
You should mount the component (using mount), instead of just creating it (using shallow).
Since you're changing the style of dom element directly, You should test the style of the dom element (component.getDOMNode().style.display), instead of testing the react style property (component.prop.style).
example:
import $ from "jquery";
it("should create a div and changes its color to red", () => {
const wrap = mount(
<div id="red_el"></div>
);
const el = wrap.find("#red_el").getDOMNode()
$(el).css("color", "red");
expect(el.style.color).toEqual("red");
});
In your case:
it("should open modal", () => {
const wrapper = mount(
<div>
<div id="overlay" style={{ display: "none" }}>
<div id="modal" style={{ display: "none" }}>
overlay
</div>
</div>
</div>
);
const overlay = wrapper.find("#overlay").getDOMNode();
const modal = wrapper.find("#modal").getDOMNode();
ReactHelpers.open_modal(overlay, modal);
expect(overlay.style.display).toEqual("block");
expect(modal.style.display).toEqual("block");
});
See it live on codesandbox (switch to the tests tab to run the tests .)

Related

Changing body background image with React

So, I'm trying to create a React App that changes the background image of the body. I did this by giving the body in my index.html an id of "body." I can get this to work with changing the background COLOR just fine. When I try to reassign the background IMAGE, though, I can't seem to get it to work no matter what I try.
This works:
document.getElementById("body").style.backgroundColor = "blue";
This doesn't:
import explosion from "./explosion.png";
function Boom(){
document.getElementById("body").style.backgroundImage = "url('" + {explosion} +
"')";
Why? I've tried writing this many different ways.
this worked for me :
import { useEffect } from "react";
import img from './img.png';
export default function App(){
useEffect(()=>{
document.getElementById('body').style.backgroundImage = `url('${img}')`;
})
return <>
<div id="body"
style={{height:'300px'}}
>
</div>
</>
}
or you can use inline css style :
import img from './img.png';
export default function App(){
return <>
<div id="body"
style={{
height:'300px',
backgroundImage: `url('${img}')`,
}}
>
</div>
</>
}
you need to pass the URL of the image as a string, without wrapping it in curly braces {}
You can try this code
import { useEffect } from "react";
export default function App() {
const bgUrl =
"https://images.unsplash.com/photo-1605106250963-ffda6d2a4b32?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80";
/*useEffect Hook allows you to perform side effects in your components. Such as fetching data, directly updating the DOM, and timers*/
useEffect(() => {
Boom();
}, []);
const Boom = () => {
document.getElementById("body").style.backgroundImage = `url(${bgUrl})`;
};
return (
<div className="App">
<h1>Hello World!</h1>
</div>
);
}
here's a link to the working demo Change Background Image

React component not rendering inline CSS

I am trying to write a react application where the App component renders an array of components to the screen. But the inline CSS are not showing up.
//App component
import data from "./data.js"
import Item from "./Item"
export default function App(){
const cardElements = data.map(item => <Item/>)
return (<div className='app'>
{cardElements}
</div>);
}
//Item component
export default function Item(){
const customStyle = {border: "2px solid black"};
return (<div style={customStyle} >Item component</div>);
}
The inline CSS in the Item component does not reflect on the webpage.
As you can see in the snippet below the inline style does indeed work. What is likely happening here is your style is bering overridden but we'd need more information to know for sure.
Sidenote: don't forget to add key prop when using .map in React.
const data = [1, 2, 3, 4];
function App() {
const cardElements = data.map(item => <Item key={item} />)
return (
<div className='app'>
{cardElements}
</div>
);
}
function Item() {
const customStyle = { border: "2px solid black" };
return <div style={customStyle}>Item component</div>;
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Add css class to 3rd component div by role

How I can (using react way) add a myCustomClass in div by role attribute (presentation)? Component don't expose any way to add my custom class and I dont change all componets (using css way), only this component.
<div class="css-1dozdou">
<div role="presentation" class="myCustomClass css-l0iinn"><div ..
Well, as I understood you, you have some third-party component, so you can't change its code. Then you should add some "magic" to overcome the problem.
First of all you should define a ref. Add it to the troubled component or its parent div as parameter ref={ref}. From now on you have a DOM object. Next you should search for div with role via native JS and add the class.
const App = () => {
const ref = useRef(null);
useEffect(() => {
const divs = ref.current.querySelector('[role="presentation"]');
if (divs.length) {
divs[0].add('myCustomClass');
}
})
return (
<div>
<h1>Change a class of a div inside child</h1>
<div ref={ref}><ThirdPartyComponent /></div>
</div>
);
};
const ThirdPartyComponent = () => {
return (
<div class="css-1dozdou">
<div role="presentation" class="myCustomClass css-l0iinn">
Some component
</div>
</div>
);
};

Add CSS for html selector based on React state?

I'd like to set overflow-y: hidden for the html selector (not an element) based on whether a React class component state variable is true. Is that possible?
If you mean you want to apply the overflow-y to the actual HTML tag then putting this code in the render worked for me
...
render() {
let html = document.querySelector('html');
this.state.test === "test" ? html.style.overflowY = "hidden" : html.style.overflowY = "visible";
return (
....
)
};
You can do
function MyComponent() {
// Set your state somehow
const [something, setSomething] = useState(initialState)
// Use it in your className`
return <div className={!!something && 'class-name'} />
}
If you have multiple class names to work with, a popular package is (aptly named) classnames. You might use it like so:
import cx from 'classnames'
function MyComponent() {
const [something, setSomething] = useState(initialState)
return <div className={cx({
'some-class' : something // if this is truthy, 'some-class' gets applie
})} />
}
Yes, It's possible. You can do this.
function App() {
const [visible, setVisible] = useState(false);
useEffect(() => {
const htmlSelector = document.querySelector("html");
htmlSelector.style.overflowY = visible ? "unset" : "hidden";
}, [visible]);
return (
<button onClick={() => setVisible(prevState => !prevState)}>
Toggle overflow
</button>
);
}
See the full example on CodeSandbox
You can use the style property to set inline CSS:
<div style={{ overflowY: hide ? 'hidden' : 'auto' }}>

Masonry layout with Next.js

I am developing a multi-page website using Next.js for the frontend and Strapi for the backend. On the blog page, the data is dynamically fetched from the database. I am trying to display this data in an adaptive layout using Masonry.
The problem I am having is that on first load, the page displays every grid-item in a single vertical column. On reload, the Masonry layout then takes effect, but with some overlapping items. On a second reload, everything then displays properly and is even responsive. However, if I navigate away from the page and come back, it is back to square one.
Here is the code for the blog page :
import React from 'react';
import Layout from '../components/StaticLayout';
import Link from 'next/link';
import fetch from 'isomorphic-unfetch';
import '../../static/styles.min.css';
let Masonry = '';
export default class Blog extends React.Component {
constructor(props) {
super(props);
this.state = {
appIsMounted: false
}
}
static async getInitialProps() {
const res = await fetch('http://localhost:1337/blogposts');
const data = await res.json();
return {
posts: data
};
}
async componentDidMount() {
let masonry = await import('masonry-layout');
this.setState({
appIsMounted: true
})
}
render() {
return (
<Layout>
<h1 className="blogHeader">Blog</h1>
{this.state.appIsMounted ? (
<div className="grid js-masonry" data-masonry-options='{ "itemSelector": ".grid-item", "columnWidth": 400 }'>
{this.props.posts.map(
post => (
<div key={post.id} className="grid-item">
<Link href="/p/[id]" as={`/p/${post.id}`}>
<img src={`http://localhost:1337${post.Image.url}`} alt={post.Image.name} className="postImage" />
</Link>
<div className="text">
<Link href="/p/[id]" as={`/p/${post.id}`}>
<a className="postLink">{post.Title}</a>
</Link>
<p className="blurb">{post.Content.substring(0, 300) + '...'}</p>
<Link href="/p/[id]" as={`/p/${post.id}`}>
<button className="viewPost">Read More!</button>
</Link>
</div>
</div>
)
)}
</div>
) : null}
</Layout>
)
}
}
To be noted, the StaticLayout script only ensure the header is at the top of each page.
EDIT
So I've solved the overlapping part of the problem. Instead of initializing Masonry in the html, I've added a js file to './static'. On my blog page, I've added the following :
import Head from 'next/head'
...
return(
<Head>
<script type="text/javascript" src="/static/runMasonry.js"></script>
</Head>
...
)
And the runMasonry.js file looks like this :
window.onload = function () {
var elem = document.querySelector('.grid');
var msnry = new Masonry(elem, {
// options
itemSelector: '.grid-item',
columnWidth: 160,
gutter: 20
});
}
But when I first arrive on the page or navigate away and come back, the elements are still displayed in a column and require a reload.
Came up with a non-ideal fix, but it works.
Added the following code to the runMasonry.js file :
window.addEventListener("DOMSubtreeModified", function () {
var elem = document.querySelector('.grid');
if (elem) {
var msnry = new Masonry(elem, {
// options
itemSelector: '.grid-item',
columnWidth: 160,
gutter: 20
});
}
});
So it detects when new page content is loaded and, if it finds my .grid element, re-runs masonry.
Leaving this open in case someone can provide a better, long-term solution.

Resources