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

toBeVisible doesn't work with antd's `Popconfirm`

Open allevo opened this issue 5 years ago • 10 comments

  • @testing-library/jest-dom version: "4.2.4"
  • node version: 12.16.1
  • npm (or yarn) version: yarn 1.22.4
  • dom-testing-library version: (if applicable)
  • react-testing-library version: "9.5.0"
  • antd version: "4.7.0"

Relevant code or config:

import {Popconfirm} from 'antd'
import React from 'react'

import {render, fireEvent, within, prettyDOM} from '@testing-library/react'

describe('Popconfirm', () => {
  test('after ok, should not be visible', async () => {
    const {getByText, getByRole} = render(
      <Popconfirm
        cancelText="No"
        onCancel={() => console.log('nnn')}
        onConfirm={() => console.log('aa')}
        okText="Yes"
        title="title"
      >
        <a href="#">Delete</a>
      </Popconfirm>
    )

    fireEvent.click(getByText('Delete'))

    expect(getByText('title')).toBeInTheDocument()

    const tooltip = within(getByRole('tooltip'))
    fireEvent.click(tooltip.getByText('Yes'))

    console.log(prettyDOM(tooltip.container))

    expect(getByText('title')).not.toBeVisible() // this fails
  })
})

What you did:

Instance Popconfirm as documentation says here. I'm testing the basic example. Under the hood antd doesn't detach the popconfirm and hidden it using display: none. For that antd assigns to an ancestor the class ant-popover-hidden.

What happened:

The code fails because


    expect(element).not.toBeVisible()

    Received element is visible:
      <div class="ant-popover-message-title" />

      27 |     console.log(prettyDOM(tooltip.container))
      28 | 
    > 29 |     expect(getByText('title')).not.toBeVisible()
         |                                    ^
      30 |   })
      31 | })
      32 | 

the printed DOM is:

    <body>
      <div>
        <a
          href="#"
        >
          Delete
        </a>
      </div>
      <div
        style="position: absolute; top: 0px; left: 0px; width: 100%;"
      >
        <div>
          <div
            class="ant-popover ant-popconfirm ant-popover-hidden"
            style="pointer-events: none;"
          >
            <div
              class="ant-popover-content"
            >
              <div
                class="ant-popover-arrow"
              >
                <span
                  class="ant-popover-arrow-content"
                />
              </div>
              <div
                class="ant-popover-inner"
                role="tooltip"
              >
                <div
                  class="ant-popover-inner-content"
                >
                  <div
                    class="ant-popover-message"
                  >
                    <span
                      aria-label="exclamation-circle"
                      class="anticon anticon-exclamation-circle"
                      role="img"
                    >
                      <svg
                        aria-hidden="true"
                        class=""
                        data-icon="exclamation-circle"
                        fill="currentColor"
                        focusable="false"
                        height="1em"
                        viewBox="64 64 896 896"
                        width="1em"
                      >
                        <path
                          d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
                        />
                      </svg>
                    </span>
                    <div
                      class="ant-popover-message-title"
                    >
                      title
                    </div>
                  </div>
                  <div
                    class="ant-popover-buttons"
                  >
                    <button
                      class="ant-btn ant-btn-sm"
                      type="button"
                    >
                      <span>
                        No
                      </span>
                    </button>
                    <button
                      ant-click-animating-without-extra-node="false"
                      class="ant-btn ant-btn-primary ant-btn-sm"
                      type="button"
                    >
                      <span>
                        Yes
                      </span>
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </body>

Reproduction:

Problem description:

Actually the Popconfirm text is not visibile

Suggested solution:

allevo avatar Oct 16 '20 23:10 allevo

Hi thanks for taking the time to report this.

Under the hood antd doesn't detach the popconfirm and hidden it using display: none.

I don’t see that that DOM shows anything with display: none.

gnapse avatar Oct 17 '20 00:10 gnapse

I don’t see that that DOM shows anything with display: none.

The class ant-popover-hidden has display: none. That class is applied to an ancestor.

allevo avatar Oct 17 '20 06:10 allevo

It's very likely that this is an issue with JSDOM which doesn't implement the Cascade in CSS. Could you run the same test in an actual browser and share the result?

eps1lon avatar Oct 17 '20 10:10 eps1lon

> el = document.querySelector('.ant-popover-message-title')
<div class=​"ant-popover-message-title">title</div>​
> getComputedStyle(el).display
"block"

allevo avatar Oct 17 '20 10:10 allevo

Anyway I'm bit confused. From Readme, if its parent element is also visible (and so on up to the top of the DOM tree) then the element is visible. So I'm thinking that the CSS cascade is implemented in this case.

Maybe we can add a parameter in order to provide this feature?

allevo avatar Oct 18 '20 09:10 allevo

Anyway I'm bit confused. From Readme, if its parent element is also visible (and so on up to the top of the DOM tree) then the element is visible. So I'm thinking that the CSS cascade is implemented in this case.

Cascade and inheritance are two different things

el = document.querySelector('.ant-popover-message-title')

title
​ getComputedStyle(el).display "block"

Can you check each parent? Ideally share a codesandbox or another form of reproduction. Otherwise we can only guess.

eps1lon avatar Oct 18 '20 14:10 eps1lon

The class ant-popover-hidden has display: none. That class is applied to an ancestor.

Are you certain the CSS is loaded and attached to the document in the context of the test running in jsdom? This does not happen automatically. When you mount a component in isolation, none of the logic that loads css in the production bundle kick in when in the contexts of tests.

What we ourselves need to do in the very tests of toHaveStyle is to manually create a style element with the wanted CSS inside and attach it to the DOM in a rather manual way.

If this the case, we really need to put a warning about this in the README around the fea matchers that rely on CSS (.toHaveStyle, .toBeVisible).

In the mean time, maybe it's enough that you check for .toHaveClass('ant-popover-hidden') instead?

gnapse avatar Oct 19 '20 12:10 gnapse

Hi! thanks for the response. How can I add custom CSS to my test? I've added this function without success:

function loadCSS (url) {
  var head = document.getElementsByTagName('head')[0]
  var cssnode = document.createElement('link')

  cssnode.type = 'text/css'
  cssnode.rel = 'stylesheet'
  cssnode.href = url

  head.appendChild(cssnode)
}

// in test
loadCSS("https://cdnjs.cloudflare.com/ajax/libs/antd/4.7.2/antd.compact.min.css")

In the mean time, maybe it's enough that you check for .toHaveClass('ant-popover-hidden') instead?

function someParentHasClass(element, cls) {
  if (new RegExp(cls).test(element.className)) {
    return true
  }

  if (!element.parentElement) {
    return false
  }
  return someParentHasClass(element.parentElement, cls)
}

// In test
const el = getByText('title')
console.log(someParentHasClass(el, 'ant-popover-hidden')) // logs true!

this works.

Anyway I don't like basing the test on external class.

allevo avatar Oct 19 '20 18:10 allevo

Anyway I don't like basing the test on external class.

I get your point, but OTOH, you'd base your tests on needed to load a css resource over the wire?

// in test
loadCSS("https://cdnjs.cloudflare.com/ajax/libs/antd/4.7.2/antd.compact.min.css")

I wouldn't do this. I'd rather rely on the class. Also, how does this ensures that then in the real life use of your app this is the very css file that's loaded?

Anyway, I don't have an easy answer for you. Having CSS loaded in isolated tests does not work as when your entire app loads in the browser in real life usage 🤷‍♂️ (ideas are welcome on how to improve this lib experience around that problem)

gnapse avatar Oct 19 '20 20:10 gnapse

I'm also using antd and my only solution was to look at the parent nodes and check if it had the class that makes it invisible - in my case d-none. If you are writing tests using snapshot, just make sure you see the right class in the snapshot and take care whenever merging a snapshot update

micaelomota avatar Jun 05 '22 02:06 micaelomota