testing-library / user-event

🐕 Simulate user events
https://testing-library.com/user-event
MIT License
2.19k stars 249 forks source link

Support open shadow DOM components #1026

Open Christian24 opened 2 years ago

Christian24 commented 2 years ago

Problem description

Hello,

this is a follow up to #1025. Currently the library doesn't support web components which use shadow dom. This feature request only deals with open shadow doms. The underlying issue seems to be that with web components the element that is focused can be different from the one where input needs to take place: Web Components wrap builtin elements on which the input takes place. To get the element to interact with document.activeElement seems to be used. In case of web components this would return the web component, while the element that would need to be interacted with (e. g. an input element within the shadow DOM) is hidden from the library.

Suggested solution

There has been prior art in the form of getActiveElement (https://github.com/testing-library/user-event/blob/main/src/utils/focus/getActiveElement.ts) which iterates over potential shadow DOM's activeElement property, until the element to interact with is reached.

This could be used as a replacement for document.activeElement to make the library open shadow DOM compatible. I quickly hacked this together on paste() and it seems to get the job done: https://github.com/Christian24/user-event/blob/f4242842c1ed51d2ec8042263bee7abdbafea21e/src/clipboard/paste.ts#L14

In some cases like keyboard input it seems to be more an issue of getting the element to focus properly first.

Interestingly, user.click(element) doesn't seem to focus the input within the web component, not sure why. I had to manually querySelector the input to get my paste test to work: https://github.com/Christian24/user-event/blob/main/tests/webcomponents/index.ts#L29

jsdom doesn't implement delegatesFocus currently: https://github.com/jsdom/jsdom/issues/3418

Additional context

I have been playing a little bit with this this afternoon: https://github.com/Christian24/user-event Not sure it will evolve into a PR though, would like to hear seasoned contributors opinion first. Also for some reason I couldn't get the tests to debug in VS Code...

ph-fritsche commented 2 years ago

Could you add information how browsers handle selections, e.g. given a collapsed selection was placed outside the Shadow tree per mousedown and then the mouse is dragged into the Shadow tree and released there, does a Selection span across the Shadow boundary or does the Range end at the boundary or(/and) does something else happen?

Christian24 commented 2 years ago

I honestly don't know how selection works. I created a small reproduction here: https://lit.dev/playground/#project=W3sibmFtZSI6InNpbXBsZS1ncmVldGluZy50cyIsImNvbnRlbnQiOiJpbXBvcnQge2h0bWwsIGNzcywgTGl0RWxlbWVudH0gZnJvbSAnbGl0JztcbmltcG9ydCB7Y3VzdG9tRWxlbWVudCwgcHJvcGVydHl9IGZyb20gJ2xpdC9kZWNvcmF0b3JzLmpzJztcblxuZG9jdW1lbnQub25zZWxlY3Rpb25jaGFuZ2UgPSAoKSA9PiB7XG4gIGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyNzZWxlY3Rpb24nKS5pbm5lckhUTUwgPSBkb2N1bWVudC5nZXRTZWxlY3Rpb24oKS50b1N0cmluZygpOyBcbn1cblxuQGN1c3RvbUVsZW1lbnQoJ3NpbXBsZS1ncmVldGluZycpXG5leHBvcnQgY2xhc3MgU2ltcGxlR3JlZXRpbmcgZXh0ZW5kcyBMaXRFbGVtZW50IHtcbiAgc3RhdGljIHN0eWxlcyA9IGNzc2BwIHsgY29sb3I6IGJsdWUgfWA7XG5cbiAgQHByb3BlcnR5KClcbiAgbmFtZSA9ICdTb21lYm9keSc7XG5cbiAgcmVuZGVyKCkge1xuICAgIHJldHVybiBodG1sYDxwPkhlbGxvLCAke3RoaXMubmFtZX0hPC9wPmA7XG4gIH1cbn1cbiJ9LHsibmFtZSI6ImluZGV4Lmh0bWwiLCJjb250ZW50IjoiPCFET0NUWVBFIGh0bWw-XG48aGVhZD5cbiAgPHNjcmlwdCB0eXBlPVwibW9kdWxlXCIgc3JjPVwiLi9zaW1wbGUtZ3JlZXRpbmcuanNcIj48L3NjcmlwdD5cbjwvaGVhZD5cbjxib2R5PlxuICA8cD5PdXRzaWRlPC9wPlxuICA8c2ltcGxlLWdyZWV0aW5nIG5hbWU9XCJXb3JsZFwiPjwvc2ltcGxlLWdyZWV0aW5nPlxuICA8cD5PdXRzaWRlIDI8L3A-XG4gIDxwIGlkPVwic2VsZWN0aW9uXCI-PC9wPlxuPC9ib2R5PlxuIn0seyJuYW1lIjoicGFja2FnZS5qc29uIiwiY29udGVudCI6IntcbiAgXCJkZXBlbmRlbmNpZXNcIjoge1xuICAgIFwibGl0XCI6IFwiXjIuMC4wXCIsXG4gICAgXCJAbGl0L3JlYWN0aXZlLWVsZW1lbnRcIjogXCJeMS4wLjBcIixcbiAgICBcImxpdC1lbGVtZW50XCI6IFwiXjMuMC4wXCIsXG4gICAgXCJsaXQtaHRtbFwiOiBcIl4yLjAuMFwiXG4gIH1cbn0iLCJoaWRkZW4iOnRydWV9XQ

ph-fritsche commented 2 years ago

It looks like this will require some research. If you press down the left mouse button inside the shadow tree and drag the mouse outside, the selection spans over the shadow boundary. The document.getSelection() indicates a collapsed Range on the shadow host, the shadowRoot.getSelection() indicates a Range that spans up to the shadow boundary, and the copied text per Ctrl+C is the whole selected text spanning over the shadow boundary as indicated per selection highlight in the UI.

For a correct implementation we need to know how the UI reacts to different interactions and which parts of the UI behavior are directly reflected by programmatically available properties/methods on the DOM.

Christian24 commented 2 years ago

While a correct implementation should be the ultimate goal, for now I would be very happy with a working simple implementation. I do not know, but I think a selection over boundaries is not super common.

ph-fritsche commented 2 years ago

a working simple implementation

How does the browser handle selections on shadow DOM? If there is an edge case that we don't handle yet, that's okay, but we need to understand how this works in general. The edge case mentioned above indicates that we don't know enough to judge what constitutes a working implementation. We might need an abstraction to handle Selection independently from Document/ShadowRoot, we might not. Let's find a few edge cases that we can use to challenge our concepts with.

Christian24 commented 2 years ago

You are right. Well, I focused on the use case most common for us: inputs in an open shadow root. I wrote some basic tests. This should basically be enough for our use case at work as we are trying to test single components. Please if you find the time, let me know what you think: https://github.com/testing-library/user-event/compare/main...Christian24:user-event:main

I didn't know what exactly you are aiming for, but if you like it, I will open a PR and it would probably allow us to adopt user-event in our current component tests.