Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
});

Expand Down
18 changes: 15 additions & 3 deletions packages/react-core/src/helpers/Popper/Popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -234,6 +234,7 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
const transitionTimerRef = useRef(null);
const showTimerRef = useRef(null);
const hideTimerRef = useRef(null);
const rafRef = useRef<number>(null);
const prevExitDelayRef = useRef<number>(undefined);

const refOrTrigger = refElement || triggerElement;
Expand Down Expand Up @@ -275,6 +276,7 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
useEffect(
() => () => {
clearTimeouts([transitionTimerRef, hideTimerRef, showTimerRef]);
clearAnimationFrames([rafRef]);
},
[]
);
Expand Down Expand Up @@ -469,6 +471,7 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
useEffect(() => {
if (prevExitDelayRef.current < exitDelay) {
clearTimeouts([transitionTimerRef, hideTimerRef]);
clearAnimationFrames([rafRef]);
hideTimerRef.current = setTimeout(() => {
transitionTimerRef.current = setTimeout(() => {
setInternalIsVisible(false);
Expand All @@ -481,16 +484,25 @@ export const Popper: React.FunctionComponent<PopperProps> = ({
const show = () => {
onShow();
clearTimeouts([transitionTimerRef, hideTimerRef]);
clearAnimationFrames([rafRef]);
showTimerRef.current = setTimeout(() => {
setInternalIsVisible(true);
setOpacity(1);
onShown();
// Ensures React has committed the DOM changes and the popper element is rendered
rafRef.current = requestAnimationFrame(() => {
// Ensures Popper.js has calculated and applied the position transform before making element visible
rafRef.current = requestAnimationFrame(() => {
setOpacity(1);
onShown();
rafRef.current = null;
});
});
}, entryDelay);
};

const hide = () => {
onHide();
clearTimeouts([showTimerRef]);
clearAnimationFrames([rafRef]);
hideTimerRef.current = setTimeout(() => {
setOpacity(0);
transitionTimerRef.current = setTimeout(() => {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-core/src/helpers/__mocks__/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ export const getUniqueId = () => 'unique_id_mock';

export const clearTimeouts = () => {};

export const clearAnimationFrames = () => {};

export const getLanguageDirection = () => 'ltr';
11 changes: 11 additions & 0 deletions packages/react-core/src/helpers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,17 @@ export const clearTimeouts = (timeoutRefs: React.RefObject<any>[]) => {
});
};

/**
* @param {React.RefObject<any>[]} animationFrameRefs - Animation frame refs to clear
*/
export const clearAnimationFrames = (animationFrameRefs: React.RefObject<any>[]) => {
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.
Expand Down
5 changes: 3 additions & 2 deletions packages/react-integration/cypress/integration/button.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
});
Expand Down Expand Up @@ -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');
});
});
});
Expand Down Expand Up @@ -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');
});
});
});
Expand Down Expand Up @@ -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 });
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
Loading