How do you test the functionality of less CSS with Jest? - javascript

I've recently joined a new organisation and they use a lot of CSS to hide/show elements.
First of all, is this good practice? I have always (in most cases) shown and hidden components using a boolean to add or remove it from the dom (this is also easy to test)
Whilst trying to add tests using #testing-library/react I've found that the classes are visible by using the identity-obj-proxy module.
However, when trying to test the functionality of an element being visible or not, it becomes difficult because I don't think the less code is being compiled.
Is it possible to compile less code so it will be reflected in the tests?
Could it be something to do with the classnames module being used?
failing test
it('should open and close when clicked', async () => {
render(
<Collapse
label="Collapse"
testId="collapse-test"
isHidden
>
<div>
<h1>just some demo text</h1>
</div>
</Collapse>
)
const content = screen.getByTestId('collapse-test-content')
expect(content).not.toBeVisible()
userEvent.click(screen.getByTestId('collapse-test-button'))
await waitFor(() => expect(content).toBeVisible())
})
====================result====================
expect(element).not.toBeVisible()
Received element is visible:
<div aria-expanded="false" class="accordionContent contentHidden" data-testid="collapse-test-content" />
38 | )
39 | const content = screen.getByTestId('collapse-test-content')
> 40 | expect(content).not.toBeVisible()
| ^
41 | userEvent.click(screen.getByTestId('collapse-test-button'))
42 | await waitFor(() => expect(content).toBeVisible())
43 | })
Component
import React, { useState } from 'react'
import cn from 'classnames'
import styles from './styles.less'
const AccordionContent = ({ children, hidden, testId }) => {
const displayClass = hidden ? styles.contentHidden : styles.contentBlock
const accordionContentClass = cn(styles.accordionContent, displayClass)
return (
<div
className={ accordionContentClass }
aria-expanded={ !hidden }
data-testid={ `${testId}-content` }
>
{children}
</div>
)
}
const CollapseComponent= ({
isHidden,
onClick,
label,
children,
testId
}) => {
const [hidden, toggleHidden] = useState(isHidden)
const handleOnpress = () => {
toggleHidden((curr) => !curr)
if (onClick) { onClick }
}
return (
<div
className={ styles.accordionWrapper }
data-testid={ testId }
>
<AccordionButton
onPress={ handleOnpress }
buttonLabel={ label }
testId={ testId }
/>
<AccordionContent
hidden={ !!hidden }
testId={ testId }
>
{children}
</AccordionContent>
</div>
)
}
styles.less
.accordion-content {
background-color: #preservica-gray-1;
display: flex;
}
.content-hidden {
display: none;
}
.content-block {
display: flex;
}
jest.config
const config = {
testEnvironment: 'jsdom',
coverageThreshold: {
global: {
statements: 80,
branches: 75,
functions: 75,
lines: 80
}
},
testPathIgnorePatterns: [
"./src/components/atoms/Icons",
"./src/models"
],
coveragePathIgnorePatterns: [
"./src/components/atoms/Icons",
"./src/models"
],
setupFilesAfterEnv: [
"<rootDir>/src/setupTests.ts"
],
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "identity-obj-proxy",
"^#root(.*)$": "<rootDir>/src$1",
"^common(.*)$": "<rootDir>/src/common$1",
"^translation(.*)$": "<rootDir>/src/translation$1",
"^view(.*)$": "<rootDir>/src/view$1",
"^actions(.*)$": "<rootDir>/src/actions$1",
"^usecases(.*)$": "<rootDir>/src/usecases$1",
"^repository(.*)$": "<rootDir>/src/repository$1",
"^models(.*)$": "<rootDir>/src/models$1",
"^router(.*)$": "<rootDir>/src/router$1",
},
transform: {
"^.+\\.(ts|tsx|js|jsx)$": "ts-jest",
},
snapshotSerializers: [
"enzyme-to-json/serializer"
]
}

You can read more on how Jest handles mocking CSS modules in the Jest docs. You could perhaps write your own module name mapper or custom transform to load and process the Less files. However, you'd have to figure out how to actually inject the CSS into the code under test (that's something that Webpack normally handles). Something like jest-transform-css might do this.
Personally, I'd just test whether the CSS class is present, like #jonrsharpe suggests. Think of it from the perspective of the test pyramid: your Jest tests should likely be focused at the unit test level, with an emphasis on speed and simplicity. Unit tests are ideally fast enough that you can run them nearly instantly, whenever you save a file; adding the complexity to parse and insert Less CSS may work against that.
It's okay if the unit tests don't test the entire stack; you have other tests, higher up in the pyramid, to do this. For example, you could have a handful of Cypress tests that run your app in the actual browser and verify that a couple of controls are actually hidden, then it should be safe to assume that (1) Jest validating all controls set the correct class plus (2) Cypress validating that a few controls with the correct class are correctly hidden means that (3) all controls are correctly hidden.
To help make your tests more self-documenting, and to make them easier to maintain if you ever change how controls are shown and hidden, you can use Jest's expect.extend to make your own matcher. Perhaps something like this (untested):
expect.extend({
toBeVisibleViaCss(received) {
const pass = !received.classList.contains('content-hidden');
const what = pass ? 'not visible' : 'visible';
return {
message: () => `expected ${received} to be ${what}`,
pass,
};
},
});
First of all, is this good practice? I have always (in most cases) shown and hidden components using a boolean to add or remove it from the dom (this is also easy to test).
Hiding components via CSS is certainly not what I'm used to. Without knowing your codebase, I'd wonder if the developers were used to previous jQuery-style approaches of hiding via manipulating the class lists. The main advantage I'm aware of keeping components always rendered is that you can animate their transitions if you want to. I'm not sure how performance compares; the browser might find it faster to toggle a CSS class than to add or remove an element, but removing the element means that React has less to render, which could help performance.

Related

How can I set 2 values for one variable, and the component will render with one value, depending of some prop input

I'm new here, So if my question is not good, Please let me know so that I can edit.
I'm using ReactJS + Material UI. I have a component, but I want this component to be rendered with different properties depending on the props, like this:
In the page where I want render the component:
<AdBanner vertical={true} />
Inside my AdBanner component I have:
export default function AdBanner(props) {
const [adWidth, setAdWidth] = useState("100%");
const [adHeight, setAdHeight] = useState("90px");
const [adSpacing, setAdSpacing] = useState(2);
const [adDirection, setAdDirection] = useState("row");
useEffect(() => {
if (props.vertical) {
console.log("ola");
setAdWidth("320px");
setAdHeight("480px");
setAdSpacing(5);
setAdDirection("column");
}
}, [props.vertical]);
return (
<>
<Paper
variant="outlined"
sx={{ width: { xs: "100%", md: adWidth }, overflow: "hidden" }}
>
...
My goal is, when I don't specify a value for the "vertical" property my component has certain characteristics (like height, width, ... ). But in some parts of my application I want a set of others values ​​for the same property.
I was getting some errors, with the help of #guilfer I was able to eliminate these errors.
Can anyone tell me if what I did is correct? Thanks.
Here the full code:
https://github.com/brunovjk/saude-vapor
Thank you.
There are some things on your code that you can't actualy do in javascript.
When a variable is defined using the "var", it has global scope.
Here is an explanation about the differences: https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/
After that, there are some other things that we don't actualy do in React, usualy in your case, there would be two different classes with the style you want, then, for aplying the style you could do something similar to this:
import style from "style.module.css"
export default function AdBanner(props) {
return <div className={props.vertical? style.firstClass : style.secondClass}>banner content<div>
}
.firstClass {
...one css styles
}
.secondClass {
...another css style
}

How do you optimize high total blocking time vue

I have encountered problem that I am not capable of solving. I have high total blocking time on my page (2+ sec). I have tried loading every vue component asynchronously, but it does not matter, still 2+ sec tbt. I don't really understand what can cause such high tbt and what can I do about it, as it is just a simple page without much underlying logic (https://i.stack.imgur.com/o7LSk.png) (Just 21 simple cards).
I have removed everything I can, compressed code, and left only the most nessesary stuff. Still it does not solve the issue. Is there any way to make it go down to 100-200ms? What can cause such a problem in your experience?
I have high amount of components though (cards, buttons, lazy-load picture, rating), so in the end there will be around 100-300 components on page. But I don't see there any possibilities of removing it, as this will break neat structure
The only way I found to fix high total blocking time in this case, is to use intersection observer and load it only when it enters the screen. I think same job can do some virtual scroller plugin.
<template>
<div class="grid">
<div class="grid__item" v-for="item in items" :data-id="item.id">
<Item v-if="itemsInScreen[item.id]" :item="item" />
</div>
</div>
</template>
<script>
export default {
name: 'Grid',
props: {
items: {required: true}
},
data() {
return {
itemsInScreen: {}
}
},
methods: {
initObserver() {
const callback = entries => {
entries.forEach(entry => {
if(entry.isIntersecting) this.$set(this.itemsInScreen, entry.target.dataset.id, true);
});
};
const options = {
rootMargin: "20px 20px 20px 20px"
};
const observer = new IntersectionObserver(callback, options);
const itemEls = this.$el.querySelectorAll('.grid__item');
itemEls.forEach(itemEl => observer.observe(itemEl));
}
},
mounted() {
this.initObserver();
}
}
</script>

How to get the value of H1 tag and test it using Jest and RTL

I'm trying to test my component if it displays the right value in my h1.
Below is my component that will be tested.
const Title = () => {
return (
<>
<h1 aria-label='Title of the project'>MY RTL PROJECT</h1>
</>
);
};
export default Title;
and here is my test file that tries to check if my component displays "MY RTL PROJECT".
import { render, } from '#testing-library/react';
import Title from '../Title';
describe('Checks the title component', () => {
it('checks the value of the Title component', () => {
const { getByText } = render(<Title />);
const titleValue = getByText('MY RTL PROJECT')
expect(titleValue).toBe('MY RTL PROJECT')
})
})
and here is the error: "messageParent" can only be used inside a worker
● Checks the title component › checks the value of the Title component
expect(received).toBe(expected) // Object.is equality
Expected: "MY RTL PROJECT"
Received: <h1 aria-label="Title of the project">MY RTL PROJECT</h1>
This line:
const titleValue = getByText('MY RTL PROJECT')
...is returning you an element, not the text the element contains. So instead of this line:
expect(titleValue).toBe('MY RTL PROJECT')
...you might want to use jest-dom toHaveTextContent():
expect(titleValue).toHaveTextContent('MY RTL PROJECT')
That said-- you are getting an element by text, then verifying it contains the text you just used to query it, so that test might not be of very high value. It might make more sense to just verify that it is there:
const titleValue = getByText('MY RTL PROJECT')
expect(titleValue).toBeInTheDocument();
...which is also from jest-dom.
Also, although I cannot say with certainty, the usage of aria-label to apply text to your heading that is different than the text the heading contains seems dubious to me-- you might want to verify that that doesn't represent an accessibility anti-pattern.
I suggest to use getByRole() or queryByRole(), that way you don't even have to specify the text,...
const titleValue = screen.getByRole('heading');
expect(titleValue).toBeInTheDocument();
expect(titleValue).toHaveTextContent(/my rtl project/i);
... although you can specify text, if you want to:
const titleValue = screen.getByRole('heading', { name: /my rtl/i });
More information about *ByRole functions:
https://testing-library.com/docs/queries/byrole/

Checking one conditional React prop does not satisfy TS

I'm having trouble understanding Typescript. I want to define a <Component/> with one required prop requiredProp, and a condition prop extend which if true, allows to use extendedProp.
e.g.
<Component requiredProp={''} /> // OK
<Component requiredProp={''} extend={true} requiredProp={Function} /> // OK
<Component requiredProp={''} requiredProp={Function} /> // ERROR
<Component requiredProp={''} extend={true} /> // ERROR
My code:
// types
interface RequiredProps {
requiredProp: 'string';
}
type ExtendedProps =
| {
extend?: false;
extendedProp?: never;
}
| {
extend: true;
extendedProp: Function;
};
type ComponentProps = RequiredProps & ExtendedProps;
// component
const Component: React.FC<ComponentProps> = ({requiredProp, extend, extendedProp}) => {
if (extend) {
extendedProp(); // ERROR: Cannot invoke an object which is possibly 'undefined'.
}
}
If I am checking extend to be true, shouldn't TS automatically know from ExtendedProps that extendedProp is also defined?
Compiler is only satisfied if I am explicitly checking extendedProp to be defined:
if (extendedProp) {
extendedProp(); // no error
}
If you do this using the entire props object, it will work. checking for props.extend will narrow down the type on props, and thus props.extendedProp will be allowed:
const Component: React.FC<ComponentProps> = (props) => {
if (props.extend) {
props.extendedProp();
}
}
But if you've already split them up into separate variables, then this can't happen. props isn't involved in your if statement at all, so the type on props can't be narrowed. There are just two unrelated local variables, and checking the type on one doesn't do anything to the type on the other.
You and i can recognize that the two variables are related to eachother, but typescript doesn't have the ability to back track to find the origin of the type, and deduce what that means for other types. It may seem simple in this case, but keep in mind that there are a huge variety of lines of code that could be written that would break the linkage, such as:
const Component: React.FC<ComponentProps> = ({requiredProp, extend, extendedProp}) => {
extend = Math.random() > 0.5
if (extend) {
extendedProp();
}
}
Figuring out the implications of those kinds of things is impractical (or maybe impossible);
Playground link

Babel plugin error unknown node of type undefined with constructor "String"

I am building a shared component library that will be used by both React and Vue.
I am using Styletron, which requires a framework-specific adapter, but otherwise works pretty much the same.
So from my source code (a bunch of functions) I need to generate a folder with the code transpiled as normal and then another folder where the functions are modified slightly.
This is my code:
const MyComponent = styled('div', ({ isActive, hasBorder}) => ({
color: 'red'
}))
// should not change
const OtherComponent = styled('div', {
background: 'blue'
})
And it should become:
const MyComponent = styled('div', ({ isActive, hasBorder}) => ({
color: 'red'
}), ['isActive', 'hasBorder'])
const OtherComponent = styled('div', {
background: 'blue'
})
I actually have a working example of this working in ASTExplorer, but when I try to make a plugin out of it, I encounter the error Babel plugin error unknown node of type undefined with constructor "String"
This is my first plugin, and I KNOW that I am doing some stuff wrong, but right now I just need to find out what I have to do to make this work outside of ASTExplorer.
This is the plugin I have written in ASTExplorer:
export default function(babel) {
const { types: t } = babel;
return {
name: "ast-transform",
visitor: {
CallExpression: (path) => {
if (
path.node.callee.name === 'styled' &&
path.node.arguments.length === 2 &&
t.isArrowFunctionExpression(path.node.arguments[1])
) {
if (t.isObjectPattern(path.node.arguments[1].params[0])) {
const props = []
path.node.arguments[1].params[0].properties.map(prop => props.push(prop.value.name))
path.node.arguments.push(JSON.stringify(props)) // I suspect that the error is from here
}
}
}
}
};
}
Babel transforms work with AST nodes, so stringifying the props and pushing them into the arguments list in this way won't work quite right. You'll want to actually create an AST structure from your object.
In this case, Babel offers a helper for this, so you can change
path.node.arguments.push(JSON.stringify(props))
to
path.node.arguments.push(t.valueToNode(props))

Categories

Resources