diff --git a/packages/@react-aria/interactions/test/PressResponder.test.js b/packages/@react-aria/interactions/test/PressResponder.test.js index 4ac92d91fa9..ed52256b539 100644 --- a/packages/@react-aria/interactions/test/PressResponder.test.js +++ b/packages/@react-aria/interactions/test/PressResponder.test.js @@ -77,7 +77,7 @@ describe('PressResponder', function () { warn.mockRestore(); }); - it('should should merge with existing props, not overwrite', function () { + it('should merge with existing props, not overwrite', function () { let onPress = jest.fn(); let onClick = jest.fn(); let {getByRole} = render( diff --git a/packages/@react-aria/interactions/test/useHover.test.js b/packages/@react-aria/interactions/test/useHover.test.js new file mode 100644 index 00000000000..233ab5be18d --- /dev/null +++ b/packages/@react-aria/interactions/test/useHover.test.js @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {cleanup, fireEvent, render} from '@testing-library/react'; +import React from 'react'; +import {useHover} from '../'; + +function Example(props) { + let {hoverProps} = useHover(props); + return
test
; +} + +describe('useHover', function () { + afterEach(cleanup); + + it('does not handle hover events if disabled', function () { + let events = []; + let addEvent = (e) => events.push(e); + let res = render( + addEvent({type: 'hoverchange', isHovering})} + onHover={addEvent} /> + ); + + let el = res.getByText('test'); + fireEvent.mouseEnter(el); + fireEvent.mouseLeave(el); + + expect(events).toEqual([]); + }); + + describe('mouse events', function () { + it('should fire hover events based on mouse events', function () { + let events = []; + let addEvent = (e) => events.push(e); + let res = render( + addEvent({type: 'hoverchange', isHovering})} + onHover={addEvent} /> + ); + + let el = res.getByText('test'); + fireEvent.mouseEnter(el); + fireEvent.mouseLeave(el); + + expect(events).toEqual([ + { + type: 'hover', + target: el, + pointerType: 'mouse' + }, + { + type: 'hoverchange', + isHovering: true + }, + { + type: 'hoverend', + target: el, + pointerType: 'mouse' + }, + { + type: 'hoverchange', + isHovering: false + } + ]); + }); + }); + + describe('touch events', function () { + it('should not fire hover events based on touch events', function () { + let events = []; + let addEvent = (e) => events.push(e); + let res = render( + addEvent({type: 'hoverchange', isHovering})} + onHover={addEvent} /> + ); + + let el = res.getByText('test'); + fireEvent.touchStart(el); + fireEvent.touchMove(el); + fireEvent.touchEnd(el); + + expect(events).toEqual([]); + }); + }); +}); diff --git a/packages/@react-spectrum/tooltip/test/TooltipTrigger.test.js b/packages/@react-spectrum/tooltip/test/TooltipTrigger.test.js index 5198d393091..a8b093e736b 100644 --- a/packages/@react-spectrum/tooltip/test/TooltipTrigger.test.js +++ b/packages/@react-spectrum/tooltip/test/TooltipTrigger.test.js @@ -11,7 +11,7 @@ */ import {ActionButton} from '@react-spectrum/button'; -import {cleanup, fireEvent, render, wait, waitForDomChange} from '@testing-library/react'; +import {cleanup, fireEvent, render, wait} from '@testing-library/react'; import {Provider} from '@react-spectrum/provider'; import React from 'react'; import scaleMedium from '@adobe/spectrum-css-temp/vars/spectrum-medium-unique.css'; @@ -34,9 +34,9 @@ describe('TooltipTrigger', function () { cleanup(); }); - describe('check defaults', function () { + describe('click related tests', function () { - it('triggered by click event', async function () { + it('a click event can open the tooltip', async function () { let {getByRole} = render( @@ -46,28 +46,17 @@ describe('TooltipTrigger', function () { ); - expect(() => { - getByRole('tooltip'); - }).toThrow(); - let button = getByRole('button'); triggerPress(button); let tooltip = getByRole('tooltip'); - // wait for appearance await wait(() => { - expect(tooltip).toBeVisible(); + expect(tooltip).toBeInTheDocument(); }); - - expect(tooltip.id).toBeTruthy(); - expect(button).toHaveAttribute('aria-describedby', tooltip.id); }); - }); - - describe('click related tests', function () { - it('triggered by click event', async function () { + it('a click event can close the tooltip', async function () { let {getByRole} = render( @@ -77,22 +66,23 @@ describe('TooltipTrigger', function () { ); - expect(() => { - getByRole('tooltip'); - }).toThrow(); - let button = getByRole('button'); triggerPress(button); let tooltip = getByRole('tooltip'); - // wait for appearance await wait(() => { - expect(tooltip).toBeVisible(); + expect(tooltip).toBeInTheDocument(); + }); + + triggerPress(button); + + await wait(() => { + expect(tooltip).not.toBeInTheDocument(); }); }); - it('pressing esc should close the tooltip after a click event', async function () { + it('pressing escape should close the tooltip after a click event', async function () { let {getByRole} = render( @@ -107,15 +97,91 @@ describe('TooltipTrigger', function () { let tooltip = getByRole('tooltip'); - // wait for appearance await wait(() => { expect(tooltip).toBeInTheDocument(); }); fireEvent.keyDown(button, {key: 'Escape'}); - await waitForDomChange(); - expect(tooltip).not.toBeInTheDocument(); + await wait(() => { + expect(tooltip).not.toBeInTheDocument(); + }); + }); + }); + + describe('focus related tests', function () { + + it('pressing escape if the trigger is focused should close the tooltip', async function () { + let {getByText} = render( + + + Trigger + content + + + ); + + let button = getByText('Trigger'); + fireEvent.mouseOver(button); + + await new Promise((b) => setTimeout(b, 300)); + + let tooltip = getByText('content'); + expect(tooltip).toBeInTheDocument(); + + fireEvent.focus(button); + fireEvent.keyDown(button, {key: 'Escape'}); + + await wait(() => { + expect(tooltip).not.toBeInTheDocument(); + }); + }); + }); + + describe('hover related tests', function () { + + it('a mouseOver event can open the tooltip', async function () { + let {getByText} = render( + + + Trigger + content + + + ); + + let button = getByText('Trigger'); + fireEvent.mouseOver(button); + + await new Promise((c) => setTimeout(c, 300)); + + let tooltip = getByText('content'); + expect(tooltip).toBeInTheDocument(); + }); + + it('a mouseOver event can close the tooltip', async function () { + let {getByText} = render( + + + Trigger + content + + + ); + + let button = getByText('Trigger'); + fireEvent.mouseOver(button); + + await new Promise((c) => setTimeout(c, 300)); + + let tooltip = getByText('content'); + expect(tooltip).toBeInTheDocument(); + + fireEvent.mouseOver(button); + + await wait(() => { + expect(tooltip).not.toBeInTheDocument(); + }); }); }); }); diff --git a/packages/@react-stately/tooltip/test/TooltipManager.test.js b/packages/@react-stately/tooltip/test/TooltipManager.test.js new file mode 100644 index 00000000000..5029d22b186 --- /dev/null +++ b/packages/@react-stately/tooltip/test/TooltipManager.test.js @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {TooltipManager} from '../'; + +describe('TooltipManager', () => { + + it('can show a tooltip', () => { + jest.useFakeTimers(); + let tooltipManager = new TooltipManager(); + let setOpenSpy = jest.fn(); + let tooltip = {open: false, setOpen: setOpenSpy, tooltipManager}; + let triggerId = 'triggerId-1'; + + expect(tooltipManager.visibleTooltips).toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + expect(tooltipManager.hoverShowTimeout).toBeNull(); + + tooltipManager.showTooltipDelayed(tooltip, triggerId); + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 200); + jest.runAllTimers(); + + expect(setOpenSpy).toHaveBeenCalledWith(true); + expect(tooltipManager.visibleTooltips).toStrictEqual({triggerId, state: tooltip}); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + expect(tooltipManager.hoverShowTimeout).toBeNull(); + }); + + it('can hide the currently visible tooltip', () => { + jest.useFakeTimers(); + let tooltipManager = new TooltipManager(); + let setOpenSpy = jest.fn(); + let tooltip = {open: false, setOpen: setOpenSpy, tooltipManager}; + let triggerId = 'triggerId-1'; + + tooltipManager.showTooltipDelayed(tooltip, triggerId); + + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 200); + jest.runAllTimers(); + + expect(setOpenSpy).toHaveBeenCalledWith(true); + expect(tooltipManager.visibleTooltips).toStrictEqual({triggerId, state: tooltip}); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + expect(tooltipManager.hoverShowTimeout).toBeNull(); + + tooltipManager.hideTooltipDelayed(tooltip, triggerId); + + expect(tooltipManager.visibleTooltips).toStrictEqual({triggerId, state: tooltip}); + expect(tooltipManager.hoverHideTimeout).toBeTruthy(); + expect(tooltipManager.hoverShowTimeout).toBeNull(); + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 200); + jest.runAllTimers(); + expect(setOpenSpy).toHaveBeenCalledWith(false); + expect(tooltipManager.visibleTooltips).toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + expect(tooltipManager.hoverShowTimeout).toBeNull(); + }); + + it('will close the currently open tooltip when showing a new one', () => { + jest.useFakeTimers(); + let tooltipManager = new TooltipManager(); + let setOpenSpy = jest.fn(); + let tooltip = {open: false, setOpen: setOpenSpy, tooltipManager}; + let triggerId = 'triggerId-1'; + + tooltipManager.showTooltipDelayed(tooltip, triggerId); + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 200); + jest.runAllTimers(); + + let setOpenSpy2 = jest.fn(); + let tooltip2 = {open: false, setOpen: setOpenSpy2, tooltipManager}; + let triggerId2 = 'triggerId-2'; + + tooltipManager.showTooltipDelayed(tooltip2, triggerId2); + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 200); + jest.runAllTimers(); + + expect(setOpenSpy).toHaveBeenLastCalledWith(false); + expect(setOpenSpy2).toHaveBeenCalledWith(true); + expect(tooltipManager.visibleTooltips).toStrictEqual({triggerId: triggerId2, state: tooltip2}); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + expect(tooltipManager.hoverShowTimeout).toBeNull(); + }); + + it('will not show the tooltip if hidden before the delayed show completes', () => { + jest.useFakeTimers(); + let tooltipManager = new TooltipManager(); + let setOpenSpy = jest.fn(); + let tooltip = {open: false, setOpen: setOpenSpy, tooltipManager}; + let triggerId = 'triggerId-1'; + + tooltipManager.showTooltipDelayed(tooltip, triggerId); + + expect(tooltipManager.hoverShowTimeout).not.toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + + jest.advanceTimersByTime(100); + expect(tooltipManager.visibleTooltips).toBeNull(); + expect(tooltipManager.hoverShowTimeout).not.toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + + tooltipManager.hideTooltip(tooltip); + + expect(setOpenSpy).toHaveBeenCalledWith(false); + expect(tooltipManager.visibleTooltips).toBeNull(); + }); + + it('will not show the first delayed tooltip if a second is delay shown before the first shows', () => { + jest.useFakeTimers(); + let tooltipManager = new TooltipManager(); + let setOpenSpy = jest.fn(); + let tooltip = {open: false, setOpen: setOpenSpy, tooltipManager}; + let triggerId = 'triggerId-1'; + + tooltipManager.showTooltipDelayed(tooltip, triggerId); + expect(tooltipManager.hoverShowTimeout).not.toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + + jest.advanceTimersByTime(100); + expect(tooltipManager.visibleTooltips).toBeNull(); + expect(tooltipManager.hoverShowTimeout).not.toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + + let setOpenSpy2 = jest.fn(); + let tooltip2 = {open: false, setOpen: setOpenSpy2, tooltipManager}; + let triggerId2 = 'triggerId-2'; + + tooltipManager.showTooltipDelayed(tooltip2, triggerId2); + + expect(tooltipManager.hoverShowTimeout).not.toBeNull(); + expect(tooltipManager.hoverHideTimeout).toBeNull(); + expect(tooltipManager.visibleTooltips).toBeNull(); + + // run past first tooltips show timer + jest.advanceTimersByTime(150); + // finish all the timers + jest.runAllTimers(); + expect(setOpenSpy2).toHaveBeenCalledWith(true); + expect(tooltipManager.visibleTooltips).toStrictEqual({triggerId: triggerId2, state: tooltip2}); + }); +});