diff --git a/src/TabNavList/index.tsx b/src/TabNavList/index.tsx index 25a7e05d..9599da3a 100644 --- a/src/TabNavList/index.tsx +++ b/src/TabNavList/index.tsx @@ -565,6 +565,8 @@ const TabNavList = React.forwardRef((props, ref // ========================= Render ======================== const hasDropdown = !!hiddenTabs.length; + const hasTabList = Boolean(id); + const tabIds = hasTabList ? tabs.map(tab => `${id}-tab-${tab.key}`).join(' ') : undefined; const wrapPrefix = `${prefixCls}-nav-wrap`; let pingLeft: boolean; let pingRight: boolean; @@ -588,8 +590,10 @@ const TabNavList = React.forwardRef((props, ref
{ @@ -597,6 +601,14 @@ const TabNavList = React.forwardRef((props, ref doLockAnimation(); }} > + {hasTabList && ( +
+ )} + diff --git a/tests/__snapshots__/index.test.tsx.snap b/tests/__snapshots__/index.test.tsx.snap index 43e22036..00dbcb76 100644 --- a/tests/__snapshots__/index.test.tsx.snap +++ b/tests/__snapshots__/index.test.tsx.snap @@ -5,10 +5,13 @@ exports[`Tabs.Basic Normal 1`] = ` class="rc-tabs rc-tabs-top" >
+
@@ -108,10 +111,13 @@ exports[`Tabs.Basic Skip invalidate children 1`] = ` class="rc-tabs rc-tabs-top" >
+
diff --git a/tests/accessibility.test.tsx b/tests/accessibility.test.tsx index 385d889c..548ff452 100644 --- a/tests/accessibility.test.tsx +++ b/tests/accessibility.test.tsx @@ -1,6 +1,7 @@ import { render, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { renderToString } from 'react-dom/server'; import type { TabsProps } from '../src'; import Tabs from '../src'; @@ -35,6 +36,56 @@ describe('Tabs.Accessibility', () => { /> ); + it('should keep tablist semantics during server rendering', () => { + const html = renderToString(createTabs()); + + expect(html).toContain('role="tablist"'); + expect(html).toContain('aria-orientation="horizontal"'); + }); + + it('should keep auxiliary controls outside the semantic tablist', () => { + const { getByRole, getAllByRole } = render( + createTabs({ + tabBarExtraContent: , + editable: { + onEdit: jest.fn(), + }, + }), + ); + + const tablist = getByRole('tablist'); + const tabs = getAllByRole('tab'); + + expect(tablist).toBeEmptyDOMElement(); + expect(tablist).toHaveAttribute('aria-owns', tabs.map(tab => tab.id).join(' ')); + expect(tablist).not.toContainElement(getByRole('button', { name: 'Extra action' })); + getAllByRole('button', { name: 'Add tab' }).forEach(button => { + expect(tablist).not.toContainElement(button); + }); + getAllByRole('button', { name: 'remove' }).forEach(button => { + expect(tablist).not.toContainElement(button); + }); + }); + + it('should keep auxiliary controls outside an empty semantic tablist', () => { + const { getByRole, getAllByRole } = render( + Extra action} + editable={{ onEdit: jest.fn() }} + />, + ); + + const tablist = getByRole('tablist'); + + expect(tablist).toBeEmptyDOMElement(); + expect(tablist).not.toHaveAttribute('aria-owns'); + expect(tablist).not.toContainElement(getByRole('button', { name: 'Extra action' })); + getAllByRole('button', { name: 'Add tab' }).forEach(button => { + expect(tablist).not.toContainElement(button); + }); + }); + it('should support keyboard navigation', async () => { const user = userEvent.setup(); const { getByRole } = render(createTabs());