React testing library can't read styles using Tailwind CSS classes - reactjs

I have a simple React component that will initially have a Tailwind CSS class of hidden which apply CSS display: none and will change the class to visible on button click.
When I test with expect().not.toBeVisible() it tells me the element is already visible while it has a hidden class.
If I don't use Tailwind CSS and use a normal style={{display: 'none'}} it'll correctly identify that the element isn't visible. That means clearly the issue is with Tailwind CSS.
Here's my test:
test("Notification bar should be initially hidden but visible on click", async () => {
render(<Notifications />);
expect(await screen.findByTestId("list")).not.toBeVisible();
// this test fails while the element already has a Tailwind CSS class of "hidden"
});
While this's my component:
<ul className="hidden" data-testid="list">
<li>item 1</li>
</ul>

The solution explained in this Stack Overflow: cannot check expectelm not tobevisible for semantic ui react component. Based on that thread, I extend the solution to make it works with TailwindCSS as the steps explained below,
Project structure
root/
src/
test/
index.css
test-utils.tsx
component.test.tsx
index.css
1. Generate CSS from the TailwindCSS template files
By issuing the command below, the CSS file called index.css will be generated in src/test directory
npx tailwindcss -i ./src/index.css -o ./src/test/index.css
Further reading: TailwindCSS installation
2. Create custom render function
Next we need to inject the generated CSS file into the JSDOM. Custom render function will be useful so we won't be needed to repeat this task for each test
import { render, RenderOptions } from '#testing-library/react';
import React, { FC, ReactElement } from 'react';
import fs from 'fs';
const wrapper: FC<{ children: React.ReactNode }> = ({ children }) => {
return <>{children}<>;
};
const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => {
const view = render(ui, { wrapper, ...options });
const style = document.createElement('style');
style.innerHTML = fs.readFileSync('src/test/index.css', 'utf8');
document.head.appendChild(style);
return view;
};
export * from '#testing-library/react';
export { customRender as render };
Further reading: Testing Library Setup
3. Perform testing, unit test suppose to be success now
import React from 'react';
import { render, screen } from './test-utils';
test('Renders hidden hello world', () => {
render(<span className="hidden">Hello World</span>);
expect(screen.getByText('Hello World')).not.toBeVisible();
});
Why souldn't we use toHaveClass matchers instead?
it wouldn't align with the Testing Library guiding principle of “emphasize a focus on tests that closely resemble how your web pages are interacted by the users“ because by doing so, you are interacting with the component unnaturally

Related

how can i ise i18n in unit test from react testing library

I'm trying to get translations from i18n files in my unit testing, I've seen other answers but they work with just one i18n file, My problem is that, I have 2 files and the folder structure is like this,
i18n/en/translation.json
i18n/es/translation.json
and translation.json file is written like this
{... "info":"information", "name":"Name", ...}
doesn't have an export default.
and here is my test file,
import React from 'react'
import '#testing-library/jest-dom'
import {render} from '#testing-library/react'
import AddUsers from '../../components/AddUsers'
test('Render OK',()=>{
const menuLinkUp =false
const component =render(
<AddUsers/>
)
component.getByText(" how can i call my i18n?")
})
I'm using react testing library and jest for doing this.
There is a section in the documentation: https://react.i18next.com/misc/testing.
I would probably mock the react-i18next module, as it requires the least amount of changes.
jest.mock('react-i18next', () => ({
// this mock makes sure any components using the translate HoC receive the t function as a prop
withTranslation: () => Component => {
Component.defaultProps = { ...Component.defaultProps, t: () => "" };
return Component;
},
}));
(If you actually want to "inject" the translations: https://react.i18next.com/misc/testing#example-test-using-this-configuration)

How to inline/include css in a react library package

I'm trying to build a react component library that I can use in other applications and I'm running into an issue of packaging css/less with the component library.
in the component library package:
component.tsx
import React, { ReactElement } from 'react';
import styles from "./component.less";
const TestComponent: React.FC = (): ReactElement => {
....
return (
<div className={styles.root}>
....
</div>
);
};
export default TestComponent;
component.less
#bg-color: #428bca;
.root {
background-color: #bg-color;
}
I run the below commands to build the library:
> tsc --emitDeclarationOnly
> lessc src/component.less dist/library.css
> babel src --out-dir dist --extensions .ts,.tsx --source-maps
> copyfiles -u 1 src/**/*.less dist/
and in the dist directory get this:
> index.d.ts
> index.js
> library.css
> component.less
> ....
in a different application I just install the library and use the component:
import TestComponent from "library-package";
<TestComponent /> // in the render somewhere
and when I start this application the component is rendered but the css/less code is not applied and not imported.
I'd like to include some default css inside of the library package. And I know that I could just include the library.css in the index.tsx of an application and the css could work, but I don't want the other application to have to include a css file or have a less loader to handle the css if it doesn't use less itself.
I assume that the problem currently is that the application doesn't know how to handle .less files from the library because it doesn't have a less loader setup (although I don't get an error about anything)
tldr: an application would just install the component library, include the component in a render and the component will render with some css (preferrably using less in the library). Is that possible to do?
I guess I was looking for something like the styled-components but using less + minifying instead of writing the CSS in a JavaScript file and wrapping HTML tags.
With the styled-components library I got it to work this way:
component.tsx
import React, { ReactElement } from "react";
import cssesc from "cssesc";
import styled, { ThemeProvider, DefaultTheme } from "styled-components";
export interface TestComponentTheme extends DefaultTheme {
backgroundColor?: string;
}
const DEFAULT_THEME: TestComponentTheme = {
backgroundColor: "#428bca",
};
const StyledDiv = styled.div`
background-color: ${props => cssesc(props.theme.backgroundColor)};
`;
interface TestComponentInput {
theme?: TestComponentTheme;
}
const TestComponent: React.FC<TestComponentInput> = ({theme}: TestComponentInput ): ReactElement => {
....
let componentTheme: TestComponentTheme = Object.assign({}, DEFAULT_THEME);
if (theme) {
componentTheme= Object.assign({}, componentTheme, theme);
}
return (
<ThemeProvider theme={componentTheme}>
<StyledDiv>
....
</StyledDiv>
</ThemeProvider>
);
};
export default TestComponent;
building the library requires just two steps now:
> tsc --emitDeclarationOnly
> babel src --out-dir dist --extensions .ts,.tsx --source-maps
and there are no CSS files in the dist directory anymore:
> index.d.ts
> index.js
> ....
in a different application I just install the library and use the component (which renders with the CSS inline):
import TestComponent from "library-package";
<TestComponent /> // in the render somewhere
and optionally I can overwrite the CSS of the component:
import TestComponent, { TestComponentTheme } from "library-package";
const customTheme: TestComponentTheme = {
backgroundColor: "#000fff",
};
<TestComponent theme={customTheme} /> // in the render somewhere

Tree shakable icon component

I am creating a custom design system on top of ChakraUI and bootstrap icons.
Bootstrap icons contains 1000+ svg icons.
I created an abstract Icon component to have abstraction over the icon name, that seems IMO more convenient.
However, I am not sure this module will be tree-shaked, and I can't figure out how to change the implementation to make it tree-shakable.
Icon.tsx
import React from 'react'
import * as bootstrapIcons from '#emotion-icons/bootstrap'
// Union of all icon names: 'Alarm' | 'Circle' | 'Square' ...
type IconNames = keyof typeof bootstrapIcons
export const Icon: FC<{name: IconNames}> = ({name}) => {
const Icon = bootstrapIcons[name]
return (
<div>
<Icon style={{...}} />
</div>
)
}
App.tsx
import React from 'react'
import { Icon } from './Icon'
const App = () => {
return <div>
<Icon name="Alarm" />
</div>
}
Astrix imports cannot be tree shaken.
Generally when it comes to Icons you can have a Sprite Sheet or Tree Shaking but not both.
To make this particular library tree shake out icons you'll have to change your imports and usage to look like this (haven't tested but should work).
import { Alarm } from '#emotion-icons/bootstrap/Alarm';
const Use = () => <Alarm/>
There's definitely a caveat here where it would be possible to build a build-time plugin that changes...
<Icon name="Alarm"/>
// to.
import { Alarm } from '#emotion-icons/bootstrap/Alarm';
<Alarm/>
and removes the import.
The reason why astrix imports can't be tree shaken is because javascript is a very dynamic language and static analysis of which keys are used is sometimes very difficult.
Consider the following code
import * as bootstrapIcons from '#emotion-icons/bootstrap'
bootstrapIcons["mralA".reverse()] // which icons should be tree shaken?
const Icons = new Proxy(bootstrapIcons) // which icons should be included? using proxy traps.
const SubSet = PullSubset(bootstrapIcons) // which icons should be included? no matter what the implementation of PullSubset is it will be impossible to preform static analysis on it to determine which icons have been used.

How to load only needed CSS per component (React js)

I've started to code my first React app and it's awesome, but I can't figure out how to manage css files per-component(so the actual CSS won't load if it is not necessary).
React with webpack(correct me if I'm wrong please) wraps the project in such a way that at every given moment the app loads only what it needs(in terms of JS).
So if I have my main App component with only two buttons visible: btn-01 and btn-02, and inside of this component I have another two: component-01 and component-02, and they are hidden till the corresponded button is clicked(btn-01 for component-01), these components won't be loaded until the actual button is clicked(am I getting this right?), however this is not the same with css as I can tell, because I see the css of each of these(component-01 and component-02) components loaded right away the App is loaded, even though none of the buttons are clicked.
I'm not a big fan of inline styling, but I did test it with css module, but the result is the same in this aspect. So I'm not even sure if this is possible to implement in an easy way.
Here's a code, so perhaps I'm not implementing it correctly, but please don't mind the none-DRY code etc.
So as you may see, the style of Component-01 and -02 are loaded even though there is no need for them at the moment(none of the button is pressed).
App.js
import React, { Component } from "react";
import "./App.css";
import Component_01 from "./Component-01/Component-01";
import Component_02 from "./Component-02/Component-02";
class App extends Component {
state = {
isComponent_01: false,
isComponent_02: false,
};
toggleComponent01 = () => {
this.setState({
isComponent_01: !this.state.isComponent_01,
});
};
toggleComponent02 = () => {
this.setState({
isComponent_02: !this.state.isComponent_02,
});
};
render() {
let showComponent_01 = null;
if (this.state.isComponent_01) {
showComponent_01 = <Component_01 />;
}
let showComponent_02 = null;
if (this.state.isComponent_02) {
showComponent_02 = <Component_02 />;
}
return (
<div className="App">
<button className="btn-01" onClick={this.toggleComponent01}>
Btn-01
</button>
<button className="btn-02" onClick={this.toggleComponent02}>
Btn-02
</button>
{showComponent_01}
{showComponent_02}
</div>
);
}
}
export default App;
Component-01.js (and Component-02.js, just with -02.js)
import React from "react";
import style from "./Component-01.module.css";
function App() {
return <div className={style["component-01"]}>Component-01</div>;
}
export default App;

How to unit test a style of a React Component using Jest.js?

I'm building unit tests with JestJS (npm jest-cli) and need to validate that a ReactJS element contains the CSS styles that I'm looking for.
I tried to check
it('should highlight the selected option in the drop-down list', function() {
var iconTriangleDown = TestUtils.findRenderedDOMComponentWithClass(dropList, 'icon-triangle-down');
var iconHeight = window.getComputedStyle(iconTriangleDown.getDOMNode(), null).height;
expect(iconHeight).notToEqual('');
});
That results in
iconHeight === ''
instead of a value of pixels.
I wonder if window is being mocked by Jest. Or if window isn't supported.
This is fairly easy using jest-dom and react-testing-library.
Tiny example:
component.js
const Component = () => <div style={{left: '4rem'}}>Left component</div>;
component.test.js
test("left shoud be 4rem", () => {
const { getByText } = render(<Component />);
expect(getByText(/left component/i).parentElement).toHaveStyle(`left: 4rem`);
})
To anyone finding this thread, Jest Enzyme now has a assert to test Styles: toHaveStyle: https://github.com/blainekasten/enzyme-matchers/blob/master/README.md#tohavestylestylekeystring-stylevalueany
The problem for me was that from seeing the code, is only testing objects and I have many styles which are arrays (I am using React Native BTW) so it was not working for me.
I am doing this method to test for a particular style:
const render = wrapper.dive();
expect(render.find("[testID='bannerStamp']").get(0).props.style[0].right).toBe(7);
You could test styles though snapshot tests, but Jest does not support evaluating component styles through assertions—that is to say through expect.
In order to do this, you need to combine Jest with enzyme, chai, and chai-enzyme.
This combination will allow you to write tests like this simple example:
it('should render style', () => {
chai.expect(shallow(
<div
style={{
left: '4rem'
}}
/>
)).to.have.style('left', '4rem');
});
First, create a setup file and add it to the the jest.setupFiles array in package.json. See the Jest documentation for an overview of this option.
This is my setup file:
// test/setup.js
import React from 'react';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
import { shallow, render, mount } from 'enzyme';
// Add commonly used methods and objects as globals
global.chai = chai;
global.mount = mount;
global.React = React;
global.render = render;
global.shallow = shallow;
chai.use(chaiEnzyme());
This is my package.json:
{
"jest": {
"setupFiles": [
"./test/setup.js"
]
}
}
Now, when necessary, you are able to access the Chai assertions API through chai.expect and the Jest assertions API through expect.

Resources