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});
+ });
+});