I am migrating some of my unit test cases which were previously written using Jest and Enzyme to React Testing Library. I am using Material UI's Select component and I know that, in order to open the dropdown, we have to trigger the mouseDown event on the corresponding div. Here is how I did it in Enzyme (working):
wrapper.find('[role="button"]').simulate('mousedown', { button: 0 });
I am trying to achieve the same using React Testing Library in the following manner, which is not working:
const { container, getAllByRole, getByRole } = renderComponent(mockProps);
fireEvent.mouseDown(getByRole('button')); // trigger the mouseDown on div having role=button
After this I am trying to access the listbox element which is ul element:
getByRole('listbox')
which throws an error and says:
TestingLibraryElementError: Unable to find an accessible element with the role "listbox"
There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole
I have verified and the ul element is visible (neither it nor its parent have display none or visibility hidden)
UPDATE - 1
I have tried all the following approach to wait for element to appear in DOM:
waitFor
findByRole instead of getByRole
in both the case it throws error that
Unable to find role="listbox"
What is wrong?
If the element is not accessible at the first render this error could appear, you can try passing the option hidden to the options key in the second argument of the getByRole, if this not works you can try using findByRole, that method wait for the element, and if you want to be sure of the visibility of the element you can try adding a waitFor inside of the test.
getByRole('listbox', { options: { hidden: true } });
Make sure animations are not compromising your results. If the animations change opacity or display and they did not finish before assertion from jest... it is likely to throw an error.
I found the root cause of this issue. I am using the Select component from MUI in disablePortal mode, which make the menu list to be rendered inside the parent component instead of document body. But while MUI does that, it doesn't remove the aria-hidden attribute from the parent component's div, and because of that testing library is not able to locate the listbox (ul) element inside.
There is an issue reported here:
https://github.com/mui/material-ui/issues/19450
So as a work around, I passed the data-testid to the menu component (MenuListProps) and using it to get the listbox.
Related
With React I'm inside of one repository, and the HTML elements are loading from another repo, which I watch for using pageLoaded. Inside updateHeader there is just more HTML element selecting and attribute/class manipulation.
useEffect(() => {
if (pageLoaded.status) {
if (someCondition) {
updateHeader(ubeName);
} else {
document.querySelector('.headerbar-menu').style.display = 'block';
if (document.querySelector('.headerbar-menu.affiliates-wrapper')) {
document.querySelector('.headerbar-menu.affiliates-wrapper').style.display = 'none';
}
}
}
}, [pageLoaded.status])
The problem here is obviously we shouldn't be using querySelector, and i think it may be causing some unexpected functionality. The elements dont properly evaluate and render until some piece of state changes i.e. a scroll state handler, so on initial page load the elements dont show with their new attributes until a scroll.
I'd like to attempt to resolve using useRef, but don't have direct access to the html. Is there a way to dynamically connect a ref to an element without access to the HTML code?
Thanks for your time and attention!
The best way to do this, in my opinion, is in this sequence:
Attempt to select the element
If non-existant, set up a DOMSubtreeModified event handler or a MutationObserver
Clean up DOMSubtreeModified event handler or a MutationObserver (or keep it around to watch for updates)
This allows for use of query selectors in a safe and modern way which doesn't stray too far from React's recommendations
The code below works as I am able to click a button on the webpage using Python/Selenium/Firefox.
button on the webpage
driver.execute_script('''return document.querySelector('dba-app').shadowRoot.getElementById('configRenderer').shadowRoot.querySelector('ing-default-layout-14579').querySelector('dba-overview').shadowRoot.querySelector('ing-feat-agreement-overview').shadowRoot.querySelector('ing-ow-overflow-menu-14587').shadowRoot.querySelector('button')''').click()
However, some elements are dynamic and the numbers are changing anytime you rerun the script.
The changing elements:
'ing-default-layout-14579'
'ing-ow-overflow-menu-14587'
What must I do to get around the dynamic elements?
One option is to look for other attributes that stay the same across pageloads. For example, given your HTML, you could do:
document.querySelector('#configRenderer') // returns the config renderer element
document.querySelector('[data-tag-name="ing-default-layout"]') // returns the ing-default-layout element
document.querySelector('[data-tag-name="dba-overview]') // returns the dba-overview element
And so on. Or you could the same method to identify a parent or a child, and then navigate to the child or parent respectively.
If the HTML isn't stable enough even for that, another approach would be to search through all elements, and find the one(s) whose tagName starts with what you need.
for (const elm of document.querySelectorAll('*')) {
if (elm.tagName.toLowerCase().startsWith('ing-ow-overflow-menu')) {
// do stuff with elm, which is the overflow menu element
}
}
I have a <span> element that is inside of a contentEditable <div>. In certain situations, when I try to delete everything in the <div> at once with Command-Delete, or when I try to delete individual characters from the <span> element when it is the only thing inside of the <div>, I get the error in the title.
How can I go about fixing this?
I put together a sandbox example of the issue:
https://codesandbox.io/s/nostalgic-wildflower-52eul?file=/src/App.js
It throws the error under two circumstances (the keyword in the example is test, it should get highlighted in blue when you type it):
Type any string with test inside of it or just test and highlight everything and delete.
Type any string with test inside of it, delete everything else except for test, then delete any character in test.
Thanks for any help!
You most probably read the following error when you set contentEditable of your div to true:
Warning: A component is `contentEditable` and contains `children` managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.
The problem here is that you let React add a child node to the content editable div and remove it through the actual DOM when interacting with it and removing all of its content.
React tries to re-render, and remove the span that should not be existent on next render however, you already removed it through your keyboard input.
You should only suppress the warning if you're absolutely sure about what you're doing, as it is warning you about the exact error you're getting.
The solution you're going for will not work as you would like it to.
You could be better off trying to implement an edit mode and highlighting the words after the user saves the content. This is only an idea as I don't know exactly what you're trying to achieve.
More info on this: https://stackoverflow.com/a/49639256/7381466
Just give the key to the parent element i.e div className='app ' according to your code or add a new parent element and just give the key to that element.
and if anything changes and you get this error, just update the key of the parent elementÂ
Because updating that parent element key causes react to detach the parent element from its dom, you can avoid this error because the problem is with the child element.
In onInput just check if the elemeent with id="addItemInput" is present or not and if it is not there just update the parent element key
I have multiple components that contains react-select. I want to iterate through the components and get the values that are in react-select menu.
I tought It should work if I do it like this
cy.get(".flight-segment-times .css-10nd86i")
.each(($select) => {
$select.click({ force: true });
cy.get(".css-11unzgr").contains("option")
})
But this doesn't work since the .css-11unzgr class appears only when it's parent element is clicked. However if I call .click() on $select element then dropdown menu won't even appear, if I call .click({multiple: true}) directly on cy.get(".flight-segment-times .css-10nd86i") then every dropdown will sequentially open, but I need to be able to do something between those click actions.
cy.each() (and other jquery-based commands) pass a jQuery collection to the callback, thus the .click() you are calling is actually a jQuery method, not a cypress command.
What you want to do is to cy.wrap() it first:
cy.get(".flight-segment-times .css-10nd86i")
.each(($select) => {
cy.wrap($select).click({ force: true });
cy.get(".css-11unzgr").contains("option");
});
just make it like two actions:
cy.get(".flight-segment-times .css-10nd86i")
.click({ force: true })
cy.get(".css-11unzgr")
.contains("option")
But since you have problems with actually opening the React-Select component you are probably clicking the wrong component. What I do is find the class that holds the 'onMouseDown' property (that class is in our application called 'react-select__control'. If I click that element it does open.
To find out which class has the 'onMouseDown' property I use the React Developer Tools: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
It doesn't seem to respond to #click but v-bind:click does register. However, I can't put a method in the directive since I can't access the mdl-menu or mdl-menu-item components to add the method.
I'm trying to do something like #click="setStatus('field-name', 'value')" with four menu options (each with a different value, same field-name).
I feel like there's either an element of VueJS I'm not yet aware of or it's going to be something fancy with Events.
I tried adding inline code bus.$emit('event', 'data'), but nothing happens (no errors, nothing in console, nothing)
Sample element:
<span class="bar" v-on:click="clicked">
<mdl-button icons raised colored
v-bind:id="generateId('av')"
v-bind:class="getButtonClass(row.avStatus)"
v-bind:title="row.avStatus"
>
<i class="material-icons">videocam</i>
</mdl-button>
<mdl-menu v-bind:for="generateId('av')">
<mdl-menu-item data-field="avStatus">Open</mdl-menu-item>
<mdl-menu-item data-field="avStatus">In Progress</mdl-menu-item>
<mdl-menu-item data-field="avStatus">Review</mdl-menu-item>
<mdl-menu-item data-field="avStatus">Ready</mdl-menu-item>
</mdl-menu>
</span>
Edit 1:
So I got the click event to work last night, but I'm not sure it's the correct method (feels very messy).
What I did was add the v-on:click="clicked" listener to the parent span, then within the clicked() method I have:
if (e.target.className == "mdl-menu__item") { /* code */ }
To only act when the menu options are clicked. This feels...wrong. Shouldn't I be able to add a listener directly to the menu option, or at least to the parent mdl-menu?
Use the .native modifier on the click. When using v-on on a component rather than a native html node, vue only look for the event among the child component's custom events declared with $emit, add .native will make vue treat it as a native event.