diff --git a/packages/@react-spectrum/s2/src/Tabs.tsx b/packages/@react-spectrum/s2/src/Tabs.tsx index 2829a3389a1..1c5f92c16af 100644 --- a/packages/@react-spectrum/s2/src/Tabs.tsx +++ b/packages/@react-spectrum/s2/src/Tabs.tsx @@ -139,6 +139,7 @@ const InternalTabsContext = createContext< Partial & { tablistRef?: RefObject; selectedKey?: Key | null; + baseId?: string; } >({}); @@ -200,6 +201,8 @@ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef(null); + // Shared base id so child Tab/TabPanel components can derive stable ids. + let baseId = useId(); return ( { + let user; + beforeAll(() => { + jest.useFakeTimers(); + user = userEvent.setup({delay: null, pointerMap}); + Element.prototype.getAnimations = jest.fn().mockImplementation(() => []); + }); + + afterEach(() => { + jest.clearAllMocks(); + act(() => jest.runAllTimers()); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it('should not infinite-loop when inside DropZone with a controlled Tabs + Skeleton + Text', async () => { + function Repro() { + const [tab, setTab] = useState('a'); + return ( + + + setTab(k as string)}> + + A + + + + x + + + + + ); + } + + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const {getByTestId} = render(); + + await act(async () => { + await user.hover(getByTestId('dropzone')); + }); + + const updateDepthError = errorSpy.mock.calls.find(call => + call.some(arg => String(arg).includes('Maximum update depth')) + ); + expect(updateDepthError).toBeUndefined(); + errorSpy.mockRestore(); + }); +});