From 5f4c03790bb3470005cf5bcb4c76d7016c6cb51a Mon Sep 17 00:00:00 2001 From: Mohamed Fall Date: Sat, 22 Nov 2025 00:34:39 +0000 Subject: [PATCH 1/5] fix(Drawer): update refs in examples Update ref types to HTMLSpanElement and initialize with null to fix TS errors Signed-off-by: Mohamed Fall --- .../Drawer/examples/DrawerAdditionalSectionAboveContent.tsx | 2 +- .../react-core/src/components/Drawer/examples/DrawerBasic.tsx | 2 +- .../src/components/Drawer/examples/DrawerBasicInline.tsx | 2 +- .../src/components/Drawer/examples/DrawerBasicPill.tsx | 2 +- .../src/components/Drawer/examples/DrawerBreakpoint.tsx | 2 +- .../src/components/Drawer/examples/DrawerInlinePanelEnd.tsx | 2 +- .../src/components/Drawer/examples/DrawerInlinePanelStart.tsx | 2 +- .../components/Drawer/examples/DrawerModifiedContentPadding.tsx | 2 +- .../components/Drawer/examples/DrawerModifiedPanelPadding.tsx | 2 +- .../src/components/Drawer/examples/DrawerPanelBottom.tsx | 2 +- .../src/components/Drawer/examples/DrawerPanelEnd.tsx | 2 +- .../src/components/Drawer/examples/DrawerPanelStart.tsx | 2 +- .../src/components/Drawer/examples/DrawerPillInline.tsx | 2 +- .../src/components/Drawer/examples/DrawerResizableAtEnd.tsx | 2 +- .../src/components/Drawer/examples/DrawerResizableAtStart.tsx | 2 +- .../src/components/Drawer/examples/DrawerResizableOnBottom.tsx | 2 +- .../src/components/Drawer/examples/DrawerResizableOnInline.tsx | 2 +- .../react-core/src/components/Drawer/examples/DrawerStatic.tsx | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/react-core/src/components/Drawer/examples/DrawerAdditionalSectionAboveContent.tsx b/packages/react-core/src/components/Drawer/examples/DrawerAdditionalSectionAboveContent.tsx index df64c62bda8..64395a8c59d 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerAdditionalSectionAboveContent.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerAdditionalSectionAboveContent.tsx @@ -13,7 +13,7 @@ import { export const DrawerAdditionalSectionAboveContent: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerBasic.tsx b/packages/react-core/src/components/Drawer/examples/DrawerBasic.tsx index e792e32a058..803d1d92aab 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerBasic.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerBasic.tsx @@ -14,7 +14,7 @@ import { export const DrawerBasic: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerBasicInline.tsx b/packages/react-core/src/components/Drawer/examples/DrawerBasicInline.tsx index a9ceb86666e..2df988d7740 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerBasicInline.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerBasicInline.tsx @@ -12,7 +12,7 @@ import { export const DrawerBasicInline: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerBasicPill.tsx b/packages/react-core/src/components/Drawer/examples/DrawerBasicPill.tsx index dda20e8229c..fdea78cbfe7 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerBasicPill.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerBasicPill.tsx @@ -12,7 +12,7 @@ import { export const DrawerBasicPill: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerBreakpoint.tsx b/packages/react-core/src/components/Drawer/examples/DrawerBreakpoint.tsx index 74eb0eb514f..0401fa35331 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerBreakpoint.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerBreakpoint.tsx @@ -12,7 +12,7 @@ import { export const DrawerBreakpoint: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelEnd.tsx b/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelEnd.tsx index 9574c6a5360..071cd9a3244 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelEnd.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelEnd.tsx @@ -12,7 +12,7 @@ import { export const DrawerInlinePanelEnd: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelStart.tsx b/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelStart.tsx index c1ed8445748..ffe46a2b9e1 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelStart.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerInlinePanelStart.tsx @@ -12,7 +12,7 @@ import { export const DrawerInlinePanelStart: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerModifiedContentPadding.tsx b/packages/react-core/src/components/Drawer/examples/DrawerModifiedContentPadding.tsx index 218f7b44889..9a06d057e3f 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerModifiedContentPadding.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerModifiedContentPadding.tsx @@ -12,7 +12,7 @@ import { export const DrawerModifiedContentPadding: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerModifiedPanelPadding.tsx b/packages/react-core/src/components/Drawer/examples/DrawerModifiedPanelPadding.tsx index 5f28bd3e451..afabeb0f9df 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerModifiedPanelPadding.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerModifiedPanelPadding.tsx @@ -12,7 +12,7 @@ import { export const DrawerModifiedPanelPadding: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerPanelBottom.tsx b/packages/react-core/src/components/Drawer/examples/DrawerPanelBottom.tsx index c89f7f13e1f..1b765555597 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerPanelBottom.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerPanelBottom.tsx @@ -12,7 +12,7 @@ import { export const DrawerPanelBottom: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerPanelEnd.tsx b/packages/react-core/src/components/Drawer/examples/DrawerPanelEnd.tsx index 852b9eff4c1..6c3460ace2f 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerPanelEnd.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerPanelEnd.tsx @@ -12,7 +12,7 @@ import { export const DrawerPanelEnd: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerPanelStart.tsx b/packages/react-core/src/components/Drawer/examples/DrawerPanelStart.tsx index 60c8cc515e0..a1acde0be10 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerPanelStart.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerPanelStart.tsx @@ -12,7 +12,7 @@ import { export const DrawerPanelStart: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerPillInline.tsx b/packages/react-core/src/components/Drawer/examples/DrawerPillInline.tsx index 2e3a42c2c81..16ae9532739 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerPillInline.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerPillInline.tsx @@ -12,7 +12,7 @@ import { export const DrawerBasicPill: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerResizableAtEnd.tsx b/packages/react-core/src/components/Drawer/examples/DrawerResizableAtEnd.tsx index 252c683ce33..939fcf4a441 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerResizableAtEnd.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerResizableAtEnd.tsx @@ -12,7 +12,7 @@ import { export const DrawerResizableAtEnd: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerResizableAtStart.tsx b/packages/react-core/src/components/Drawer/examples/DrawerResizableAtStart.tsx index b5d2ef2da8f..32e56665aa0 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerResizableAtStart.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerResizableAtStart.tsx @@ -12,7 +12,7 @@ import { export const DrawerResizableAtStart: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerResizableOnBottom.tsx b/packages/react-core/src/components/Drawer/examples/DrawerResizableOnBottom.tsx index 86da473b620..ecce2daa1ca 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerResizableOnBottom.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerResizableOnBottom.tsx @@ -12,7 +12,7 @@ import { export const DrawerResizableOnBottom: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerResizableOnInline.tsx b/packages/react-core/src/components/Drawer/examples/DrawerResizableOnInline.tsx index e4dd4a57bd5..16299b6db8e 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerResizableOnInline.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerResizableOnInline.tsx @@ -12,7 +12,7 @@ import { export const DrawerResizableOnInline: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); diff --git a/packages/react-core/src/components/Drawer/examples/DrawerStatic.tsx b/packages/react-core/src/components/Drawer/examples/DrawerStatic.tsx index 773d693207b..2f5229223cc 100644 --- a/packages/react-core/src/components/Drawer/examples/DrawerStatic.tsx +++ b/packages/react-core/src/components/Drawer/examples/DrawerStatic.tsx @@ -13,7 +13,7 @@ import accessibility from '@patternfly/react-styles/css/utilities/Accessibility/ export const DrawerStatic: React.FunctionComponent = () => { const [isExpanded, setIsExpanded] = useState(false); - const drawerRef = useRef(undefined); + const drawerRef = useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); From 1bb6a2b3784ef7b613a8ac110955a417cc77fbd7 Mon Sep 17 00:00:00 2001 From: Mohamed Fall Date: Sun, 14 Dec 2025 12:31:49 +0000 Subject: [PATCH 2/5] fix(Popper): prevent flash of incorrectly positioned popper on open Use requestAnimationFrame to ensure Popper.js has calculated and applied position transforms before transitioning opacity to 1. Signed-off-by: Mohamed Fall --- packages/react-core/src/helpers/Popper/Popper.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/react-core/src/helpers/Popper/Popper.tsx b/packages/react-core/src/helpers/Popper/Popper.tsx index d3d49a591ae..26a041b9748 100644 --- a/packages/react-core/src/helpers/Popper/Popper.tsx +++ b/packages/react-core/src/helpers/Popper/Popper.tsx @@ -483,8 +483,14 @@ export const Popper: React.FunctionComponent = ({ clearTimeouts([transitionTimerRef, hideTimerRef]); showTimerRef.current = setTimeout(() => { setInternalIsVisible(true); - setOpacity(1); - onShown(); + // Ensures React has committed the DOM changes and the popper element is rendered + requestAnimationFrame(() => { + // Ensures Popper.js has calculated and applied the position transform before making element visible + requestAnimationFrame(() => { + setOpacity(1); + onShown(); + }); + }); }, entryDelay); }; From 97c8cfc9087c3474d8d3e3aa2ce05027f103e153 Mon Sep 17 00:00:00 2001 From: Mohamed Fall Date: Sun, 14 Dec 2025 13:45:11 +0000 Subject: [PATCH 3/5] test: update snapshot tests to wait for popper opacity transition Update Jest snapshot tests to wait for popper elements to complete their opacity transition after the double requestAnimationFrame fix. This ensures snapshots are taken after poppers are fully visible (opacity: 1) rather than during the transition phase (opacity: 0). Signed-off-by: Mohamed Fall --- .../components/DatePicker/__tests__/DatePicker.test.tsx | 7 ++++++- .../react-core/src/components/Nav/__tests__/Nav.test.tsx | 7 ++++++- .../components/SearchInput/__tests__/SearchInput.test.tsx | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/react-core/src/components/DatePicker/__tests__/DatePicker.test.tsx b/packages/react-core/src/components/DatePicker/__tests__/DatePicker.test.tsx index 4225fee64e4..1bf4502d22b 100644 --- a/packages/react-core/src/components/DatePicker/__tests__/DatePicker.test.tsx +++ b/packages/react-core/src/components/DatePicker/__tests__/DatePicker.test.tsx @@ -1,4 +1,4 @@ -import { screen, render } from '@testing-library/react'; +import { screen, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HelperText, HelperTextItem } from '../../HelperText'; @@ -93,6 +93,11 @@ test('With popover opened', async () => { await user.click(screen.getByRole('button', { name: 'Toggle date picker' })); await screen.findByRole('button', { name: 'Previous month' }); + // Wait for popper opacity transition after requestAnimationFrame + await waitFor(() => { + const popover = screen.getByRole('dialog'); + expect(popover).toHaveStyle({ opacity: '1' }); + }); expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/react-core/src/components/Nav/__tests__/Nav.test.tsx b/packages/react-core/src/components/Nav/__tests__/Nav.test.tsx index 6153183accb..b73998c0e9d 100644 --- a/packages/react-core/src/components/Nav/__tests__/Nav.test.tsx +++ b/packages/react-core/src/components/Nav/__tests__/Nav.test.tsx @@ -1,5 +1,5 @@ import { StrictMode } from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; @@ -242,6 +242,11 @@ describe('Nav', () => { ); await user.hover(screen.getByText('My custom node')); + // Wait for popper opacity transition after requestAnimationFrame + await waitFor(() => { + const flyout = screen.getByText('Flyout test').parentElement; + expect(flyout).toHaveStyle({ opacity: '1' }); + }); expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/react-core/src/components/SearchInput/__tests__/SearchInput.test.tsx b/packages/react-core/src/components/SearchInput/__tests__/SearchInput.test.tsx index a545ebe7579..301e9063808 100644 --- a/packages/react-core/src/components/SearchInput/__tests__/SearchInput.test.tsx +++ b/packages/react-core/src/components/SearchInput/__tests__/SearchInput.test.tsx @@ -1,5 +1,5 @@ import { StrictMode } from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SearchInput } from '../SearchInput'; @@ -151,6 +151,11 @@ describe('SearchInput', () => { expect(screen.getByTestId('test-id')).toContainElement(screen.getByText('First name')); expect(props.onSearch).toHaveBeenCalled(); + // Wait for popper opacity transition after requestAnimationFrame + await waitFor(() => { + const panel = screen.getByText('First name').closest('.pf-v6-c-panel'); + expect(panel?.parentElement).toHaveStyle({ opacity: '1' }); + }); expect(asFragment()).toMatchSnapshot(); }); From 161cb8840e5befb4b4809a1a08d492293d561563 Mon Sep 17 00:00:00 2001 From: Mohamed Fall Date: Sun, 14 Dec 2025 14:12:20 +0000 Subject: [PATCH 4/5] fix(Popper): prevent race conditions by properly cleaning up animation frames Add cleanup for requestAnimationFrame to prevent race conditions when popper visibility changes rapidly or component unmounts during RAF execution. Signed-off-by: Mohamed Fall --- packages/react-core/src/helpers/Popper/Popper.tsx | 12 +++++++++--- packages/react-core/src/helpers/__mocks__/util.ts | 2 ++ packages/react-core/src/helpers/util.ts | 11 +++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/react-core/src/helpers/Popper/Popper.tsx b/packages/react-core/src/helpers/Popper/Popper.tsx index 26a041b9748..7de3ab3cdd8 100644 --- a/packages/react-core/src/helpers/Popper/Popper.tsx +++ b/packages/react-core/src/helpers/Popper/Popper.tsx @@ -3,7 +3,7 @@ import * as ReactDOM from 'react-dom'; import { usePopper } from './thirdparty/react-popper/usePopper'; import { Options as OffsetOptions } from './thirdparty/popper-core/modifiers/offset'; import { Placement, Modifier } from './thirdparty/popper-core'; -import { clearTimeouts } from '../util'; +import { clearAnimationFrames, clearTimeouts } from '../util'; import { css } from '@patternfly/react-styles'; import '@patternfly/react-styles/css/components/Popper/Popper.css'; import { getLanguageDirection } from '../util'; @@ -234,6 +234,7 @@ export const Popper: React.FunctionComponent = ({ const transitionTimerRef = useRef(null); const showTimerRef = useRef(null); const hideTimerRef = useRef(null); + const rafRef = useRef(null); const prevExitDelayRef = useRef(undefined); const refOrTrigger = refElement || triggerElement; @@ -275,6 +276,7 @@ export const Popper: React.FunctionComponent = ({ useEffect( () => () => { clearTimeouts([transitionTimerRef, hideTimerRef, showTimerRef]); + clearAnimationFrames([rafRef]); }, [] ); @@ -469,6 +471,7 @@ export const Popper: React.FunctionComponent = ({ useEffect(() => { if (prevExitDelayRef.current < exitDelay) { clearTimeouts([transitionTimerRef, hideTimerRef]); + clearAnimationFrames([rafRef]); hideTimerRef.current = setTimeout(() => { transitionTimerRef.current = setTimeout(() => { setInternalIsVisible(false); @@ -481,14 +484,16 @@ export const Popper: React.FunctionComponent = ({ const show = () => { onShow(); clearTimeouts([transitionTimerRef, hideTimerRef]); + clearAnimationFrames([rafRef]); showTimerRef.current = setTimeout(() => { setInternalIsVisible(true); // Ensures React has committed the DOM changes and the popper element is rendered - requestAnimationFrame(() => { + rafRef.current = requestAnimationFrame(() => { // Ensures Popper.js has calculated and applied the position transform before making element visible - requestAnimationFrame(() => { + rafRef.current = requestAnimationFrame(() => { setOpacity(1); onShown(); + rafRef.current = null; }); }); }, entryDelay); @@ -497,6 +502,7 @@ export const Popper: React.FunctionComponent = ({ const hide = () => { onHide(); clearTimeouts([showTimerRef]); + clearAnimationFrames([rafRef]); hideTimerRef.current = setTimeout(() => { setOpacity(0); transitionTimerRef.current = setTimeout(() => { diff --git a/packages/react-core/src/helpers/__mocks__/util.ts b/packages/react-core/src/helpers/__mocks__/util.ts index 5be1b4a6e2c..58c10de56db 100644 --- a/packages/react-core/src/helpers/__mocks__/util.ts +++ b/packages/react-core/src/helpers/__mocks__/util.ts @@ -2,4 +2,6 @@ export const getUniqueId = () => 'unique_id_mock'; export const clearTimeouts = () => {}; +export const clearAnimationFrames = () => {}; + export const getLanguageDirection = () => 'ltr'; diff --git a/packages/react-core/src/helpers/util.ts b/packages/react-core/src/helpers/util.ts index ac63bbaeade..e323815be84 100644 --- a/packages/react-core/src/helpers/util.ts +++ b/packages/react-core/src/helpers/util.ts @@ -524,6 +524,17 @@ export const clearTimeouts = (timeoutRefs: React.RefObject[]) => { }); }; +/** + * @param {React.RefObject[]} animationFrameRefs - Animation frame refs to clear + */ +export const clearAnimationFrames = (animationFrameRefs: React.RefObject[]) => { + animationFrameRefs.forEach((ref) => { + if (ref.current) { + cancelAnimationFrame(ref.current); + } + }); +}; + /** * Helper function to get the language direction of a given element, useful for figuring out if left-to-right * or right-to-left specific logic should be applied. From a9a1aafe05bca4ddf2c9cf14b97bf2c5d938e796 Mon Sep 17 00:00:00 2001 From: Mohamed Fall Date: Sat, 20 Dec 2025 12:42:21 +0000 Subject: [PATCH 5/5] fix(tests): increase tooltip visibility timeout to accomodate for async nature Signed-off-by: Mohamed Fall --- .../react-integration/cypress/integration/button.spec.ts | 5 +++-- .../cypress/integration/overflowmenu.spec.ts | 8 ++++---- .../cypress/integration/tabsdisable.spec.ts | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/react-integration/cypress/integration/button.spec.ts b/packages/react-integration/cypress/integration/button.spec.ts index 50ba5537d99..2d4491d25d6 100644 --- a/packages/react-integration/cypress/integration/button.spec.ts +++ b/packages/react-integration/cypress/integration/button.spec.ts @@ -9,7 +9,8 @@ describe('Button Demo Test', () => { .focus() .should('have.attr', 'aria-describedby', 'button-with-tooltip-1'); }); - cy.get('.pf-v6-c-tooltip').should('be.visible'); + // Tooltip visibility is async due to requestAnimationFrame-based positioning + cy.get('.pf-v6-c-tooltip', { timeout: 6000 }).should('be.visible'); }); it('Verify isAriaDisabled button has tooltip when hovered', () => { @@ -18,7 +19,7 @@ describe('Button Demo Test', () => { .trigger('mouseover') .should('have.attr', 'aria-describedby', 'button-with-tooltip-1'); }); - cy.get('.pf-v6-c-tooltip').should('be.visible'); + cy.get('.pf-v6-c-tooltip', { timeout: 6000 }).should('be.visible'); }); it('Verify isAriaDisabled button prevents default actions', () => { diff --git a/packages/react-integration/cypress/integration/overflowmenu.spec.ts b/packages/react-integration/cypress/integration/overflowmenu.spec.ts index bffe6612847..58918174d99 100644 --- a/packages/react-integration/cypress/integration/overflowmenu.spec.ts +++ b/packages/react-integration/cypress/integration/overflowmenu.spec.ts @@ -32,7 +32,7 @@ describe('OverflowMenu Demo Test', () => { it('Verify dropdown menu expanded', () => { cy.get('#simple-overflow-menu button').last().click({ force: true }); cy.get('#simple-overflow-menu .pf-v6-c-menu-toggle').should('have.class', 'pf-m-expanded'); - cy.get('.simple-overflow-menu.pf-v6-c-menu').should('be.visible'); + cy.get('.simple-overflow-menu.pf-v6-c-menu', { timeout: 6000 }).should('be.visible'); // close overflow menu again cy.get('#simple-overflow-menu button').last().click({ force: true }); }); @@ -69,7 +69,7 @@ describe('OverflowMenu Demo Test', () => { it('Verify dropdown menu expanded', () => { cy.get('#additional-options-overflow-menu button').last().click({ force: true }); cy.get('#additional-options-overflow-menu .pf-v6-c-menu-toggle').should('have.class', 'pf-m-expanded'); - cy.get('.additional-options-overflow-menu.pf-v6-c-menu').should('be.visible'); + cy.get('.additional-options-overflow-menu.pf-v6-c-menu', { timeout: 6000 }).should('be.visible'); }); }); }); @@ -107,7 +107,7 @@ describe('OverflowMenu Demo Test', () => { it('Verify dropdown menu expanded', () => { cy.get('#persist-overflow-menu button').last().click({ force: true }); cy.get('#persist-overflow-menu .pf-v6-c-menu-toggle').should('have.class', 'pf-m-expanded'); - cy.get('.persist-overflow-menu.pf-v6-c-menu').should('be.visible'); + cy.get('.persist-overflow-menu.pf-v6-c-menu', { timeout: 6000 }).should('be.visible'); }); }); }); @@ -142,7 +142,7 @@ describe('OverflowMenu Demo Test', () => { it('Verify dropdown menu expanded', () => { cy.get('#container-breakpoint-overflow-menu button').last().click({ force: true }); cy.get('#container-breakpoint-overflow-menu .pf-v6-c-menu-toggle').should('have.class', 'pf-m-expanded'); - cy.get('.container-breakpoint-overflow-menu.pf-v6-c-menu').should('be.visible'); + cy.get('.container-breakpoint-overflow-menu.pf-v6-c-menu', { timeout: 6000 }).should('be.visible'); // close overflow menu again cy.get('#container-breakpoint-overflow-menu button').last().click({ force: true }); }); diff --git a/packages/react-integration/cypress/integration/tabsdisable.spec.ts b/packages/react-integration/cypress/integration/tabsdisable.spec.ts index 0512d164183..2ea548b370d 100644 --- a/packages/react-integration/cypress/integration/tabsdisable.spec.ts +++ b/packages/react-integration/cypress/integration/tabsdisable.spec.ts @@ -40,6 +40,7 @@ describe('Disabled Tab Demo Test', () => { it('Verify aria-disabled with tooltip', () => { cy.get(withTooltip.button).trigger('mouseover'); - cy.get('.pf-v6-c-tooltip').should('be.visible'); + // Tooltip visibility is async due to requestAnimationFrame-based positioning + cy.get('.pf-v6-c-tooltip', { timeout: 6000 }).should('be.visible'); }); });