I have written following test:
it('componentDidUpdate should mount and change props', () => {
const onChange = jest.fn();
const wrapper = enzyme
.mount(
<JsonInput
onChange={onChange}
onValueChange={mockOnValueChange}
value={exampleJsonStringValidated}
/>,
{ wrappingComponent: withTestThemeWrapper },
);
console.log(wrapper.debug());
expect(wrapper.find(JsonInput).hasClass('valid')).toEqual(true);
wrapper.setProps({ value: exampleJsonStringNotValidated });
expect(wrapper.find(JsonInput).hasClass('invalid')).toBe(true);
});
and console.log shows:
<JsonInput onChange={[Function: mockConstructor]} onValueChange={[Function: mockConstructor]} value="{"firstName":"John","lastName":"Doe","age":210}">
<styled.textarea onChange={[Function]} value="{"firstName":"John","lastName":"Doe","age":210}" height="">
<StyledComponent onChange={[Function]} value="{"firstName":"John","lastName":"Doe","age":210}" height="" forwardedComponent={{...}} forwardedRef={{...}}>
<textarea onChange={[Function]} value="{"firstName":"John","lastName":"Doe","age":210}" height="" className="sc-bdVaJa lavZWj" />
</StyledComponent>
</styled.textarea>
</JsonInput>
In the component the code className="sc-bdVaJa lavZWj" is valid and invalid but now I see that there is no readable names of classes, how to test it?
Component (styled part)
export const StyledTextArea = styled.textarea<{ height: string }>`
margin: 0;
box-sizing: border-box;
width: 350px;
outline: none;
border: none;
height: ${props => props.height};
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.23);
background-color: ${props => props.theme.palette.foreground};
color: ${props => props.theme.palette.text};
cursor: text;
&:focus{
border-bottom: 2px solid ${props => props.theme.palette.active};
}
&:valid{
border-bottom: 2px solid ${props => props.theme.palette.positive};
}
&:invalid{
border-bottom: 2px solid ${props => props.theme.palette.negative};
}
`;
and render:
render() {
// to exclude unknown property 'onValueChange' for JsonInput for DevTools
const { height = '', onValueChange, ...restProps } = this.props;
return (
<StyledTextArea
ref={this.JsonInputRef}
{...restProps}
onChange={this.handleValueChange}
height={height}
/>
);
}
So you does not need(and cannt) to test classnames themselves since :valid and :invalid are state/pseudoselector.
For toHaveStyleRule from jest-styled-components there is 3rd argument options where we could provide desired state like :hover or :valid.
Try this:
expect(wrapper
.find('textarea')
.toHaveStyleRule(
'border-color',
'border-bottom: 2px solid red',
{modifier: ':invalid'}
)
).toBeTruthy();
Related
I made 5 blocks and want to make the letters on each block thick when the mouse is hover. I made isHover state and changed the thickness of the writing according to the state, but the problem is that the thickness of all five changes. I think I can solve it by using conditional rendering, but I don't know how to use it. Of course, it can be implemented only with css, but I want to implement it with conditional rendering because I am practicing the code concisely.
import "./styles.css";
import styled from "styled-components";
import { useState } from "react";
export default function App() {
const array = [
{ id: "1", title: "ABC" },
{ id: "2", title: "DEF" },
{ id: "3", title: "GHI" },
{ id: "4", title: "JKL" },
{ id: "5", title: "MNO" }
];
const [isHover, setIsHover] = useState(false);
return (
<Head isHover={isHover}>
<div className="header">
{array.map((content, id) => {
return (
<div
className="header__title"
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
{content.title}
</div>
);
})}
</div>
</Head>
);
}
const Head = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.header {
display: inline-flex;
border: 1px solid black;
box-sizing: border-box;
}
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: ${(props) => (props.isHover ? "700" : "400")};
}
`;
codesandbox
https://codesandbox.io/s/aged-cherry-53pr2r?file=/src/App.js:0-1170
The problem is that you are using the same state for all the 5 blocks. There are multiple approaches you could take to solve this problem.
1. Multiple states
You could create 5 different isHover<N> states (maybe a single one, but as an array)
2. Component extraction
You could just extract out a component for each entry in array and do state management in that component.
function App() {
const array = [...];
return (
<Head>
<div className="header">
{array.map((content, id) => (
<HeaderTitle key={content.id} content={content} />
)}
</div>
</Head>
);
}
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
isHover={isHover}
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
{content.title}
</StyledHeaderTitle>
);
}
const StyledHeaderTitle = styled.div`
font-weight: ${(props) => (props.isHover ? "700" : "400")};
`
3. Using style prop
Directly apply the font weight using the style prop (An extension to approach 2)
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
style={{ fontWeight: isHover ? "700" : "400" }}
>
{content.title}
</StyledHeaderTitle>
);
}
4. CSS
CSS already allows you to track hover states over different elements and you don't need to manually track it in javascript.
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
&:hover {
font-weight: 700;
}
}
There's no need to use React state and event listeners here, you can do it all in CSS instead:
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
}
.header__title:hover {
font-weight: 700;
}
Just add this pseudo class and you're good to go
.header__title:hover {
font-weight: 700;
}
I wanted to know whether my onAdd function in <NewItemButton> will get the latest value of text state.
import { useState } from "react";
import {
NewItemFormContainer,
NewItemInput,
NewItemButton
} from "./styles";
type NewItemFormProps = {
onAdd(text: string): void
}
const NewItemForm = (props: NewItemFormProps) => {
const [text, setText] = useState("");
return (
<NewItemFormContainer>
<NewItemInput
value={text}
onChange={(e) => setText(e.target.value)}
/>
<NewItemButton onClick={() => props.onAdd(text)}>
Create
</NewItemButton>
</NewItemFormContainer>
);
}
export default NewItemForm;
If it does not get the latest value, what other ways can you suggest me? One that comes to my mind to use Refs (forwardRef) and send it directly to the html input element and then call props.onAdd with the current value. But the thing is that I am using styled-components and my NewItemInput looks like this
export const NewItemInput = styled.input`
border-radius: 3px;
border: none;
box-shadow: #091e4240 0px 1px 0px 0px;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
width: 100%;
background: #484747;
color: #f1f1f1;
`
So how will do that?
I always use props as it's the basic way for managing different use cases, but I'm trying to change border color during the focus state of a styled input (is it possible to assign props to a focus state?).
I'm familiar with using props but even within the styled component, I'm not able to assign to a variable. i can't say {props => props.focused ? accentCol : null}. The only way I've been able to assign variables has been through inline styles. However, afaik there's no way to access focus state through inline styles :/
const accentCol = `{some redux function which retrieves different colors in different scenarios`
const styledInput = styled.input`
background: #181a1a;
border: 1px solid rgba(255, 255, 255, 0.4);
&::placeholder {
}
&:focus {
outline: none !important;
border: solid 2px accentCol !important;
}
`
how do I assign border color to a variable?
Following the docs explanation:
const StyledInput = styled.input`
&:focus {
outline: none;
border: solid 2px ${(props) => props.focusBorder};
}
`;
function App() {
return (
<>
<StyledInput focusBorder="red" />
<StyledInput focusBorder="blue" />
</>
);
}
https://codesandbox.io/s/styled-focus-etf4pd
interface IProps {
focus: string,
}
const variantsfocus = {
primary: 'solid 2px accentCol',
};
const StyledInput = styled.input<IProps>`
background: #181a1a;
border: 1px solid rgba(255, 255, 255, 0.4);
&::placeholder {
}
&:focus {
outline: none !important;
border: ${props => variantsfocus[props.focus]};
}
`
I hope this would be helpful, you have to pass the prop in the component like
<StyledInput focus="primary" />
This link may be helpful also.
I am trying to create a to-do list app. The basic functionality includes adding and deleting. So when a user selects one or multiple items, a delete button will appear and it will be deleted. My problem is I am implementing a toggle state which when a user clicks on todo item, it will be strikethrough( A strikethrough text decoration will be added via CSS).
The problem arises when I add two items. When I click on the first item , it gets a strike through and when I delete it, the first one goes but the second item gets the strike through this time.
The running sample in codesandbox. Just try adding two items and delete the first one. The second one also gets a strike through.
I believe its because the toggle state value is being remembered.
This is the Content.js component
import "./styles/content.css";
import Individual from "./Individual";
import { useEffect, useState } from "react";
import { updateItem, markIncomplete } from "./action/action";
const Contents = (props) => {
const items = useSelector((state) => state.todoReducer.items);
const dispatch = useDispatch();
const handleClick = (e, isComplete, content, id) => {
// console.log(isComplete);
if (isComplete === false) {
//evaluates false to true
const newobj = {
isComplete: true,
content,
id
};
dispatch(updateItem(newobj));
props.deletebutton(true);
} else {
const falseobj = {
isComplete: false,
content,
id
};
dispatch(markIncomplete(falseobj));
}
};
useEffect(() => {
console.log("statechanging of contents");
});
return (
<div className="content-ui">
<div>
{items.map((vals) => (
<Individual
vals={vals}
deletebutton={props.deletebutton}
handleClick={handleClick}
/>
))}
</div>
</div>
);
};
export default Contents;
This is the individual.js which deals with toggle function
import { useDispatch, useSelector } from "react-redux";
import "./styles/content.css";
import { updateItem, markIncomplete } from "./action/action";
const Individual = (props) => {
console.log("child" + props.vals.isComplete);
const [toggle, isToggled] = useState(false);
const handleToggle = () => {
const mytoggle = !toggle;
isToggled(mytoggle);
};
return (
<div>
<div
className={toggle ? "toggled" : "card-elements"}
onMouseDown={handleToggle}
onClick={(e) => {
props.handleClick(
e,
props.vals.isComplete,
props.vals.content,
props.vals.id
);
handleToggle;
}}
>
{props.vals.content}
</div>
</div>
);
};
export default Individual;
Css of toggler
.toggled {
/* border: 1px solid rgb(94, 94, 94); */
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
text-align: left;
box-sizing: border-box;
padding: 10px 3px 10px 7px;
margin-top: 4px;
margin-bottom: 8px;
border-radius: 5px;
background-color: white;
font-size: 12px;
font-family: "Roboto ", monospace;
text-decoration: line-through;
}
.card-elements {
/* border: 1px solid rgb(94, 94, 94); */
box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 0px 1px;
text-align: left;
box-sizing: border-box;
padding: 10px 3px 10px 7px;
margin-top: 4px;
margin-bottom: 8px;
border-radius: 5px;
background-color: white;
font-size: 12px;
font-family: "Roboto ", monospace;
}
You need to add keys to your mapped items (there should also be a warning about this in the console).
Keys help React identify which items have changed, are added, or are removed.
As is stated from React's documents.
return (
<div className="content-ui">
<div>
{items.map((vals) => (
<Individual
key={vals.id} // <-- add unique key
vals={vals}
deletebutton={props.deletebutton}
handleClick={handleClick}
/>
))}
</div>
</div>
);
I have an onChange-event on an input listening for when target.value is greater than 0 to enable classes to then display a button.
This works on desktop however, not functional on iPad Safari - I'm unsure why?
Note I am using styled components and PDInput is declared as an input.
const PDInput = styled.input`
background: #fff;
border: none;
border-bottom: 1px solid #f2f2f5;
padding: 8px 0;
padding-left: ${props => props.amount ? "15px" : "0"};
font-size: 16px;
color: #0c2074;
width: 100%;
margin-bottom: 24px;
&::placeholder {
color: #0c2074;
opacity: 0.5;
}
`
const updateRef = e => {
if(e.target.value > 0) {
setRef(true);
} else {
setRef(false);
}
}
<PDInput type="text" placeholder="E.G Rent" onChange={(e) => updateRef(e)} />
Here's the solution - the issue was that e.target.value isn't technically > 0. It returns false. You want to check if the e.target.value !== ''.
Code:
const updateRef = e => {
if(e.target.value > 0 || e.target.value !== '') {
setRef(true);
} else {
setRef(false);
}
}