-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
TabPanel
: implement Ariakit internally
#52133
Conversation
025db24
to
f5a8313
Compare
@radix-ui/react-tabs
-powered version of TabPanel
Ariakit
-powered version of TabPanel
Size Change: +2.01 kB (0%) Total Size: 1.44 MB
ℹ️ View Unchanged
|
👋 @diegohaz while I was working on this with @ciampo we actually found we had a question for you! A number of our unit tests began failing when we implemented Ariakit, and it turned out that the elements or text the test checked for didn't render immediately. The tests began passing when we updated them to run async with We suspect this is due to re-renders caused by the different |
Flaky tests detected in d938bd3. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/5672378046
|
If you don't provide In theory, you could do something like this right after await act(async () => {
await new Promise(requestAnimationFrame);
}); This would flush the pending animation frame callbacks triggered after render and let you use But this is an implementation detail. There may be cases where In Ariakit, we use an internal testing package that automatically flushes those timers in JSDOM in a similar way that browsers would do, so our JSDOM tests don't include implementation details. I'd say |
Excellent, thanks so much for all of that insight @diegohaz! |
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.
This is looking very nice!
The one difference I noticed with keyboard navigation is that the tab content (tabpanel
) now gets a tab stop, which I believe is the standard and thus desirable behavior.
A bit more open to debate is what happens when there's a focusable element (like a button) in the tab content. The ARIA guidance is somewhat unclear, and there's variance in the main library implementations. Ariakit and Radix still retain the tab stop on tabpanel
, while React Aria will go straight to the focusable element. I think this is fine to ship as an enhancement and not a breaking change. (cc @alexstine @andrewhayward)
Good catch! Ariakit should only focus on the tab panel element if there are no tabbable elements inside it. But it's currently not able to identify tabbable elements inside the tab panel after the first render, so it only works for the tab panel that's initially visible (using the If that's a requirement now, passing |
Thanks for all of the time and advice on this one @mirka. Final round of changes addressed, just waiting on CI 🚀 |
Seems that this PR caused that some unit tests started failing intermittently, because a
It's a test that uses React Testing Library and JSDOM. @diegohaz is it possible that the |
Yes, |
Is it the And how is it scheduled relative to the initial render? Is there
I see, there are utilities that do The preferred way is to use |
I'm not sure what utilities you're talking about, but the functions we use are like This is the opposite of implementation knowledge. Unless you're talking about browser implementation knowledge, which we had to study a lot to build those functions, but that's entirely abstracted into the testing library.
Since But I understand Playwright may be slower and less convenient than JSDOM. So that's an okay solution, I guess. |
Yes, I found the relevant code in the
Yes, I was talking about the utility functions that the helpers like If it was only about mimicking browser behavior, that would be fine. But in that case Ariakit wouldn't need its own helpers, the helpers in But unfortunately there is more. A call like: await act( () => new Promise( r => requestAnimationFrame( r ) ) ); isn't there just to mimick browser behavior. If it was, it wouldn't need the React function TabButton() {
const [ tabIndex, setTabIndex ] = useState( undefined );
useEffect( () => {
requestAnimationFrame( () => {
setTabIndex( -1 );
} );
}, [] );
return <button tabIndex={ tabIndex } />;
} If you didn't call that So, when user is testing their React component that uses some library that somewhere deep down does the We've seen many instances of that when migrating Gutenberg to React 18 and newer versions of Testing Library. The solution is to:
|
The When I last experimented with
I respectfully disagree with your last statement. In almost all cases, a Take your example, if a library invokes I realize that the current state of unit testing doesn't provide many solutions to this issue. But that is precisely what |
The tests shouldn't fail, but, if they wait for specific fine-grained events inside the event loop, they will fail. It's very likely that if Ariakit's internal implementation changed, the
To really replicate accurate browser behavior in a React application, the tests would simply need to stop using Recent versions of React Testing Library completely disable the |
I wasn't aware you could completely disable the |
It's a matter of setting the I think that React Testing Library wants to maintain full control over the Once you disable the |
Thanks! I tried setting this global value to |
[ instanceId ] | ||
); | ||
|
||
const tabStore = Ariakit.useTabStore( { |
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.
Just having a look at this PR — is there a reason why we imported * from Ariakit
instead of importing only the needed components / functions?
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.
No, actually. Importing just the needed items would have been better. I'll plan a followup.
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.
Just in case this is a factor, besides code style/aesthetics, there's no difference between importing the namespace and components individually. In the docs, we sometimes use the namespace import. The reasons are described in this section of the docs: https://ariakit.org/guide/coding-guidelines#import-namespace
This PR removes the need for calling React's `act` when performing asynchronous tasks using `@ariakit/test`. More details on reactwg/react-18#102 and WordPress/gutenberg#52133 (comment) Some breaking changes have been introduced to the `@ariakit/test` package. Refer to the changeset files for more details.
See #48440 for the combined dev note |
Related/alternate to: #51551
This PR is a collaboration between myself and @ciampo, and also builds off of some previous work by @flootr.
What?
Introduce a Ariakit-powered version of
TabPanel
, namedTabs
, maintaining the same functionality and API surface as the existing component. Eventually this will be used to ensure compatibility for existingTabPanel
implementations as we move fully to a completely new component.Initially, Radix-powered
TabPanel
alternative was explored, but as we continue to evaluate which library(ies) will be the best fit, we're also looking at things like this Ariakit implementation.Why?
As we experiment with third-party libraries for certain UI patterns,
TabPanel
is a good candidate to explore, as there are time where additional functionality could be achieved with a more composable version of the component, which will come in future PRs.How?
Tabs
is a wrapper around a composedTabPanel
utilizing the variousAriakit
subcomponents.Tabs
is written to accept all the same props asTabPanel
, passing them to the right props for the various Ariakit subcomponents internally.Notes
We've had to maintain the existing tab selection logic, both for initial tab selection and the handling of disabled tabs. While
Ariakits
's built indefaultSelectedId
works well, there were a few elements of our existing component that we needed to to preserve our own custom logic to maintain.Note: The new component does not yet have a README, as this isn't form that
Tabs
will take long-term. I'll make sure one is added in a future PR when the new component is nearer to completion.Testing Instructions
Tabs
component.TabPanel
counterpart.