From b22ce34ede97f4ab463da4b5b825e0f072a3d125 Mon Sep 17 00:00:00 2001 From: tarunvashishth Date: Sat, 24 Oct 2026 09:16:20 +0000 Subject: [PATCH] fix(Drawer): call latest onResize from resizable panel mouse/touch handlers The document-level mousemove/mouseup/touchmove/touchend listeners in DrawerPanelContent were memoized with useCallback(..., []) around the first render's handlers. As a result, ending a drag called the onResize (and id/position) captured on the first render instead of the current props. Keep the listeners stable so they can still be removed, but delegate to the latest handlers through a ref. Add regression tests: onResize receives the resized (non-zero) width after ArrowLeft then Enter/Escape, and a mouse drag reports the dragged width to the most recent onResize after a rerender. --- .../components/Drawer/DrawerPanelContent.tsx | 13 ++- .../__tests__/DrawerPanelContent.test.tsx | 84 ++++++++++++++++++- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/packages/react-core/src/components/Drawer/DrawerPanelContent.tsx b/packages/react-core/src/components/Drawer/DrawerPanelContent.tsx index 14b8c4eb056..e27e034ab46 100644 --- a/packages/react-core/src/components/Drawer/DrawerPanelContent.tsx +++ b/packages/react-core/src/components/Drawer/DrawerPanelContent.tsx @@ -276,10 +276,15 @@ export const DrawerPanelContent: React.FunctionComponent latestHandlers.current.handleMouseMove(e), []); + const callbackTouchEnd = useCallback((e: TouchEvent) => latestHandlers.current.handleTouchEnd(e), []); + const callbackTouchMove = useCallback((e: TouchEvent) => latestHandlers.current.handleTouchMove(e), []); + const callbackMouseUp = useCallback((e: MouseEvent) => latestHandlers.current.handleMouseup(e), []); const handleKeys = (e: React.KeyboardEvent) => { const isRTL = getLanguageDirection(panel.current) === 'rtl'; diff --git a/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx b/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx index ba0b0cec4c4..d109587f309 100644 --- a/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx +++ b/packages/react-core/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx @@ -1,6 +1,9 @@ -import { render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { DrawerPanelContent } from '../DrawerPanelContent'; import { Drawer } from '../Drawer'; +import { DrawerContent } from '../DrawerContent'; +import { DrawerContentBody } from '../DrawerContentBody'; import styles from '@patternfly/react-styles/css/components/Drawer/drawer'; test(`Renders with only class ${styles.drawerPanel} by default`, () => { @@ -218,3 +221,82 @@ test(`Renders with class ${styles.modifiers.noPlainOnGlass} when isNoPlainOnGlas expect(screen.getByText('Drawer panel content')).toHaveClass(styles.modifiers.noPlainOnGlass); }); + +describe('resizable onResize width', () => { + const panelWidth = 300; + let rectSpy: jest.SpyInstance; + + beforeEach(() => { + rectSpy = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation( + () => + ({ + width: panelWidth, + height: panelWidth, + top: 0, + left: 0, + right: panelWidth, + bottom: panelWidth, + x: 0, + y: 0, + toJSON: () => {} + }) as DOMRect + ); + }); + + afterEach(() => { + rectSpy.mockRestore(); + }); + + const renderResizableDrawer = (onResize: jest.Mock) => + render( + + }> + Content + + + ); + + test('reports the resized width when Enter is pressed after an arrow key', async () => { + const user = userEvent.setup(); + const onResize = jest.fn(); + renderResizableDrawer(onResize); + + act(() => screen.getByRole('separator').focus()); + await user.keyboard('{ArrowLeft}{Enter}'); + + expect(onResize).toHaveBeenCalledTimes(1); + expect(onResize).toHaveBeenCalledWith(expect.anything(), panelWidth + 5, 'panel'); + }); + + test('reports the resized width when Escape is pressed after an arrow key', async () => { + const user = userEvent.setup(); + const onResize = jest.fn(); + renderResizableDrawer(onResize); + + act(() => screen.getByRole('separator').focus()); + await user.keyboard('{ArrowLeft}{Escape}'); + + expect(onResize).toHaveBeenCalledWith(expect.anything(), panelWidth + 5, 'panel'); + }); + + test('calls the latest onResize with the dragged width on mouseup after a rerender', () => { + const firstOnResize = jest.fn(); + const latestOnResize = jest.fn(); + const { rerender } = renderResizableDrawer(firstOnResize); + rerender( + + }> + Content + + + ); + + const separator = screen.getByRole('separator'); + fireEvent.mouseDown(separator, { clientX: 100 }); + fireEvent.mouseMove(document, { clientX: 90 }); + fireEvent.mouseUp(document); + + expect(firstOnResize).not.toHaveBeenCalled(); + expect(latestOnResize).toHaveBeenCalledWith(expect.anything(), panelWidth + 10, 'panel'); + }); +});