Testing DOM in Enzyme - javascript

Let's say I have a tiny component like this:
Button.js
import React from 'react';
import './Button.css';
export default class Button extends React.Component {
render() {
return (
<a href={ this.props.url } className={`button button-${ this.props.type }`}>
{ this.props.content }
</a>
);
}
}
And there's some super basic styling like this:
Button.css
.button {
color: white;
padding: 1em;
border-radius: 5px;
text-decoration: none;
}
.button-primary {
background-color: red;
}
.button-primary:hover {
background-color: darkred
}
.button-secondary {
background-color: aqua;
color: black;
}
.button-secondary:hover {
background-color: darkcyan;
color: white;
}
And let's say I want to write some tests for this:
Button.test.js
import React from 'react';
import Enzyme, {shallow, mount} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()});
import Button from './Button';
import './Button.css';
// Render buttons
const primaryButton = mount(
<Button
content="Primary button"
url="http://www.amazon.co.uk"
type="primary"
/>
);
const secondaryButton = mount(
<Button
content="Secondary button"
url="http://www.ebay.co.uk"
type="secondary"
/>
);
it('should exist', () => {
expect(primaryButton).toBeDefined();
expect(secondaryButton).toBeDefined();
});
it('should display text in the button', () => {
expect(primaryButton.text()).toEqual('Primary button');
});
it('should have the correct CSS classes', () => {
expect(primaryButton.find('.button').hasClass('button-primary')).toEqual(true);
expect(secondaryButton.find('.button').hasClass('button-secondary')).toEqual(true);
});
I've set this up using react-create-app and all the above works perfectly.
My question is: how do I test that what is getting rendered looks correct? For example, in this case I would want to make sure that the buttons have the correct background colours defined in the CSS file and that they have the correct border radius. This will prevent other developers accidentally overriding critical styling for example.
I was under the impression that Enzyme did this out of the box, but I cannot understand how to interrogate the virtual DOM which I assume is happening in the background? I thought that JSDOM was automatically running and I'm executing this from the CLI which is a Node environment.
I've tried this so far:
it('should have the correct background colours', () => {
const domNode = primaryButton.find('.button').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');
});
But background is returned blank, in fact if I do console.log(getComputedStyle(domNode)) I get this returned which seems to be missing the styles:
console.log src/modules/Button/Button.test.js:42
CSSStyleDeclaration {
_values: {},
_importants: {},
_length: 0,
_onChange: [Function] }

The getDOMNode of an enzyme wrapper gets you the corresponding DOM node.
You can then use getComputedStyle to get the style of that DOM:
const renderedComponent = mount(<MyComponent /);
const domNode = renderedComponent.find('div').at(0).getDOMNode();
const background = getComputedStyle(domNode).getPropertyValue('background');
expect(background).toBe('red');

Related

onClick event not firing in my styled component in my react app

I'am trying to trigger an onCick event that i passed to my style component icon tag but for some reason the event does not get triggered when the icon is clicked. I've tried raping it in a div and passing the onClick event to the div instead but still get the same result.
here is the code:
import React from "react";
import { useState } from "react";
import StyledNav, {NavbarhamButton} from "./style/Navbar.styled";
const Navbar = () => {
const [NavOpen, setNavOpen] = useState(false);
return (
<StyledNav>
<NavbarhamButton
className="fas fa-bars"
onclick={() => setNavOpen(!NavOpen)}
/>
</StyledNav>
);
};
export default Navbar;
Here is the styled components:
import { Link } from "react-router-dom";
import styled from "styled-components";
import { theme } from "../../theme";
const StyledNav = styled.div``;
export const NavbarhamButton = styled.i`
position: absolute;
top: 0;
left: 0;
margin: 1rem;
font-size: 2rem;
color: white;
#media screen and (min-width: 600px) {
display: none;
}
z-index: 2;
`;
I just started to use style components of recent so am still trying to wrap my head around it please correct me if am doing something wrong thanks.
Try changing the prop to onClick for <NavbarhamButton />. You may also need to use a base component other than i. So you might want to try styled.button instead of styled.i.

how to use hover in objects in react

I defined a object useStyle and called it in the component SecondTest defined background color, is it possible to add hover in the object useStyle
const useStyle = {
backgroundColor: "red",
};
function SecondTest() {
return <div style={useStyle}>SecondTest go down</div>;
}
export default SecondTest;
You can use Radium React Library
import React from "react";
import Radium from "radium";
const style = {
color: "#000000",
":hover": {
color: "#ffffff"
}
};
const MyComponent = () => {
return <section style={style}>hello world</section>;
};
const MyStyledComponent = Radium(MyComponent);
export default function App() {
return (
<>
<MyStyledComponent />
</>
);
}
You can accomplish it with events like onMouseEnter & onMouseLeave if you wish to use javascript to solve your issue. Easier way to do it would be just to give an element className like
<section className='myClass'>hello world</section>
and then just to add desired properties in your .css file which you can import in your component or globally.
.myClass {
color: #000000;
}
.myClass:hover {
color: #ffffff;
}
It is not possible to add hover with in-line styles.

How to extend component style in styled-components

I have a basic ElipseDetail component that looks like the image below
<ElipseDetail text="1.07" />
When I am using the component everything works as expected.
But now I want to reuse the component in another place but add an extension to the Text component style
How can I achieve that with styled-components and reuse the component but change the Text which is a child?
import React, { ReactElement } from "react";
import styled from "styled-components";
interface Props {
text: string;
children?: React.ReactNode;
}
export const Container = styled.div`
padding: 4px 12px;
border-radius: 30px;
background-color: #eaeef2;
display: inline-block;
`;
export const Text = styled.p`
font-size: 12.5px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
letter-spacing: 0.63px;
color: #687c97;
`;
export default function ElipseDetail({ text, children }: Props): ReactElement {
return (
<Container>
<Text>{text}</Text>
{children}
</Container>
);
}
Since ElipseDetails is not a styled component, but calls to a styled one, you can do something like:
function ElipseDetail({ text, children }: Props): ReactElement {
return (
<Container>
<Text>{text}</Text>
{children}
</Container>
);
}
ElipseDetail.Styled = Container;
export default ElipseDetail
And then, in a different component, you can change it like so:
const StyledElipseDetail = styled(ElipseDetail.Styled)`
${Text} {
//
}
`;
...
return <StyledElipseDetailed>...</StyledElipseDetail>
PS - I have taken this approach from an older question of mine which I found quite useful.
I would suggest to create a container and if Text is in that container then add different properties. & here means this class name so it evaluates to Text so when text would be in ElipseContainer then it will behave in different way depending on your use case. Here if it's wrapped with ElipseContainer then colour of Text is green and it's red otherwise.
Here is sandbox link :
sandobx
const ElipseContainer = styled.div``;
const Text = styled.p`
color: red;
${ElipseContainer} & {
color: green;
}
`;
const App =() =>
<ElipseContainer>
<ElipseDetail text="word2" />
</ElipseContainer>

How do I reference a styled-component that is a functional component?

This is the most basic example I could think of:
import React from 'react';
import {
css,
} from 'styled-components';
const Foo = (props) => {
console.log(props);
const {
children,
} = props;
return <div {...props}>{children}</div>;
};
export default () => {
return <div
css={css`
${Foo} {
background: #f00;
}
`}
>
<Foo>FOO</Foo>
</div>;
};
In this example, I want to style Foo component that is a descendent of div.
I would expect the resulting markup to look something like:
<div class="test__Foo-wusfqk-0 hNfawX">FOO</div>
However, instead it is simply:
<div>FOO</div>
It seems like no styling is applied anywhere.
Furthermore, the component Foo is rendered only once, but it is invoked twice, with different parameters:
{children: {…}, theme: {…}}
children: {$$typeof: Symbol(react.element), key: null, ref: null, props: {…}, type: ƒ, …}
theme: {}
{children: "FOO"}
I should mention that I tried:
// #flow
import React from 'react';
import styled, {
css,
} from 'styled-components';
const Foo = styled((props) => {
const {
className,
children,
} = props;
return <div className={className}>{children}</div>;
});
export default () => {
return <div
css={css`
${Foo} {
background: #f00;
}
`}
>
<Foo>FOO</Foo>
</div>;
};
However, when executing this code in next.js I am getting the following error:
The component Styled(Component) with the id of "sc-dlnjPT" has been created dynamically.
You may see this warning because you've called styled inside another component.
To resolve this only create new StyledComponents outside of any render method and function component.
The component Styled(Component) with the id of "sc-hKFyIo" has been created dynamically.
You may see this warning because you've called styled inside another component.
To resolve this only create new StyledComponents outside of any render method and function component.
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
which does not make much sense given the subject code snippet.
The main issue is that <Foo /> is not a styled component its a functional component
I think you would need to do
const Foo = styled.div`
background: #f00;
`
Then you can change the style of Foo using css and the $ refrence
The reason your code does not work is the following
This behaviour is only supported within the context of Styled Components: attempting to mount B in the following example will fail because component Foo is an instance of React.Component not a Styled Component.
const Foo = () => <div> </div>
const B = styled.div`
${Foo} {
}
`
However, wrapping Foo in a styled() factory makes it eligible for interpolation -- just make sure the wrapped component passes along className.
const Foo = (props) => {
console.log(props);
const {
children,
} = props;
return <div className="Test-Class" {...props}>{children}</div>;
};
const StyledFoo = styled(Foo)``
const Main = styled.div`
${StyledFoo} {
background: #f00;
}
`
Code Sandbox
import { render } from "react-dom";
import React from "react";
import styled from "styled-components";
const Foo = (props) => {
const { className, children } = props;
return <div className={className}>{children}</div>;
};
const Bar = styled(Foo)``;
const Main = styled.div`
${Bar} {
background-color: #000;
color: #fff;
}
`;
const App = () => {
return (
<Main>
{" "}
<Bar>Hello </Bar>{" "}
</Main>
);
};
render(<App />, document.getElementById("root"));
https://codesandbox.io/s/styled-components-forked-5s201?file=/index.js

Styled-component object can't observe mobx changes

I would like an HOC generated by styled-components to re-render when one of its properties get changed. I'm using MobX for change detection.
This doesn't respond to changes, I think I understand why. The question is if there is a simple workaround to make it work.
const DemoComponent = observer(styled.div`
background-color: ${props => props.myObject.myObservableIsTrue ? 'red' :
'green'};
`);
It's hard to tell by this little snippet, but one of my guesses would be you are not injecting any store, so currently, no store is being connected to your component.
here's a simple example of how I used styled-components with mobx if it helps:
EDITED:
I've updated the code example.
Do you know the Container / Presentational pattern?
This was the missing link.
In order to keep your renders as little as possible
you need to separate your stateful component from each other.
Spread them across a Container component (aka Dumb Components)
This way you separate state concerns and render only the component with the changed state.
UPDATED:
https://codesandbox.io/s/zxx6o2pq3l
Sorry!!! A bit of a hack job, but attempt to bring your entire code inside the #inject("store") class:
import React from "react";
import { observer, inject } from "mobx-react";
import styled, { css } from "styled-components";
#inject("store")
#observer
export default class OtherComponent extends React.Component {
render() {
const MyWrapper = (store) => {
const Wrapper = styled.div`
border: 1px solid black;
display: flex;
justify-content: space-between;
color: ${({ color }) => color};
border: 2px solid ${({ color }) => color || "black"};
padding: 10px;
margin-bottom: 10px;
`;
return (
<Wrapper {...store}>
styled-component
<button onClick={store.changeColor}>change color</button>
</Wrapper>
);
}
const { store } = this.props;
return (
<div>
{
MyWrapper(store)
}
</div>
);
}
}
Mobx is actually read like this: #inject("store") #observer export default class...
So it really an extension of an extended component; only wrapped variables will apply!

Categories

Resources