diff --git a/static/app/utils/useHoverOverlay.spec.tsx b/static/app/utils/useHoverOverlay.spec.tsx new file mode 100644 index 00000000000000..f2c765450877a1 --- /dev/null +++ b/static/app/utils/useHoverOverlay.spec.tsx @@ -0,0 +1,36 @@ +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; + +import {useHoverOverlay} from 'sentry/utils/useHoverOverlay'; + +function Component(props: Partial>) { + const {wrapTrigger} = useHoverOverlay('test', {skipWrapper: true}); + + return wrapTrigger(); +} + +describe('useHoverOverlay', () => { + it('skipWrapper=true composes handlers', async () => { + const componentProps = { + onPointerEnter: jest.fn(), + onPointerLeave: jest.fn(), + onFocus: jest.fn(), + onBlur: jest.fn(), + }; + + render(); + + const input = screen.getByRole('textbox'); + + await userEvent.hover(input); + expect(componentProps.onPointerEnter).toHaveBeenCalled(); + + await userEvent.unhover(input); + expect(componentProps.onPointerLeave).toHaveBeenCalled(); + + await userEvent.click(input); + expect(componentProps.onFocus).toHaveBeenCalled(); + + await userEvent.tab(); + expect(componentProps.onBlur).toHaveBeenCalled(); + }); +}); diff --git a/static/app/utils/useHoverOverlay.tsx b/static/app/utils/useHoverOverlay.tsx index 3e563f5f330d0c..4c8f6ccb06671f 100644 --- a/static/app/utils/useHoverOverlay.tsx +++ b/static/app/utils/useHoverOverlay.tsx @@ -10,6 +10,7 @@ import { import type {PopperProps} from 'react-popper'; import {usePopper} from 'react-popper'; import {useTheme} from '@emotion/react'; +import {mergeProps} from '@react-aria/utils'; import domId from 'sentry/utils/domId'; import type {ColorOrAlias} from 'sentry/utils/theme'; @@ -266,9 +267,11 @@ function useHoverOverlay( */ const wrapTrigger = useCallback( (triggerChildren: React.ReactNode) => { - const props = { + const providedProps = { + // !!These props are always overriden!! 'aria-describedby': describeById, ref: setTriggerElement, + // The following props are composed from the componentProps trigger props onFocus: handleMouseEnter, onBlur: handleMouseLeave, onPointerEnter: handleMouseEnter, @@ -291,20 +294,22 @@ function useHoverOverlay( return cloneElement( triggerChildren, - Object.assign(props, {style: triggerStyle}) + Object.assign(mergeProps(triggerChildren.props, providedProps), { + style: triggerStyle, + }) ); } // Basic DOM nodes can be cloned and have more props applied. return cloneElement( triggerChildren, - Object.assign(props, { + Object.assign(mergeProps(triggerChildren.props, providedProps), { style: triggerChildren.props.style, }) ); } - const containerProps = Object.assign(props, { + const containerProps = Object.assign(providedProps, { style: { ...(showUnderline ? theme.tooltipUnderline(underlineColor) : {}), ...(containerDisplayMode ? {display: containerDisplayMode} : {}),