Why Enzyme not load style from React component? - reactjs

I was trying to test my component style with Jest and Enzyme. I'm using TailwindCSS as my CSS framework and non ejected create-react-app. So here is my component:
import React from 'react'
import './tailwind.css' // the tailwind css
export function Alert({ children, className, icon, ...resProps }) {
return (
<div data-testid="qa-alert" className="my-2 p-3 text-lg bg-red-200 rounded-md flex items-center justify-center} {...resProps}>
{icon && React.createElement(icon, { className: 'mr-2 text-xl' })}
{children}
</div>
);
}
I want to test that the background color is red (bg-red-200 or #fed7d7) by using the getComputedStyle method, here is my test:
it('Should have red background color', () => {
const wrapper = mount(<Alert />);
const alert = wrapper.find('div');
const style = getComputedStyle(alert.getDOMNode());
console.log(style);
expect(style.backgroundColor).toBe('#fed7d7');
});
but the test fails because I don't get the background style. The received value is empty string. and the log of style is
CSSStyleDeclaration {
'0': 'display',
_values: { display: 'block' },
_importants: { display: '' },
_length: 1,
_onChange: [Function]
}
how can i get the style?

You can try to use jest-dom method toHaveStyle():
expect(alert).toHaveStyle({backgroundColor: '#fed7d7'});

You can archive it by update:
The selector wrapper.find('div') is too generic. Use wrapper.find('div[data-testid="qa-alert"]') or wrapper.find({'data-testid': "qa-alert"]})
use Enzyme hasClass to test
Example:
it('Should have red background color', () => {
const wrapper = mount(<Alert />);
const alert = wrapper.find({'data-testid': "qa-alert"]});
expect(alert.hasClass('bg-red-200')).toEqual(true);
});

Related

Dark theme does not work with React Stitches

I'm using Stitches in React to handle my CSS and theming. I have the following code:
import React from 'react'
import { createStitches, globalCss } from '#stitches/react'
const { theme, createTheme } = createStitches({
theme: {
colors: {
text: 'blue',
bodyBg: 'lightgray',
},
},
})
const darkTheme = createTheme('dark-theme', {
colors: {
bodyBg: 'black',
},
})
const globalStyles = globalCss({
'body': {
color: '$text',
backgroundColor: '$bodyBg'
},
});
function App() {
globalStyles()
return (
<div className='App'>
<h1>Hello World!</h1>
</div>
)
}
export default App
As you can see, I have a default theme, and then a dark theme that extends the default theme while overriding some properties (in this case, the bodyBg). I'm applying these styles directly in my <body>. The default theme works fine, but the dark theme does not. When I add the .dark-theme class to my <html>, nothing changes (the background should turn black). What exactly am I doing wrong here?
You are probably trying to add the class directly to the body in the developer tools which doesn't work.
I managed to make it work with a button onClick event:
const darkTheme = createTheme('dark-theme', {
colors: {
bodyBg: 'black',
},
})
function App() {
const setBlackTheme = () => {
document.body.classList.remove(...document.body.classList);
// Here we set the darkTheme as a class to the body tag
document.body.classList.add(darkTheme);
}
globalStyles()
return (
<div className='App'>
<button onClick={setBlackTheme}>Dark Theme</button>
<h1>Hello World!</h1>
</div>
)
}
export default App
Try it and let's see if it works for you as well.

Rendered Component doesn't show in storybook

I want to render MUI Button Component in my storybook and then change MUI attributes(variant, color, disabled, etc.) from storybook. I could get result when working on simple plain html button but it doesn't work on MUI one. The current result is it doesn't show any error in terminal, nor in browser. When I type something in args it's visible on Storybook Controls but Button itself doesn't show up itself. Here is the code of MyNewButton.tsx file.
import Button from "#mui/material/Button"
export const MyNewButton = () => {
return <Button />
}
and here is the code of MyNewButton.stories.tsx
import { ComponentMeta, ComponentStory } from "#storybook/react"
import { MyNewButton } from "./MyNewButton"
export default {
title: "MyComponents/MyNewButton",
component: MyNewButton,
} as ComponentMeta<typeof MyNewButton>
const Template: ComponentStory<typeof MyNewButton> = (args) => (
<MyNewButton {...args} />
)
export const First = Template.bind({})
First.args = {
label: "MyNewButton",
backgroundColor: "red",
}
and Here is the storybook screenshot.
So, I can see you are overlooking some basic steps and I'm going to point them out to you. Your Storybook is rendering fine.
First, you are rendering a button from MUI without a label/text. You are supposed to have an opening tag and a closing tag for the button.
so, it should be <Button>Hello World!</Button> and not <Button />.
After fixing that, the next step is to use your args in your MyNewButton component so that storybook will know what to change your background color to.
import { FC } from "react";
type ButtonProps = {
backgroundColor: string;
label: string;
};
export const MyNewButton: FC<ButtonProps> = (props) => {
const { backgroundColor, label } = props;
const style = {
backgroundColor,
};
return (
<Button variant="contained" style={style}>
{label}
</Button>
);
};
Your button should be rendering fine correctly now and to make change the background in your app, you just have to pass in the props like so:
<MyNewButton backgroundColor={"green"} label={"My New Button"} />

code block not displaying when previewing in react-quill

In this code, I am trying to insert a code block using react-quilljs
import React, { useState } from 'react';
import hljs from 'highlight.js';
import { useQuill } from 'react-quilljs';
import 'quill/dist/quill.snow.css'; // Add css for snow theme
export default () => {
hljs.configure({
languages: ['javascript', 'ruby', 'python', 'rust'],
});
const theme = 'snow';
const modules = {
toolbar: [['code-block']],
syntax: {
highlight: (text) => hljs.highlightAuto(text).value,
},
};
const placeholder = 'Compose an epic...';
const formats = ['code-block'];
const { quill, quillRef } = useQuill({
theme,
modules,
formats,
placeholder,
});
const [content, setContent] = useState('');
React.useEffect(() => {
if (quill) {
quill.on('text-change', () => {
setContent(quill.root.innerHTML);
});
}
}, [quill]);
const submitHandler = (e) => {};
return (
<div style={{ width: 500, height: 300 }}>
<div ref={quillRef} />
<form onSubmit={submitHandler}>
<button type='submit'>Submit</button>
</form>
{quill && (
<div
className='ql-editor'
dangerouslySetInnerHTML={{ __html: content }}
/>
)}
</div>
);
};
Using the above code, I get the following preview of the editor's content
There are two problems with this:
There is no code syntax highlighting, as I want to achieve this using the highlihgt.js package, inside the code block inside the editor, and
The code block is not displayed (with the black background and highlighting syntax when it's working) in the previewing div outside the editor.
How can I fix these two issues?
Your code is getting marked up by highlight.js with CSS classes:
<span class="hljs-keyword">const</span>
You are not seeing the impact of those CSS classes because you don't have a stylesheet loaded to handle them. You need to choose the theme that you want from the available styles and import the corresponding stylesheet.
import 'highlight.js/styles/darcula.css';
Look at the css in the editor mode. It depends on two class names ql-snow and ql-editor.
You can fix this issue by wrapping it around one more div with className ql-snow.
<div className='ql-snow'>
<div className='ql-editor' dangerouslySetInnerHTML={{ __html: content }}>
<div/>
</div>
This should work.
I got the same issue and when I used hjls what happened was that I got syntax highlighting in the editor but not in the value.
If you noticed the syntax gets highlighted some seconds after you write the code block, this means that the value gets set before the syntax gets highlighted.
So, I just set the value after 2 seconds using setTimeout and this solved my problem
Like this:
<ReactQuill
theme="snow"
value={value}
onChange={(content) => {
setTimeout(() => {
setValue(content)
}, 2000)
}}
modules={modules}
formats={formats}
bounds="#editor"
placeholder="Write something..."
className="text-black dark:text-white"
/>
I recently implemented this logic into my project. I used React Quill for the text editor, implemented syntax highlighting to it using highlight.js, and I also used React Markdown to display the formatted content on my website. React Markdown by default works with markdown, so you need a plugin (rehype-raw) to get it to parse HTML. This is my code, from my project. Just remove some of the unnecessary stuff from here that is specific to my project and use what you need.
// PLUGINS IMPORTS //
import { Typography } from "#mui/material";
import { useEffect, useState } from "react";
import hljs from "highlight.js";
import "react-quill/dist/quill.core.css";
import "react-quill/dist/quill.snow.css";
import "highlight.js/styles/atom-one-dark.css";
import ReactQuill from "react-quill";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
// COMPONENTS IMPORTS //
import { CreateButton } from "components/atoms";
// EXTRA IMPORTS //
import styles from "./create-post.module.css";
import { ETheme } from "types/theme";
/////////////////////////////////////////////////////////////////////////////
type CreatePostProps = {};
hljs.configure({
// optionally configure hljs
languages: ["javascript", "python", "c", "c++", "java", "HTML", "css", "matlab"],
});
const toolbarOptions = [
["bold", "italic", "underline", "strike"],
["blockquote", "code-block"],
[{ list: "ordered" }, { list: "bullet" }],
["link"],
[{ indent: "-1" }, { indent: "+1" }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ align: [] }],
];
const modules = {
syntax: {
highlight: function (text: string) {
return hljs.highlightAuto(text).value;
},
},
toolbar: toolbarOptions,
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
};
const formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"code-block",
"list",
"bullet",
"indent",
"link",
"align",
];
const placeholder = "Description";
const CreatePost = (props: CreatePostProps) => {
const [markdownText, setMarkdownText] = useState("");
useEffect(() => {
console.log({ markdownText });
}, [markdownText]);
return (
<main className={`${styles["create-post-wrapper"]}`}>
<header className={`${styles["create-post-header"]}`}>
<Typography variant="h6">Create a post</Typography>
</header>
{/* making the border a seperate div makes it easier to apply margin */}
<div className={`${styles["border"]} ${styles["top"]}`}></div>
<div>Choose a community</div>
<article className={`${styles["create-post"]}`}>
<section className={`${styles["inner-create-post"]}`}>
<section>Title</section>
<ReactQuill
value={markdownText}
onChange={value => setMarkdownText(value)}
theme="snow"
modules={modules}
formats={formats}
placeholder={placeholder}
/>
<div className="ql-snow">
<div className="ql-editor">
<ReactMarkdown children={markdownText} rehypePlugins={[rehypeRaw]} />
</div>
</div>
<div className={`${styles["border"]} ${styles["bottom"]}`}></div>
<section className={`${styles["post-button"]}`}>
<CreateButton
theme={ETheme.LIGHT}
buttonText="Post"
buttonProps={{
fullWidth: false,
}}
/>
</section>
</section>
</article>
</main>
);
};
export default CreatePost;
You can always add more options to toolbarOptions, but don't forget to also add them to formats if you do. Also, if you want to keep formatting anywhere else in your website, you need the two divs with these 2 classes around your markdown.
React Quill bacially saves everything into a string with HTML and classes, you import styles for those classes and it works like magic.

Can't update the font size in react using tailwindcss library

I am trying to create a component that has a default font-size(rem) and also the component accepts other classes as props.
the problem is the component has default font-size class:
.text-base{font-size: 1rem}
and I want to have more control over the component by passing classes as props with smaller font-size but the default font-size doesn't allow.
The component:
<Info className="text-sm">Test component</Info>
code inside the component:
const Info = ({ children, className = "" }) => {
return (
<div
className={cn("flex p-4 text-base grey", {
[className]: className
})}
>
<div>{children}</div>
</div>
);
};
https://codesandbox.io/s/react-functional-component-forked-ebwni?file=/src/Info.js
Thanks
What you wanted is whenever you create a new "Info" component get it with a "text-base" that represents font-size of 1rem as tailwindcss documentation shows here https://tailwindcss.com/docs/font-size
so what I did was that I removed "text-base" from className and create a new state using useState hooks that will be updated only if the passed props className from the Info component does not include "text" string inside it which means that the created component is without a font size initialization so in that case initialize to "text-base" so the code will be as the following:
import cn from "classnames";
import React, { useEffect, useState } from "react";
const Info = ({ children, className = "" }) => {
const [baseClass, setBaseClass] = useState("");
const checkText = () => {
if (!className.includes("text")) {
setBaseClass("text-base");
}
};
useEffect(() => {
checkText();
});
return (
<div
className={cn(
"flex p-4 grey",
{
[className]: className
},
{
[baseClass]: baseClass
}
)}
>
<div>{children}</div>
</div>
);
};
export default Info;
https://codesandbox.io/s/react-functional-component-forked-5807v?file=/src/Info.js

Cannot check expect(elm).not.toBeVisible() for semantic-ui react component

I'm trying to test a react component and use expect(elm).not.toBeVisible() without success.
Update 3
I have cut down the code into this simpler form:
// ./TestItem.js
import React from 'react'
import './TestItem.css'
export default ({ hide }) => {
return <div className={hide ? 'shouldHide' : ''}>Text</div>
}
// ./__tests__/TestItem.test.js
import React from 'react'
import { render } from 'react-testing-library'
import TestItem from '../TestItem'
import 'jest-dom/extend-expect'
import 'react-testing-library/cleanup-after-each'
test.only('TestItem should render correctly', async () => {
const { getByText, debug } = render(<TestItem hide={true} />)
const itemNode = getByText('Text')
debug()
expect(itemNode).not.toBeVisible()
})
// ./TestItem.css
.shouldHide {
display: none;
}
Test result:
TestItem should render correctly
expect(element).not.toBeVisible()
Received element is visible:
<div class="shouldHide" />
7 | const itemNode = getByText('Text')
8 | debug()
> 9 | expect(itemNode).not.toBeVisible()
| ^
10 | })
11 |
debug() log:
console.log node_modules/react-testing-library/dist/index.js:58
<body>
<div>
<div
class="shouldHide"
>
Text
</div>
</div>
</body>
Update 2:
Okay it's getting pretty weird because I got the test to pass on codesanbox but still find no luck on my local machine.
My original question:
I use React, semantic-ui-react and react-testing-library.
Here is the code:
// ComboItem.test.js
import React from 'react'
import ComboItem from '../ComboItem'
import { render } from 'react-testing-library'
import comboXoi from '../images/combo-xoi.jpg'
import 'path/to/semantic/semantic.min.css'
describe('ComboItem', () => {
test('should render', async () => {
const { getByText, debug } = render(
<ComboItem image={comboXoi} outOfStock={false} />
)
const outOfStockNotice = getByText('Out of stock')
debug()
expect(outOfStockNotice).not.toBeVisible()
})
})
// ComboItem.js
import React from 'react'
import { Card, Image } from 'semantic-ui-react'
export default ({ image, outOfStock = false }) => {
return (
<Card>
<Image
src={image}
dimmer={{
active: outOfStock,
inverted: true,
'data-testid': 'combo-item-dimmer',
content: (
<span style={{ marginTop: 'auto', color: 'black' }}>
Out of stock
</span>
),
}}
/>
</Card>
)
}
What i get is the result here:
ComboItem › should render
expect(element).not.toBeVisible()
Received element is visible:
<span style="margin-top: auto; color: black;" />
at Object.test (src/app/screens/App/screens/SaleEntries/screens/CreateSaleEntry/screens/StickyRiceComboSelect/__tests__/ComboItem.test.js:14:34)
at process._tickCallback (internal/process/next_tick.js:68:7)
I have tried to see the component render result on the browser and the node outOfStockNotice in the test code is actually hidden because its parent, which is a div with class dimmer has style display: none.
According to jest-dom doc (which is used by testing-react-library:
toBeVisible
An element is visible if all the following conditions are met:
it does not have its css property display set to none
it does not have its css property visibility set to either hidden or collapse
it does not have its css property opacity set to 0
its parent element is also visible (and so on up to the top of the DOM tree)
Please help. I really don't know what could go wrong here.
Update:
I include the result of debug() here:
console.log node_modules/react-testing-library/dist/index.js:58
<body>
<div>
<div
class="ui card"
>
<div
class="ui image"
>
<div
class="ui inverted dimmer"
data-testid="combo-item-dimmer"
>
<div
class="content"
>
<span
style="margin-top: auto; color: black;"
>
Out of stock
</span>
</div>
</div>
<img
src="combo-xoi.jpg"
/>
</div>
</div>
</div>
</body>
Here is the answer according to the author of react-testing-library himself:
Probably a JSDOM limitation (in codesandbox it runs in the real browser). Actually, the problem is that the css isn't actually loaded into the document in JSDOM. If it were, then that would work. If you can come up with a custom jest transform that could insert the css file into the document during tests, then you'd be set.
So this would work if you were using CSS-in-JS.
So basically the import './TestItem.css' part in the test will not works because JSDOM doesn't load it, therefore jest-dom could not understand the class shouldHide means display: none.
Update:
According to this Stack Overflow thread, you can insert css into jsdom:
import React from 'react'
import { render } from 'react-testing-library'
import TestItem from '../TestItem'
import fs from 'fs'
import path from 'path'
test.only('TestItem should render correctly', async () => {
const cssFile = fs.readFileSync(
path.resolve(__dirname, '../TestItem.css'),
'utf8'
)
const { container, getByText, debug } = render(<TestItem hide={true} />)
const style = document.createElement('style')
style.type = 'text/css'
style.innerHTML = cssFile
container.append(style)
const itemNode = getByText('Text')
debug()
expect(itemNode).not.toBeVisible()
})
And then the test should pass.

Resources