jest-dom icon indicating copy to clipboard operation
jest-dom copied to clipboard

`toBeVisible` not working as expected with `visibility` property

Open lhnrd opened this issue 4 years ago • 38 comments

  • @testing-library/jest-dom version: v5.1.1
  • @testing-library/react version: v9.4.0
  • node version: v12.14.1
  • npm (or yarn) 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.

lhnrd avatar Feb 18 '20 19:02 lhnrd

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 avatar Feb 18 '20 20:02 eps1lon

@eps1lon thanks for letting me know, just included a test example there.

lhnrd avatar Feb 18 '20 20:02 lhnrd

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.

eps1lon avatar Feb 18 '20 20:02 eps1lon

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.

lhnrd avatar Feb 19 '20 21:02 lhnrd

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 avatar Feb 20 '20 07:02 eps1lon

@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

lhnrd avatar Feb 20 '20 12:02 lhnrd

@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

eps1lon avatar Feb 20 '20 15:02 eps1lon

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?

lhnrd avatar Feb 20 '20 16:02 lhnrd

I prefer additional tests over changing existing ones. This way we make sure we don't regress.

eps1lon avatar Feb 20 '20 19:02 eps1lon

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.

lhnrd avatar Feb 20 '20 20:02 lhnrd

Write two tests then and comment the wrong one properly so that it is obvious that it will break once jsdom is fixed.

eps1lon avatar Feb 20 '20 21:02 eps1lon

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

Smolations avatar May 13 '20 20:05 Smolations

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

simonolubhaing avatar Jun 17 '20 11:06 simonolubhaing

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.

eps1lon avatar Jun 17 '20 15:06 eps1lon

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 avatar Sep 23 '20 11:09 MarcoLeko

@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' })

gnapse avatar Sep 23 '20 12:09 gnapse

It's working in styled-component v5.2.1, see this issue

tatinacher avatar Jan 19 '21 09:01 tatinacher

Thanks for letting us know @tatinacher. I'll close this issue now.

gnapse avatar Jan 19 '21 12:01 gnapse

@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.

lhnrd avatar Jan 19 '21 14:01 lhnrd

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.

gnapse avatar Jan 19 '21 15:01 gnapse

Not sure i got same bug here or no

CodeSandbox here

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

davidnghk01 avatar May 03 '21 04:05 davidnghk01

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
});

codewaseem avatar Jan 13 '22 15:01 codewaseem

I've got a PR ready that fixes this issue: https://github.com/testing-library/jest-dom/pull/428

gnapse avatar Jan 13 '22 22:01 gnapse

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 avatar Jan 13 '22 22:01 gnapse

@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.

codewaseem avatar Jan 14 '22 02:01 codewaseem

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.

gnapse avatar Jan 14 '22 12:01 gnapse

Sorry, I see this issue is still in open. Any update about this issue?

M162 avatar May 11 '22 03:05 M162

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

jrnail23 avatar Jun 08 '22 05:06 jrnail23

Same problem here with opacity.

Hai-San avatar Nov 14 '23 13:11 Hai-San

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?

yaman3bd avatar Mar 21 '24 12:03 yaman3bd