jest-dom
jest-dom copied to clipboard
`toBeVisible` not working as expected with `visibility` property
-
@testing-library/jest-dom
version: v5.1.1 -
@testing-library/react
version: v9.4.0 -
node
version: v12.14.1 -
npm
(oryarn
) version: v1.21.1
Relevant code or config:
import styled from 'styled-components';
const Container = styled.div`
visibility: hidden;
&:hover {
visibility: visible;
}
> input:checked {
visibility: visible;
}
`;
const { getByTestId } = render(
<Container>
<input data-testid="checkbox" type="checkbox" checked />
</Container>
);
expect(getByTestId('checkbox')).toBeVisible();
What you did:
Tried to test a checkbox that must be visible when it has been checked with toBeVisible
.
What happened:
Even though the checkbox is visible toBeVisible
throws an error because it detects that the parent has visibility: hidden
.
Reproduction:
https://codesandbox.io/s/kind-rain-62otm?fontsize=14&hidenavigation=1&previewwindow=tests&theme=dark
Problem description:
My code has a checkbox that must be visible when the user checks it. The visibility property is perfect for this because it overrides its parents' values.
So, if the checkbox has visibility: visible
property, it will be shown regardless of the parent value.
Suggested solution:
Not sure :( will come up with something if this is really an issue.
The codesandbox does not use any of the code you described. Don't forget to save and freeze the codesandbox after submission.
I suspect this is an issue with jsdom
not implementing CSS cascade. I need a repro to verify though.
@eps1lon thanks for letting me know, just included a test example there.
Perfect. This is indeed a bug.
We're currently assuming that visibility
acts like display
. While display: none
hides any child even if they do set display: block
, visibility: hidden
can be overridden. The difference between display
and visibility
is that visibility
is an inherited CSS property while display
is not.
We need to account for that now that JSDOM implements CSS inheritance.
I've tried to implement but I think JSDOM CSS inheritance doesn't work correctly. By default all elements have visibility: visible
, so:
<section style="display: block; visibility: hidden">
<p>Hello <strong>World</strong></p>
</section>
getComputedStyle
brings visible
for the strong
and p
elements even if the property is not set. It should've brought hidden
because it inherits.
getComputedStyle brings visible for the strong and p elements even if the property is not set. It should've brought hidden because it inherits.
Which jsdom version are you using? It should be working with ^15.2.0
. If you have the implementation ready then you could go ahead an open a PR and I'll have a look at it.
@eps1lon are you sure CSS inheritance is working? Check this code. I've tried to replicate what's being done in the project, see that what's written on the page.
It should be
hidden
hidden
hidden
because of inheritance.
https://github.com/jsdom/jsdom/issues/2160
@eps1lon are you sure CSS inheritance is working?
For visibility
, yes. Seems to be that inline styles are ignored for inheritance. For declaration it's working: https://runkit.com/eps1lon/5e4ea8c4d54b2400136cd768
Oh ok, thanks for explaining @eps1lon . Do you think it's ok if I change this test to use style declaration instead of inline styles?
I prefer additional tests over changing existing ones. This way we make sure we don't regress.
The problem is that implementing this functionality as if the inheritance works will break these tests because it relies on inline styles instead of declared ones.
Write two tests then and comment the wrong one properly so that it is obvious that it will break once jsdom is fixed.
I don't currently have time to set up a sandbox repro, but this same assertion is failing for me when opacity: 0
(using stylesheet styles). I'm only commenting because it seems this assertion may need more refining.
Stylesheet (imported directly into component definition file):
.Dialog {
opacity: 0;
// ...
.Dialog--open {
opacity: 1;
// ...
}
}
And test output failure (verified other classes not setting opacity: 1
):
expect(element).not.toBeVisible()
Received element is visible:
<div aria-labelledby="h9FY4ou1e5jp_LqptbvKB" aria-modal="true" class="Dialog Dialog--with-close-icon ModalDialog" data-component="ModalDialog" data-focus-lock-disabled="disabled" role="dialog" tabindex="-1" />
- @testing-library/jest-dom version: v5.7.0
- @testing-library/react version: v10.0.4
- node version: v10.18.0
- npm version: v6.13.4
Is there any update on this, or a workaround. I've posted a CodeSandbox with minimal code on StackOverflow reproducing the issue https://stackoverflow.com/questions/62427793/isvisible-doesnt-work-in-jest-test-using-jest-dom
Is there any update on this, or a workaround. I've posted a CodeSandbox with minimal code on StackOverflow reproducing the issue stackoverflow.com/questions/62427793/isvisible-doesnt-work-in-jest-test-using-jest-dom
That stackoverflow question is about opacity
not visibility
.
Is there any progress on this? I am also facing the same issue and put up a question with all relevant infos here: https://stackoverflow.com/questions/64026278/react-jest-rtl-check-if-element-is-not-visible
Btw. react testing library only reacts to styling changes when, I am using inline-styling in the react comp.
@MarcoLeko you may be experiencing an additional issue where styles from a stylesheet are not loaded automatically in jest/jsdom test env where you load and test the component in isolation. Check this comment and let me know if that helps. To confirm if the stylesheet is loaded, can you confirm that you can assert toHaveStyles
with other properties that should be there no matter what? For instance, based on the example in the SO question, does this assertion passes if you add it to your test right after rendering?
expect(screen.getByTestId('backdrop')).toHaveStyle({ position: 'absolute' })
It's working in styled-component v5.2.1, see this issue
Thanks for letting us know @tatinacher. I'll close this issue now.
@tatinacher @gnapse
Hey 👋, I think there's been a mistake. This issue has nothing to do with styled-components (only my example).
As you can see in the CodeSandbox, I've updated all the dependencies and it seems that the issue persists.
Oh ok, sorry. The signal here got lost between all the noise of styled components and the concern about stylesheets being loaded or not.
Indeed, it is a mistake that we treat parent lack of visibility as final, when in fact visibility, as opposed to display: none
, can be overridden by child elements.
Not sure i got same bug here or no
But seem due to this issue, Code sandbox
not working right now.
Below is my code :
import styled from "styled-components";
const Container = styled.a`
display: flex;
width: 50px;
height: 50px;
background-color: #0f0;
&:hover {
background-color: #f00;
}
`;
const HoverVisibleElement = styled.button`
padding: 0px;
margin: 0px;
outline: 0px;
border: 0px;
width: 10px;
height: 10px;
background-color: #00f;
visibility: hidden;
${Container}:hover & {
visibility: visible;
}
`;
export default function App() {
return (
<Container>
<HoverVisibleElement />
</Container>
);
}
In browser it working well by hover then button become visible and unhover it hidden.
But in test below:
import App from "./App";
import "@testing-library/jest-dom/extend-expect";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/userEvent";
it("Show button when hover link", async () => {
render(<App />);
expect(screen.queryByRole("button")).toBeNull();
userEvent.hover(screen.getByRole("link"));
await expect(screen.findByRole("button")).resolves.toBeVisible();
});
it would fail because button
not found
Here is a simple test to reproduce this bug
import styled from "@emotion/styled";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
const Container = styled("div")`
position: relative;
&:hover {
& .child {
visibility: visible;
}
}
`;
const Child = styled("div")`
visibility: hidden;
`;
function Test() {
return (
<Container data-testid="container">
<p>Test</p>
<Child className="child" data-testid="child">
Child
</Child>
</Container>
);
}
it("doesn't detect style changes applied from a parent to child", async () => {
render(<Test />);
const container = screen.getByTestId("container");
expect(container).toBeInTheDocument();
/*
these two lines passes the assertion
which confirms that styles are loaded
*/
expect(screen.getByTestId("child")).not.toBeVisible();
expect(screen.getByTestId("child")).toHaveStyle({
visibility: "hidden",
});
/*
when container is hovered, it applies the styles to the child
using CSS descendant selector.
*/
userEvent.hover(container);
/*
JSDOM not picking up styles applied to child through CSS descendant selector,
so the child is not visible
*/
expect(await screen.findByTestId("child")).toBeVisible(); // <--- only this line fails
});
I've got a PR ready that fixes this issue: https://github.com/testing-library/jest-dom/pull/428
BTW, @codewaseem, your example is not related to this issue. It does not involve the visibility
CSS property at all.
I believe your example is suffering from the issue that CSS from styled-components or emotion are not loaded in the DOM in the context of jest tests.
@gnapse , thanks for your response!
I have updated my example to use visibility
property and also I confirm that styles are being loaded from emotion, because these two lines passes
expect(screen.getByTestId("child")).not.toBeVisible();
expect(screen.getByTestId("child")).toHaveStyle({
visibility: "hidden",
});
which confirms styles are loaded.
Only the last line fails.
I think the issue is, styles that are applied to a child using css descendent selector are not being detected. This is regardless of specific property.
UPDATE: This is working in pure css setup in codesandbox. There might be some issue in my configuration or emotion.
If it's something that you can reproduce regardless of it being related to the visibility
CSS property, then open a new issue, please. If it works in codesandbox but not locally it could be a jsdom issue, though.
Sorry, I see this issue is still in open. Any update about this issue?
Here's an absolutely minimal example of how .toBeVisible()
is failing for me, with inline SVG:
document.body.innerHTML = `
<svg>
<g id="my-element" visibility="hidden" />
</svg>`;
// yep, this assertion fails.
expect(document.getElementById('my-element')).not.toBeVisible();
Also in Code Sandbox.
My best guess is that this is a bug in jsdom itself, because the computed style for visibility
of my-element
ends up being visible
here: https://github.com/testing-library/jest-dom/blob/main/src/to-be-visible.js#L6
Same problem here with opacity.
I am using tailwindcss
and vitest
and facing an issue where if I am hiding an element using className it does not work, and I have configured vitest
to parse CSS but it still does not work.
however, when I test the component in the browser it works fine as expected and it works only if I add visibility as inline style to the element I want to hide.
Button.tsx
:
const Button = forwardRef(({ isLoading, children }, ref) => {
return (
<button className={cn("[&.btn-is-loading>[data-slot=children]]:invisible", isLoading && "btn-is-loading")}>
{isLoading ? (
<span
data-slot="children"
children={children}
/>
) : (
children
)}
</button>
);
});
Button.test.tsx
:
it("should hide children content when loading", () => {
render(<Button isLoading>children</Button>);
const content = screen.getByText("children", { selector: '[data-slot="children"]' });
expect(content).not.toBeVisible();
});
does anyone have any idea what I might have done wrong?