From 72461f224b2f6a826b4e3f233097284ce0a8fcef Mon Sep 17 00:00:00 2001 From: Alaa Yahia Date: Mon, 18 Mar 2024 09:11:17 +0200 Subject: [PATCH 1/2] feat(tooltip): accessibility improvements for tooltip --- components/tooltip/src/tooltip.js | 49 +++++++++++++++++++++----- components/tooltip/src/tooltip.test.js | 42 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/components/tooltip/src/tooltip.js b/components/tooltip/src/tooltip.js index 9c19f46041..3053d1e19a 100644 --- a/components/tooltip/src/tooltip.js +++ b/components/tooltip/src/tooltip.js @@ -9,8 +9,6 @@ const TOOLTIP_OFFSET = 4 const popperStyle = resolve` z-index: ${layers.applicationTop}; - pointer-events: none; - // Hide popper when reference component is obscured (https://popper.js.org/docs/v2/modifiers/hide/) div[data-popper-reference-hidden="true"] { visibility: hidden; @@ -66,13 +64,39 @@ const Tooltip = ({ }, closeDelay) } - useEffect( - () => () => { + const onFocus = () => { + clearTimeout(closeTimerRef.current) + + openTimerRef.current = setTimeout(() => { + setOpen(true) + }, openDelay) + } + + const onBlur = () => { + clearTimeout(openTimerRef.current) + + closeTimerRef.current = setTimeout(() => { + setOpen(false) + }, closeDelay) + } + + const handleKeyDown = (event) => { + if (event.key === 'Escape') { + closeTimerRef.current = setTimeout(() => { + setOpen(false) + }, closeDelay) + } + } + + useEffect(() => { + document.addEventListener('keydown', handleKeyDown) + + return () => { clearTimeout(openTimerRef.current) clearTimeout(closeTimerRef.current) - }, - [] - ) + document.removeEventListener('keydown', handleKeyDown) + } + }, []) return ( <> @@ -80,19 +104,23 @@ const Tooltip = ({ children({ onMouseOver: onMouseOver, onMouseOut: onMouseOut, + onFocus: { onFocus }, + onBlur: { onBlur }, ref: popperReference, }) ) : ( {children} )} - {open && ( diff --git a/components/tooltip/src/tooltip.test.js b/components/tooltip/src/tooltip.test.js index 5f44c6b18a..82efad3c16 100644 --- a/components/tooltip/src/tooltip.test.js +++ b/components/tooltip/src/tooltip.test.js @@ -152,3 +152,45 @@ describe('it handles custom open and close delays', () => { expect(document.querySelector(tooltipContentSelector)).toBe(null) }) }) + +describe('Keyboard interaction', () => { + it('opens on keyboard focus and closes on blur', () => { + wrapper = mount(Hi hi!) + wrapper.simulate('focus') + + expect(document.querySelector(tooltipContentSelector)).toBe(null) + + // wait for 'open delay' to elapse to open tooltip + act(() => { + jest.advanceTimersByTime(200) + }) + + // expect tooltip to be open after delay + const res = document.querySelector(tooltipContentSelector) + expect(res).not.toBe(null) + expect(res.textContent).toBe(tooltipContent) + + wrapper.simulate('blur') + act(() => { + jest.advanceTimersByTime(200) // Assuming default closeDelay is 200ms + }) + expect(document.querySelector(tooltipContentSelector)).toBe(null) + // this last part clears a warning about "code should be wrapped in `act(...)`" + // and clears the tooltip + act(() => { + jest.runAllTimers() + }) + }) + + it('closes when escape key is pressed', () => { + wrapper = mount(Hi hi!) + + // open tooltip + wrapper.simulate('mouseover') + act(() => { + jest.advanceTimersByTime(200) // open the tooltip + }) + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) + expect(document.querySelector(tooltipContentSelector)).toBe(null) + }) +}) From eeafeef8eebea2538655853b7c3dcd888a41c282 Mon Sep 17 00:00:00 2001 From: Alaa Yahia Date: Tue, 19 Mar 2024 14:42:10 +0200 Subject: [PATCH 2/2] fix(tooltip): solve failing test --- components/tooltip/src/tooltip.js | 1 + components/tooltip/src/tooltip.test.js | 28 +++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/components/tooltip/src/tooltip.js b/components/tooltip/src/tooltip.js index 3053d1e19a..bccb6bf101 100644 --- a/components/tooltip/src/tooltip.js +++ b/components/tooltip/src/tooltip.js @@ -130,6 +130,7 @@ const Tooltip = ({ modifiers={[offsetModifier, flipModifier, hideModifier]} >
{ expect(document.querySelector(tooltipContentSelector)).toBe(null) - // wait for 'open delay' to elapse to open tooltip act(() => { jest.advanceTimersByTime(200) }) - // expect tooltip to be open after delay const res = document.querySelector(tooltipContentSelector) expect(res).not.toBe(null) expect(res.textContent).toBe(tooltipContent) wrapper.simulate('blur') act(() => { - jest.advanceTimersByTime(200) // Assuming default closeDelay is 200ms + jest.advanceTimersByTime(200) }) expect(document.querySelector(tooltipContentSelector)).toBe(null) - // this last part clears a warning about "code should be wrapped in `act(...)`" - // and clears the tooltip + act(() => { jest.runAllTimers() }) @@ -187,10 +184,27 @@ describe('Keyboard interaction', () => { // open tooltip wrapper.simulate('mouseover') + act(() => { - jest.advanceTimersByTime(200) // open the tooltip + jest.advanceTimersByTime(200) }) - document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })) + + // verify tooltip is open + expect(document.querySelector(tooltipContentSelector)).not.toBe(null) + + //Press the Escape key + act(() => { + document.dispatchEvent( + new KeyboardEvent('keydown', { key: 'Escape' }) + ) + }) + + // wait for close delay + act(() => { + jest.advanceTimersByTime(200) + }) + + // expect tooltip to be closed expect(document.querySelector(tooltipContentSelector)).toBe(null) }) })