diff --git a/package.json b/package.json index 27224dd7002..1506322cc51 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "start": "yarn build && concurrently --kill-others \"yarn watch\" \"yarn workspace @patternfly/react-docs develop\"", "start:cypress": "lerna run cypress:open", "start:demo-app": "lerna run dev --scope=demo-app-ts --stream", - "test": "TZ=EST LC_ALL=en_US.UTF8 jest packages", + "test": "TZ=Etc/GMT+5 LC_ALL=en_US.UTF8 jest packages", "test:a11y": "lerna run test:a11y --stream", "test:integration": "yarn workspace @patternfly/react-integration test:integration", "uninstall": "find . -name node_modules -type d | xargs rm -rf", diff --git a/packages/react-core/src/components/Tabs/Tabs.tsx b/packages/react-core/src/components/Tabs/Tabs.tsx index cd34bd7228a..4315ed79f39 100644 --- a/packages/react-core/src/components/Tabs/Tabs.tsx +++ b/packages/react-core/src/components/Tabs/Tabs.tsx @@ -18,6 +18,7 @@ import { TabsContextProvider } from './TabsContext'; import { OverflowTab, HorizontalOverflowPopperProps } from './OverflowTab'; import { Button } from '../Button'; import { getOUIAProps, OUIAProps, canUseDOM } from '../../helpers'; +import { getResizeObserver } from '../../helpers/resizeObserver'; import { SSRSafeIds } from '../../helpers/SSRSafeIds/SSRSafeIds'; import { GenerateId } from '../../helpers/GenerateId/GenerateId'; import linkAccentLength from '@patternfly/react-tokens/dist/esm/c_tabs_link_accent_length'; @@ -217,6 +218,7 @@ class Tabs extends Component { } scrollTimeout: NodeJS.Timeout = null; + observer: any = () => {}; static defaultProps: PickOptional = { activeKey: 0, @@ -411,15 +413,16 @@ class Tabs extends Component { }; handleResize = () => { + if (!this.tabList.current) { + return; + } this.handleScrollButtons(); this.setAccentStyles(); }; componentDidMount() { if (!this.props.isVertical) { - if (canUseDOM) { - window.addEventListener('resize', this.handleResize, false); - } + this.observer = getResizeObserver(this.tabList.current, this.handleResize, true); this.direction = getLanguageDirection(this.tabList.current); // call the handle resize function to check if scroll buttons should be shown this.handleScrollButtons(); @@ -429,11 +432,7 @@ class Tabs extends Component { } componentWillUnmount() { - if (!this.props.isVertical) { - if (canUseDOM) { - window.removeEventListener('resize', this.handleResize, false); - } - } + this.observer(); clearTimeout(this.scrollTimeout); this.leftScrollButtonRef.current?.removeEventListener('transitionend', this.hideScrollButtons); } diff --git a/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx b/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx index 3aa1d847bd4..b020b603cdb 100644 --- a/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx +++ b/packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx @@ -1006,3 +1006,31 @@ test('should not render tablist aria-label or aria-labelledby when neither is pr expect(asFragment()).toMatchSnapshot(); }); + +test('observes the tab list for resize and stops observing on unmount', () => { + const observe = jest.fn(); + const unobserve = jest.fn(); + let onResize: (entries: any[]) => void; + const originalResizeObserver = (window as any).ResizeObserver; + (window as any).ResizeObserver = jest.fn((callback) => { + onResize = callback; + return { observe, unobserve }; + }); + + const { unmount } = render( + + Tab 1} /> + + ); + const tabList = screen.getByRole('tablist'); + expect(observe).toHaveBeenCalledWith(tabList); + + // a resize queued just before unmount must not run against the removed tab list + const rafSpy = jest.spyOn(window, 'requestAnimationFrame').mockImplementation(() => 0); + onResize([{}]); + unmount(); + expect(unobserve).toHaveBeenCalledWith(tabList); + expect(() => rafSpy.mock.calls[0][0](0)).not.toThrow(); + rafSpy.mockRestore(); + (window as any).ResizeObserver = originalResizeObserver; +});