React + Jest: How to test private styled components? - reactjs

I have a Charities component that shows text "Sorry..." when the status prop is === "error":
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: ${props => (props.hide ? "none" : "block")};
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
<ErrorNotification hide={status !== "error"}>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
...
</Container>
);
export default Charities;
I'm trying to test this with jest like this:
import React from "react";
import { mount, shallow } from "enzyme";
import { Charities } from "./index";
describe("Charities", () => {
let props;
let mountedCharities;
const charities = () => {
if (!mountedCharities) {
mountedCharities = mount(<Charities {...props} />);
}
return mountedCharities;
};
beforeEach(() => {
props = {
status: undefined,
...
};
mountedCharities = undefined;
});
describe("when status is pending", () => {
beforeEach(() => {
props.status = "pending";
});
it("doesn't render error", () => {
expect(charities().text()).not.toMatch(/Sorry/); // <---------- FAILS
});
});
});
My test fails with:
Expected value not to match:
/Sorry/
Received:
"Sorry, something was wrong with your payment. Please try again.Congratulations! You have successfully made a donation."
It seems like it's loading the children of the styled components even when it doesn't meet the conditions. How would I test this?

Your code is working as expected, it's just that styled() works by putting class names on the elements to control the styling.
In the unit test ErrorNotification is still there but it has css classes on it that will give it display: none in the final rendered HTML.
To make your components easier to unit test I recommend doing the hiding within Charities like this:
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: block;
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
{status !== "error" ? null : (
<ErrorNotification>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
)}
...
</Container>
);
export default Charities;
That way if the status in the props for Charities is anything except 'error' then ErrorNotification won't be rendered at all.

You are using the attribute hide which maps to 'display: none' when true this still renders the component albeit invisibly you should do something like this instead:
{ status === "error" &&
<ErrorNotification >
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
}

Related

Custom components in react-markdown

I would like to create a custom component in react-markdown. Specifically I need to be able to create a tag that will allow me to create a tooltip. E.g. [name]{ tooltip content}. I found a similar solution in the link. Unfortunately the component is created as a regular div and not as my component. I don't know exactly what I did wrong.
my code:
import React from "react";
import styled from "styled-components";
import ReactMarkdown from "react-markdown";
import directive from "remark-directive";
import { visit } from "unist-util-visit";
import { h } from "hastscript/html.js";
const Test = styled.span`
font-size: 12rem;
font-family: "DM Serif Display", serif;
`;
const TestComp = ({ ...props }) => {
return <Test> {props.children}</Test>;
};
const components = {
myTag: TestComp,
};
// remark plugin to add a custom tag to the AST
function htmlDirectives() {
return transform;
function transform(tree) {
visit(
tree,
["textDirective", "leafDirective", "containerDirective"],
ondirective
);
}
function ondirective(node) {
var data = node.data || (node.data = {});
var hast = h(node.name, node.attributes);
data.hName = hast.tagname;
data.hProperties = hast.properties;
}
}
var markdown =
' Some markdown with a :myTag[custom directive]{title="My custom tag"}';
const Text = () => {
return (
<ReactMarkdown
className={"markdown"}
components={components}
remarkPlugins={[directive, htmlDirectives]}
children={markdown}
linkTarget={"_blank"}
/>
);
};
export default Text;
I use markdown-to-jsx to use custom component.
https://www.npmjs.com/package/markdown-to-jsx

Not able to dynamically style component - TypeScript , Styled-component

I have a struggle here and I fear I do not understand what is happening.
I am following the documentation of styled-components
and everything seems to be like they described it, but it seems that it is not working on my side.
I am trying to render a Card/div or basically to expand it based on props coming from the parent
...
const [open, setOpen] = useState<boolean>(false)
...
return (
<CardWrapper
open={open}
... />
...
)
And here I use my state inside the styled component
import { keyframes, css } from '#emotion/react'
import styled from '#emotion/styled/macro'
const expandAnimation = keyframes`
0% {
height: 180px;
}
100% {
height: 240px;
}
`
const unexpandAnimation = keyframes`
0% {
height: 240px;
}
100% {
height: 180px;
}
`
...
export const CardWrapper = styled.div<{open: boolean}>`
animation: ${(open) => (open
? css`${expandAnimation} 1s ease-in-out ;`
: css`${unexpandAnimation} 1s ease-in-out;`)} ;
`
Gave it a try also with
export const CardWrapper = styled.div<{open: boolean}>`
${({ open }) => open && css`
animation-name: ${expandAnimation }
`}
`
But it seems I am not doing something right. I am pretty new to styled-components, but if someone can tell me what am I doing wrong.
UPDATE
In order for us to display some animation we need to provide a prop of ease and a prop of direction. In my case i use forwards and backawrds
export const CardWrapper = styled.div<{open: boolean}>`
${({ open }) => (open
? css`animation: ${expandAnimation} 0.7s ease forwards`
: css`animation: ${unexpandAnimation}1s ease backwards `)
}
`

Next.js: How to get applied styles from element?

Let's say I have global.css
.test {
background-color: black;
width: 50px;
height: 50px;
}
For some reason, I need to get the styles data from applied element. I tried refs but it always return empty string.
import { useState, useEffect, useRef } from "react";
const IndexPage = () => {
const divEl = useRef<HTMLDivElement | null>(null);
const [divStyle, setDivStyle] = useState({} as CSSStyleDeclaration);
useEffect(() => {
if (divEl.current) {
setDivStyle(divEl.current.style);
}
}, [divEl.current]);
return (
<div>
<div ref={divEl} className="test"></div>
<pre>{JSON.stringify(divStyle, undefined, 2)}</pre>
</div>
);
};
export default IndexPage;
Is it because next.js SSR or should I add something to dependency array?
code sandbox here
You can use computed styles to get what you need, although it won't be a "simple" object with properties. You'll need to query each property individually:
if (divEl.current) {
setDivStyle(getComputedStyle(divEl.current));
}
and then you can do something like:
divStyle.getPropertyValue("background-color")
Here's an example sandbox (forked from yours) that demostrates it.

Why I'm getting invalid hook call using Nav tag?

New React user here. I'm trying to make my first Navbar with React and getting this error:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
I tried to execute npm i -S react react-dom and also check that I'm executing the Hook correctly (understanding this concept yet, sorry). My current code is very simple:
import React from 'react';
import { Nav } from './NavbarElements';
const Navbar = () => {
return (
<Nav>
</Nav>
);
};
export default Navbar;
Why I'm getting this error with Nav tag? The element Nav is declared in NavbarElements.js:
import styled from 'styled-components';
export const Nav = styled.nav`
background: #000;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
position: sticky;
top: 0;
z-index: 10;
#media screen and (max-width: 960px) {
trasition: 0.8s all ease;
}
`;
Moreover, I'm using React to make the front part of a DApp (blockchain App). I have my file main file App.js (which's kinda big):
class App extends Component {
state = { storageValue: 0, web3: null, accounts: null, contract: null };
componentDidMount = async () => {
try {
// Get network provider and web3 instance.
const web3 = await getWeb3();
// Use web3 to get the user's accounts.
const accounts = await web3.eth.getAccounts();
// Get the contract instance.
const networkId = await web3.eth.net.getId();
const deployedNetwork = SimpleStorageContract.networks[networkId];
const instance = new web3.eth.Contract(
SimpleStorageContract.abi,
deployedNetwork && deployedNetwork.address,
);
instance.options.address = "0xA65990EC0CA555d2eCDD1d84E9D1397CFA967E60"
// Set web3, accounts, and contract to the state, and then proceed with an
// example of interacting with the contract's methods.
this.setState({ web3, accounts, contract: instance }, this.runExample);
} catch (error) {
// Catch any errors for any of the above operations.
alert(
`Failed to load web3, accounts, or contract. Check console for details.`,
);
console.error(error);
}
};
runExample = async () => {
const { accounts, contract } = this.state;
// Stores a given value, 5 by default.
await contract.methods.set(25).send({ from: accounts[0] });
// Get the value from the contract to prove it worked.
const response = await contract.methods.get().call();
// Update state with the result.
this.setState({ storageValue: response });
};
render() {
if (!this.state.web3) {
return <div>Loading Web3, accounts, and contract...</div>;
}
return (
<div className="App">
<Router>
<Navbar />
</Router>
</div>
);
}
}
export default App;
And, the error is on the line this.setState({ web3, accounts, contract: instance }, this.runExample);. My question is: Am I declaring my react "interface" in a bad place? It's after render() instruction (at the end of the file), where I thought it is. But, the point is that I'm getting that error only when I use the Nav tag. Am I misunderstanding something?

React: Enzyme's it local scope does not work

I am using React. I used basic redux counter by using redux-toolkit. I am really new in testing. I am using Enzyme and Jest for the test. My redux counter intialState is 1. From my testing, inside it scope I first take the intialState then after simulate('click') increase button, I got result 2, which I expected. When I try to test my decrease button inside the it scope it takes the result from increase's it scope. If I put intialState 1 inside the decrease button's it scope, it gives me failed test because it expected 2.
This is my testing file
import React from 'react';
import { mount } from "enzyme"; // mount is fulldom renderning function with children
import Counter from 'components/counter';
import Root from "root/index"; // this is the root index which connect react component and redux
let wrapped;
beforeEach(() => {
wrapped = mount(
<Root>
<Counter />
</Root>
);
});
afterEach(() => {
wrapped.unmount(); // it cleans the mount after test.
});
describe(`This is counter component`, () => {
it(`This is show intial Value`, () => {
expect(wrapped.find(`h1`).text()).toEqual(`1`);
});
it(`after click it will increase the value`, () => {
expect(wrapped.find(`h1`).text()).toEqual(`1`);
wrapped.find(`button`).at(0).find(`[data-test="increment"]`).simulate(`click`);
expect(wrapped.find(`h1`).text()).toEqual(`2`);
});
it(`after click it will decrease the value`, () => {
expect(wrapped.find(`h1`).text()).toEqual(`1`); // test failed because it expect 2
wrapped.find(`button`).at(1).find(`[data-test="decrement"]`).simulate(`click`);
expect(wrapped.find(`h1`).text()).toEqual(`0`);
});
});
This is my counter component
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from 'store/reducer/counter/index';
import { IRootState } from 'store/combineReducer';
import styled from 'styled-components';
const Button = styled.button`
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
`;
const Text = styled.h1`
color: blue;
`;
export default () => {
const counter = useSelector((state: IRootState) => state.counter);
const dispatch = useDispatch();
return (
<div >
<Text>{counter}</Text>
<Button data-test="increment" onClick={() => dispatch(increment())}>
Increment counter
</Button>
<br></br>
<br></br>
<Button data-test="decrement" onClick={() => dispatch(decrement())}>
Decrement counter
</Button>
</div>
);
};
it block has a scope in a sense that a function has a scope, additionally other things like spies can be affected by currently running test. There are no problems with scopes here, the only thing that is affected by scopes is wrapper variable and it's defined in a scope that is common to all tests. Since it's reassigned in beforeEach, it cannot result in test cross-contamination.
The problem here is that there's global state because Redux naturally provides one.
Most commonly a custom store is set up for tests so initial value, etc. could be manipulated depending on testing needs:
let wrapped;
beforeEach(() => {
const store = ...
wrapped = mount(
<Provider store={store}><Counter /></Provider>
);
});
This is what the documentation recommends.
Alternatively, a store and all modules that directly depend on it needs to be re-imported per test, this should be done instead of top-level import of these modules:
let wrapped;
let Root;
beforeEach(() => {
jest.resetModules();
Root = require("root/index");
wrapped = mount(
<Root>
<Counter />
</Root>
);
});

Resources