jest-dom
jest-dom copied to clipboard
toBeVisible doesn't work with antd's `Popconfirm`
@testing-library/jest-domversion: "4.2.4"nodeversion: 12.16.1npm(oryarn) version: yarn 1.22.4
dom-testing-libraryversion: (if applicable)react-testing-libraryversion: "9.5.0"antdversion: "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:
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.
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.
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?
> el = document.querySelector('.ant-popover-message-title')
<div class="ant-popover-message-title">title</div>
> getComputedStyle(el).display
"block"
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?
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
getComputedStyle(el).display "block"el = document.querySelector('.ant-popover-message-title')
Can you check each parent? Ideally share a codesandbox or another form of reproduction. Otherwise we can only guess.
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?
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.
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)
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