hover testing not working on react component - javascript

Testing opacity while hover button is not working. Tried with both userEvent.hover(hoverButton) and fireEvent.mouseOver(hoverButton) unfortunately the result is same. At the same time the other properties are working, for example 'cursor:not-allowed'
Button.js
import styled from 'styled-components';
const ButtonWrapper = styled.button`
color: #fff;
background-color: tomato;
font-size: 14px;
padding: 6px 30px;
height: 35px;
&:not(:disabled):hover {
opacity: 0.8;
background: red;
}
${({ disabled }) =>
disabled && `
opacity: 0.4;
cursor: not-allowed;
`}
`;
// background-color: ${(props) => props.theme.buttonColors[props.kind]};
const Button = ({ children, className, disabled, size = 'medium' }) => {
return <ButtonWrapper
className={className}
disabled={disabled}
onClick={() => console.log('Hello mate')}
size={size}
>{children}</ButtonWrapper>;
}
export default Button;
Button.test.js
import { fireEvent, render, screen } from '#testing-library/react';
import userEvent from "#testing-library/user-event";
import Button from './index';
test('should render button', () => {
render(<Button>Click on me</Button>);
expect(screen.getByText(/Click on me/i)).toBeInTheDocument();
});
test('should render disabled button', () => {
render(<Button disabled>hover on me</Button>);
const hoverButton = screen.getByRole('button');
expect(hoverButton).toBeDisabled();
expect(hoverButton).toHaveStyle('opacity: 0.4')
expect(hoverButton).toHaveStyle('cursor:not-allowed')
});
test('should hover button', () => {
render(<Button>hover on me</Button>);
// const hoverButton = screen.getByRole('button');
const hoverButton = screen.getByRole('button', { name: 'hover on me' });
userEvent.hover(hoverButton);
// fireEvent.mouseOver(hoverButton)
expect(hoverButton).toHaveStyle('opacity: 0.8')
expect(hoverButton).toHaveStyle(`background-color: #000`);
});
test('toMatchSnapshot', () => {
const { asFragment } = render(<Button>Click on me</Button>);
expect(asFragment()).toMatchSnapshot();
});

Problem
I found a few obstacles that you're running into:
There's a mistake in the test, where the background-color was expected to be #000 when it's set as background: redu in the Button.js file.
I'm pretty confident that the testing-library's hover interaction doesn't update the computed styles. I verified this using getComputedStyle in the DOM versus in the test. That said, you can use jest-styled-components' toHaveStyleRule, to check for pseudo styles however that's not really not going to cover what you're attempting to test.
Currently, there's really no way for you to test a button's current appearance based upon a browser interaction, unless that interaction add/removes styles dynamically using some sort of React state or prop (similar to what you've done with disabled). As a result, there's no way to accurately determine it's appearance since every Button has the same pseudo styles (the snapshots showcase this, but also looking at the DOM will showcase the same thing):
exports[`Button disabled snapshot 1`] = `
<DocumentFragment>
.c0 {
color: #fff;
background-color: tomato;
font-size: 14px;
padding: 6px 30px;
height: 35px;
opacity: 0.4;
cursor: not-allowed;
}
.c0:not(:disabled):hover {
opacity: 0.8;
background: red;
}
<button
class="c0"
disabled=""
>
Click on me
</button>
</DocumentFragment>
`;
exports[`Button enabled snapshot 1`] = `
<DocumentFragment>
.c0 {
color: #fff;
background-color: tomato;
font-size: 14px;
padding: 6px 30px;
height: 35px;
}
.c0:not(:disabled):hover {
opacity: 0.8;
background: red;
}
<button
class="c0"
>
Click on me
</button>
</DocumentFragment>
`;
Recommendation:
You don't need to test against native interactions/style changes with the DOM. Instead, test against what you do control. In this case, you're controlling whether or not a button can be disabled and appending styles based upon that prop. Other than testing that (which you've already done), in this case, any other tests should be considered ancillary/superfluous.
Taking a step back, what exactly are you trying to test? It seems like you are trying to test if a browser can apply a style via a specific CSS selector to a button? This seems rather unnecessary, no?
Not recommended:
This will test if an element contains a particular pseudo style, but again, it's shared and there's really no way to determine when its styles are active (even if you added a disabled button prop, this test will still pass because it still contains the pseudo styles!):
test("should contain button hover styles", () => {
render(<Button>hover on me</Button>);
const hoverButton = screen.getByRole("button");
expect(hoverButton).toHaveStyleRule("opacity", "0.8", {
modifier: ":not(:disabled):hover",
});
expect(hoverButton).toHaveStyleRule("background", "red", {
modifier: ":not(:disabled):hover",
});
});

Related

Scroll to Top button shows up fine on desktop/desktop mobile but not on actual mobile phone

When in localhost, google chrome devtool's responsive output shows that my scroll to top button looks and works as intended in any viewport. Production also looked fine on desktop "mobile" and desktop browsers (currently removed until fixed).
However, the button sometimes takes up the entire lower viewport on actual mobile browsers (both Safari and Chrome) Screenshot of iPhone 13 safari. Since I cannot reproduce the issue locally, I am having trouble sourcing the issue. Here is the repo in question. Here is the scroll component:
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { MdExpandLess } from '#react-icons/all-files/md/MdExpandLess';
import PropTypes from 'prop-types';
//https://juliapottinger.com/react-gatsby-scroll-to-top/
const IconButton = styled.button`
position: fixed;
z-index: 2;
padding: 15px;
bottom: 2.8vh;
right: 15%;
border-radius: 40px;
background-color: transparent;
border: 2px solid var(--green);
transition: var(--transition);
&:hover,
&:focus {
background-color: var(--green-tint);
outline: none;
}
&:active {
background-color: var(--blue);
}
#media (max-width: 1200px) {
bottom: 2.8vh;
right: 7%;
background-color: var(--dark-navy);
opacity: .75;
}
#media (max-width: 768px) {
bottom: 2.8vh;
right: 5%;
}
}
`;
const Scroll = ({ showBelow }) => {
const [show, setShow] = useState(showBelow ? false : true);
const handleScroll = () => {
if (window.pageYOffset > showBelow) {
if (!show) {
setShow(true);
}
} else {
if (show) {
setShow(false);
}
}
};
const handleClick = () => {
window[`scrollTo`]({ top: 0, behavior: `smooth` });
};
useEffect(() => {
if (showBelow) {
window.addEventListener(`scroll`, handleScroll);
return () => window.removeEventListener(`scroll`, handleScroll);
}
});
return (
<div>
{show && (
<div>
<IconButton onClick={handleClick}>
<MdExpandLess color="var(--green)" size="40px" />
</IconButton>
</div>
)}
</div>
);
};
Scroll.propTypes = {
showBelow: PropTypes.string.isRequired,
};
export default Scroll;
The component was being used in every .js file in personalWebsite/src/pages/projectDetails, placed right above the <StyledAboutSection/> component. To see if the location was causing the bug, I tried removing it from all the files, and simply placing it in the personalWebsite/src/components/layout.js file, within <div id="content">. While this worked locally, the error still persists once published. I took out the component for now, but I really need the button with my long blog posts. Any suggestions are welcome.

onDoubleClick is not working on react to call a function

I'm following this React tutorial here: https://ibaslogic.com/how-to-edit-todos-items-in-react/ to build a simple TO DO app.
I've also reviewed Why onDoubleClick event is not working in React.js? but there's no onclick event to worry about in my example.
My onDoubleClick event should call a function handleEditing but nothing happens when I double click a list item.
I'm unsure of why it does not work (the web browser does not seem to register a double click event.
Below is my example:
import React from "react";
import styles from "./TodoItem.module.css";
class TodoItem extends React.Component {
state = {
editing: false,
};
handleEditing = () => {
console.log("doubleClick")
this.setState({
editing: true,
});
};
render() {
const completedStyle = {
fontStyle: "italic",
color: "#595959",
opacity: 0.4,
textDecoration: "line-through",
};
const { completed, id, title } = this.props.todo;
let viewMode = {}
let editMode = {}
if (this.state.editing) {
viewMode.display = "none"
} else {
editMode.display = "none"
}
return (
<li className={styles.item}>
<div onDoubleClick={this.handleEditing} style={viewMode}>
<input
type="checkbox"
className={styles.checkbox}
checked={completed}
onChange={() => this.props.handleChangeProps(id)}
/>
<button onClick={() => this.props.deleteTodoProps(id)}>Delete</button>
<span style={completed ? completedStyle : null}>{title}</span>
</div>
<input type="text" style={editMode} className={styles.textInput} />
</li>
);
}
}
export default TodoItem;
I don't think this is relevant, but here is my css:
.item {
font-size: 1.2rem;
list-style-type: none;
padding: 17px 0px;
border-bottom: 1px solid #eaeaea;
}
.checkbox {
margin-right: 15px;
}
.item button {
font-size: 13px;
background: #f1f3f4;
border: none;
cursor: pointer;
float: right;
outline: none;
border-radius: 100px;
height: 50px;
width: 50px;
margin: -10px 0 0 10px;
}
.textInput {
width: 100%;
padding: 10px;
border: 1px solid #dfdfdf;
}
onDoubleClick works when your dev tool is not opened
Updated answer:
As found out in the comments, the problem was a combination of OS and Browser. Windows / Chrome in this example.
Old answer:
I haven't read into much detail, but the first difference I can spot is that in your code the handleEditing is not bound. Which should not prevent the output of your console.log. Does it appear?
onDoubleClick={this.handleEditing.bind(this)}
Hope this helps in your case.

Is it possible to change the class of an element if the class of another elements equals to sth?

I want to remove/add the classList of some element if the classList of another element is equal to "sth". But there is a little problem that it only runs the command once, I mean the condition doesn't check the classList of the first element every second so it runs it without any problem. If the if is true then it runs the if condition and then it won't check else if and so reverse.
Here is my code:
const dToggle = () => {
const firstElm = document.querySelector('.firstElm');
firstElm.classList.toggle('red-border');
};
document.querySelector('button').addEventListener('click', dToggle);
const orangeOrRed = () => {
const firstElm = document.querySelector('.firstElm');
const secondElm = document.querySelector('.secondElm');
firstElm.classList === 'red-border' ? secondElm.classList.add('red') : secondElm.classList.add('orange');
// if (firstElm.classList === 'red-border') {
// secondElm.classList.remove('orange');
// secondElm.classList.add('red');
// } else if (firstElm.classList === 'orange-border' {
// secondElm.classList.remove('red');
// secondElm.classList.add('orange');
// };
};
// Maybe the exact problem is right here.
window.addEventListener('load', orangeOrRed);
console.log(document.querySelector('.secondElm').classList.value);
.d-none {
display: none !important;
}
.firstElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: blue;
}
.orange-border {
border: orange 3px solid;
}
.red-border {
border: red 3px solid;
}
.secondElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: green;
}
.orange {
background-color: orange !important;
}
.red {
background-color: red !important;
}
<button type="button" style="cursor: pointer;">Click here.</button>
<p>It should run the conditional statement whenever I click on the button to apply the border.</p>
<p> First Elm Border Color (MUST BE) Second Elm Background Color</p>
<div class='firstElm orange-border'></div>
<div class='secondElm'></div>
<p style="padding-bottom: 10px;">Remember that I only want to do this with(conditioal statements), It's possible either with classList.toggle() but I don't want to.</p>
Here we have three problems:
1. Checking for the wrong condition
classList is a DOMTokenList that has a contains method which returns true if the element has the desired className or vice versa
2. Adding the same className multiple times without removing the old one
you need to remove the orange className if you want to add the red className and vice versa
3. The orangeOrRed function runs only once
The orangeOrRed function runs only once on window's load event
You need to add the same event listener to the button's click event
const dToggle = () => {
const firstElm = document.querySelector('.firstElm');
firstElm.classList.toggle('red-border');
};
document.querySelector('button').addEventListener('click', dToggle);
const orangeOrRed = () => {
const firstElm = document.querySelector('.firstElm');
const secondElm = document.querySelector('.secondElm');
if (firstElm.classList.contains('red-border')) {
secondElm.classList.remove('orange');
secondElm.classList.add('red');
} else if (firstElm.classList.contains('orange-border')) {
secondElm.classList.remove('red');
secondElm.classList.add('orange');
};
};
// You're right, the exact problem is right here.
window.addEventListener('load', orangeOrRed);
document.querySelector('button').addEventListener('click', orangeOrRed);
console.log(document.querySelector('.secondElm').classList.value);
.d-none {
display: none !important;
}
.firstElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: blue;
}
.orange-border {
border: orange 3px solid;
}
.red-border {
border: red 3px solid;
}
.secondElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: green;
}
.orange {
background-color: orange !important;
}
.red {
background-color: red !important;
}
<button type="button" style="cursor: pointer;">Click here.</button>
<p>It should run the conditional statement whenever I click on the button to apply the border.</p>
<p> First Elm Border Color (MUST BE) Second Elm Background Color</p>
<div class='firstElm orange-border'></div>
<div class='secondElm'></div>
<p style="padding-bottom: 10px;">Remember that I only want to do this with(conditioal statements), It's possible either with classList.toggle() but I don't want to.</p>
Achieving the same results using the MutationObserver API.
It's useful when you have no toggle button or when you're not in control of when the class name of the element changes.
const firstElm = document.querySelector('.firstElm');
const secondElm = document.querySelector('.secondElm');
const orangeOrRed = () => {
if (firstElm.classList.contains('red-border')) {
secondElm.classList.remove('orange');
secondElm.classList.add('red');
} else if (firstElm.classList.contains('orange-border')) {
secondElm.classList.remove('red');
secondElm.classList.add('orange');
};
}
// Callback function to execute when mutations are observed
const callback = (mutationsList) => {
// check to see if the changed attribute that caused the mutationObserver callback to be called is the class attribute and run the desired code
if (mutationsList[0].attributeName === 'class') orangeOrRed();
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Options for the observer (which mutations to observe)
// Here the MutationObserver will call the specified callback just if attributes of the target node have changed
const config = { attributes: true, childList: false, subtree: false };
// Start observing the target node (firstElm) for configured mutations
observer.observe(firstElm, config);
const dToggle = () => {
firstElm.classList.toggle('red-border');
};
document.querySelector('button').addEventListener('click', dToggle);
// You're right, the exact problem is right here.
window.addEventListener('load', orangeOrRed);
.d-none {
display: none !important;
}
.firstElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: blue;
}
.orange-border {
border: orange 3px solid;
}
.red-border {
border: red 3px solid;
}
.secondElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: green;
}
.orange {
background-color: orange !important;
}
.red {
background-color: red !important;
}
<button type="button" style="cursor: pointer;">Click here.</button>
<div class='firstElm orange-border'></div>
<div class='secondElm'></div>
<p style="padding-bottom: 10px;">Remember that I only want to do this with(conditioal statements), It's possible either with classList.toggle() but I don't want to.</p>
You've misinterpreted what the problem is here; it's not that the conditional isn't running, it's that it's checking the wrong thing.
You're looking to see if a DOM element's classList === 'orange-border' but this will never be true; classList isn't a simple string. What you want is to see if the classList includes orange-border.
const dToggle = () => {
const firstElm = document.querySelector('.firstElm');
firstElm.classList.toggle('red-border');
};
document.querySelector('button').addEventListener('click', dToggle);
const orangeOrRed = () => {
const firstElm = document.querySelector('.firstElm');
const secondElm = document.querySelector('.secondElm');
// I'm not sure why you have both this ternary and the similar if:else below, but I've fixed both to at least be checking the classList correctly:
firstElm.classList.contains('red-border') ? secondElm.classList.add('red') : secondElm.classList.add('orange');
if (firstElm.classList.contains('red-border')) {
secondElm.classList.remove('orange');
secondElm.classList.add('red');
} else if (firstElm.classList.contains('orange-border')) {
secondElm.classList.remove('red');
secondElm.classList.add('orange');
};
};
window.addEventListener('load', orangeOrRed);
console.log(document.querySelector('.secondElm').classList.value);
.d-none {
display: none !important;
}
.firstElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: blue;
}
.orange-border {
border: orange 3px solid;
}
.red-border {
border: red 3px solid;
}
.secondElm {
margin-top: 10px;
width: 200px;
height: 100px;
background-color: green;
}
.orange {
background-color: orange !important;
}
.red {
background-color: red !important;
}
<button type="button" style="cursor: pointer;">Click here.</button>
<p>It should run the conditional statement whenever I click on the button to apply the border.</p>
<p> First Elm Border Color (MUST BE) Second Elm Background Color</p>
<div class='firstElm orange-border'></div>
<div class='secondElm'></div>
<p style="padding-bottom: 10px;">Remember that I only want to do this with(conditioal statements), It's possible either with classList.toggle() but I don't want to.</p>

How to reference to a method in parent component from child component with vanilla JS Web Components? (Not any framework or Library)

The question is, How do I call a method from a child component? Ex : Consider I have a login form component located in the Parent component. So I need to show that form when I click the login button. The function to show the login form will be written in the Parent component and I need to t that function when I click the Button located in a child component.
This is the Parent component 👇
import Nav from './componets/navigation-bar.js'
import Comp from './componets/footer.js'
import UserComp from './componets/user-comp.js'
import Base from './componets/Base.js'
const style = `
.container {
display: flex;
flex-direction: row;
justify-content: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.container > user-comp {
padding: 1em;
}
`
const content = `
<navigation-bar></navigation-bar>
<div class="container">
<user-comp mirror="true">
<img slot="image" src="https://www.zricks.com/img/UpdatesBlog/44b94c9d-ab13-401d-9e5b-86a00f9da6496%20Must%20Follow%20Tips%20to%20Market%20a%20Luxury%20Home.jpg" alt="Image"></img>
<h1 slot="title">Rent or Lease your own property</h1>
</user-comp>
<user-comp mirror="true">
<img slot="image" src="https://s3.amazonaws.com/clients.granalacantadvertiser.images/wp-content/uploads/2017/06/14072232/2236775_2_O.jpg" alt="Image"></img>
<h1 slot="title">Looking for a place</h1>
</user-comp>
</div>
<footer-c></footer-c>
`
export default class UI extends Base {
constructor() {
super()
this.render(style, content)
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(this.template.content.cloneNode(true))
}
clicked = () => {
console.log('clicked')
}
}
window.customElements.define('ui-c', UI)
document.querySelector('#root').innerHTML = '<ui-c></ui-c>'
This is the child component 👇
import Base from './Base.js'
const style = `
header {
position: absolute;
top:0;
left:0;
right:0;
background-color: #111111;
color: #eeeeee;
z-index:1;
}
.logo {
margin-left: 2em;
}
nav {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#login-button {
height: 2.5em;
width: 10em;
margin: auto 2em;
text-transform: uppercase;
color: #eeeeee;
background-color: #239710;
border: none;
box-shadow: 1px 1px 5px 1px rgba(23,97,10,0.64);
outline: none;
cursor: pointer;
transition: 0.4s;
}
#login-button:hover {
background-color: #34a832;
}
`
const content = `
<header>
<nav>
<h3 class="logo">Homey</h3>
<button id="login-button"> login </button>
</nav
</header>
`
export default class Nav extends Base {
constructor() {
super()
this.render(style, content)
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(this.template.content.cloneNode(true))
}
connectedCallback() {
this.shadowRoot
.querySelector('#login-button')
.addEventListener('click', clicked())
}
}
window.customElements.define('navigation-bar', Nav)
This is the Base class was written by me (In case to understand) 👇
export default class Base extends HTMLElement {
template = document.createElement('template')
style(style) {
if (style === null) return ' '
return '<style>' + style + '</style>'
}
render(style, content) {
if (content === null) content = ''
this.template.innerHTML = this.style(style) + content
}
}
You can pass data from your child component to parent component using an event.
Within your child component you can create a custom event and fire it up when you want to call the method on parent component.
// create and dispatch the event
var event = new CustomEvent("cat", {
detail: {
hazcheeseburger: true
}
});
obj.dispatchEvent(event);
Then in your parent component you can listen for that event. Once the event is triggered, the event listener will catch and proceed accordingly. It will look something like this.
obj.addEventListener("cat", function(e) { process(e.detail) });
Example is taken from MDN web docs.
Events are a great solution to prevent tight coupling between components. But require some work.
Sometimes you just know you need that DIV element 3 levels up/down the DOM
UP the DOM
The standard element.closest(selector) "walks" up the DOM to find the selector you are after.
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
But .closest() does not escape shadowDOM
For that you have to write a recursive closestNode(selector) function that crosses all shadowDOMs with .getRootNode() till it finds the selector
customElements.define("my-element", class extends HTMLElement {
closestNode(
selector, // selector like in .closest()
start = this, // extra functionality to skip a parent
closest = (el, found = el && el.closest(selector)) =>
!el || el === document || el === window
? null // standard .closest() returns null for non-found selectors also
: found || closest(el.getRootNode().host) // recursion!! break out to parent DOM
) {
return closest(start); // look from start
}
connectedCallback() {
this.attachShadow({
mode: 'closed'// just to show it works with closed mode
}).append(document.getElementById(this.nodeName).content.cloneNode(true));
this.onclick = (evt) => {
evt.stopPropagation();
let container = this.closestNode('div');
let color = evt.target.childNodes[0].nodeValue;
container.style.background = color;
}
}
})
<template id=MY-ELEMENT>
<style>
button {
font: 16px Arial, sans;
margin:.5em;
}
button:hover{
background:lightgreen;
}
</style>
<button><slot></slot></button>
</template>
<div>
<my-element>red
<my-element>green
<my-element>orange
<my-element>blue
<my-element>yellow
<my-element>hotpink
</my-element>
</my-element>
</my-element>
</my-element>
</my-element>
</my-element>
</div>
DOWN the DOM
Something you want to prevent, but sometimes comes in handy
const shadowDive = (
el,
selector,
match = (m, r) => console.warn('match', m, r)
) => {
let root = el.shadowRoot || el;
root.querySelector(selector) && match(root.querySelector(selector), root);
[...root.children].map(el => shadowDive(el, selector, match));
}

Pass props to styled-components

I am querying data for my react site using graphql from my CMS (prismic.io) in order to produce color themed pages. I want to pass a variable or props into my styled component to change the background color based on what is sent back from the CMS.
In the below example, my graphql query will return a HEX that has been inputted by the user, this would then be applied to buttons etc to theme that page.
The colour can and will change from page to page as the user will be selecting it within the CMS.
Any help would be appreciated. Code example below:
Props
props.data.case_study_color
Component
const ContactButton = styled.button `
background: #004655;
color: #fff;
font-size: 2rem;
padding: 10px;
`;
You could do the following.
const ContactButton = styled.button`
background: #004655;
color: ${props => props.color || '#fff'};
font-size: 2rem;
padding: 10px;
`;
See codesandbox example here.
This would be the component code:
.....component
const [color, setColor] = React.useState("#fff");
React.useEffect(() => {
fetch(URL).then(data => {
setColor(data.response);
});
}, []);
return (
<div className="App">
<ContactButton color={color}>White</ContactButton>
</div>
);
const ContactButton = styled.button `
background: ${props => props.caseStudyColor};
color: #fff;
font-size: 2rem;
padding: 10px;
`;
<ContactButton caseStudyColor={'#004655'} />
As my solution was slightly different but based on Paul's answer it might be useful for someone else.
Button Component
const ContactButton = styled.button`
background: ${props => props.themeColor || '#004655'};`
Color Component
const themeColor = props.data.case_study_color;
Button
<ContactButton themeColor={themeColor}>Get in touch</ContactButton>

Categories