New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experimental test selector API #18607
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 6f2096e:
|
Details of bundled changes.Comparing: 7992ca1...6f2096e react-native-renderer
react-dom
react-art
react-test-renderer
react-reconciler
ReactDOM: size: 0.0%, gzip: 0.0% Size changes (stable) |
Details of bundled changes.Comparing: 7992ca1...6f2096e react-dom
react-art
react-test-renderer
react-reconciler
react-native-renderer
ReactDOM: size: 0.0%, gzip: -0.0% Size changes (experimental) |
da9b613
to
d521316
Compare
Okay, ready for a first pass of review and discussion I think. |
|
||
if (element.tabIndex === null || element.tabIndex < 0) { | ||
// The HTML spec says that negative tab index values indicate an element should be, | ||
// "click focusable but not sequentially focusable". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the function name reflect that? Negative tab indices are also used to programmatically move focus.
272b206
to
f116626
Compare
Pinging potential reviewers 😄 |
Ping @sebmarkbage / @gaearon / @acdlite again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a lot to review in this PR but overall I'm happy with things. We can always revise specific mechanics after we have some internal usage and get to use it.
Thanks a ton for the review and feedback, Dominic! |
Selectors can now be: React component, accessibility role, display text, or data-testname.
Instead, we'll just call .focus() and let the browser decide if it's focusable.
Adds several new experimental APIs to aid with automated testing.
This is not an RFC, although the PR includes some basic documentation in a similar format.
Types of selectors
Each of the methods below accepts an array of "selectors" that identifies a path (or paths) through a React tree. There are four basic selector types:
Fibers
with the specified React component typedata-testname
attribute.There is also a special lookahead selector type that enables further matching within a path (without actually including the path in the result). This selector type was inspired by the
:has()
CSS pseudo-class. It enables e.g. matching a<section>
that contained a specific header text, then finding a like button within that<section>
.API
findAllNodes()
Finds all Host Instances (e.g.
HTMLElement
) within a host subtree that match the specified selector criteria.How does it work?
document.body
) in which case, React will traverse the native tree until it finds the first React container, or...data-testname
attribute (e.g. a match from an earlierfindAllNodes
query).Fiber
tree ¹ looking for all paths matching the specified selector.¹ Traversing the
Fiber
tree (instead of the host tree) provides several benefits:What does it return?
HTMLElement
s) that match the specified criteria. (This array will be empty if no matches are found.)getFindAllNodesFailureDescription()
Returns an error string describing the matched and unmatched portions of the selector query.
How does it work?
What does it return?
findBoundingRects()
For all React components within a host subtree that match the specified selector criteria, return a set of bounding boxes that covers the bounds of the nearest (shallowed) Host Instances within those trees.
How does it work?
findAllNodes
.getBoundingClientRect
Host Config method. Add the result to a list of matched boxes.What does it return?
{ x: number, y: number, width: number, height: number }
What can this API be used for?
observeVisibleRects()
For all React components within a host subtree that match the specified selector criteria, observe if it’s bounding rect is visible in the viewport and is not occluded.
How does it work?
IntersectionObserver
or equivalentHostConfig
specific API, passing a callback and options argument to it.{ ratio, rect }
, otherwise this callback works the same as the DOM version.findAllNodes
.observe(intersectionObserver, instance)
Host Config method to add it to the list to be observed.Observer
and start over from the top to attach a new one. This makes this observation live updating. This prevents accidentally relying on implementation details such as if a DOM node is reused or remounted.What does it return?
disconnect()
method. When this method is called, we disconnect theIntersectionObserver
and disconnect the callback in the React reconciler.What can this API be used for?
Why is this API necessary?
findBoundingRects
would be enough for this but these APIs don’t give access to where in the DOM this component is, nor really all the other things that might obscuring the bounding rect. This lets us solve this use case without exposing more implementation details and we can build in details such as if it’s obscured by something virtual (e.g. if an ART component is covered by another ART component within a canvas).focusWithin()
For all React components within a host subtree that match the specified selector criteria, set focus within the first focusable Host Instance (as if you started before this component in the tree and moved focus forwards one step).
The internal structure of a node is an implementation detail. However, you can start from the outside of a component and move forward (e.g. hit tab) to focus within a component. This has behavior that is defined, and so it can be thought of as part of the public API fo the component.
With the Focus Selectors API used for a11y we might be able to even expose more specific capabilities for the outside of a component move focus into a child.
How does it work?
findAllNodes
.What does it return?
What can this API be used for?
Usage examples
Coming soon. (For now, refer to unit tests.)