Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/tooltips/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Tooltip } from '@zendeskgarden/react-tooltips';
* Place a `ThemeProvider` at the root of your React application
*/
<ThemeProvider>
<Tooltip trigger={<button>Trigger top placement</button>}>This an small tooltip</Tooltip>
<Tooltip trigger={<button>Trigger top placement</button>}>This is a small tooltip</Tooltip>
</ThemeProvider>;
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const { Input } = require('@zendeskgarden/react-textfields/src');
{...getTriggerProps({
onMouseEnter: event => event.preventDefault(), // stop our default logic
onMouseLeave: event => event.preventDefault(), // stop our default logic
'aria-label': 'Example hover only input',
placeholder: 'Hover does not trigger me, but focus does',
style: { width: 500 }
})}
Expand Down
17 changes: 5 additions & 12 deletions packages/tooltips/src/containers/TooltipContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ class TooltipContainer extends ControlledComponent {
}, delayMilliseconds);
};

getTooltipId = () => `${this.getControlledState().id}--tooltip`;

getTriggerProps = ({
tabIndex = 0,
onMouseEnter,
Expand All @@ -149,7 +147,6 @@ class TooltipContainer extends ControlledComponent {
} = {}) => {
return {
tabIndex,
'aria-describedby': this.getTooltipId(),
onMouseEnter: composeEventHandlers(onMouseEnter, () => this.openTooltip()),
onMouseLeave: composeEventHandlers(onMouseLeave, () => this.closeTooltip()),
onFocus: composeEventHandlers(onFocus, () => this.openTooltip()),
Expand All @@ -159,15 +156,8 @@ class TooltipContainer extends ControlledComponent {
};
};

getTooltipProps = ({
id = this.getTooltipId(),
role = 'tooltip',
onMouseEnter,
onMouseLeave,
...other
} = {}) => {
getTooltipProps = ({ role = 'tooltip', onMouseEnter, onMouseLeave, ...other } = {}) => {
return {
id,
role,
onMouseEnter: composeEventHandlers(onMouseEnter, () => this.openTooltip()),
onMouseLeave: composeEventHandlers(onMouseLeave, () => this.closeTooltip()),
Expand Down Expand Up @@ -222,11 +212,14 @@ class TooltipContainer extends ControlledComponent {
const popperPlacement = popperProps['data-placement'];
const outOfBoundaries = popperProps['data-x-out-of-boundaries'];

if (!isVisible) {
return null;
}

const tooltip = (
<TooltipWrapper
innerRef={popperProps.ref}
style={popperProps.style}
aria-hidden={!isVisible}
zIndex={zIndex}
>
{render({
Expand Down
46 changes: 17 additions & 29 deletions packages/tooltips/src/containers/TooltipContainer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,18 @@ describe('TooltipContainer', () => {
expect(findTrigger(wrapper)).toHaveProp('tabIndex', 0);
});

describe('aria-describedby', () => {
it('should reference tooltip id when visible', () => {
findTrigger(wrapper).simulate('focus');
jest.runOnlyPendingTimers();
wrapper.update();
expect(findTrigger(wrapper)).toHaveProp('aria-describedby', 'custom-test-id--tooltip');
});
});

describe('onFocus()', () => {
it('should not display tooltip immediately when focused', () => {
findTrigger(wrapper).simulate('focus');
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', true);
expect(findTooltip(wrapper)).toHaveLength(0);
});

it('should display tooltip after delay when focused', () => {
findTrigger(wrapper).simulate('focus');

jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', false);
expect(findTooltip(wrapper)).toHaveLength(1);
});
});

Expand All @@ -108,22 +99,22 @@ describe('TooltipContainer', () => {

jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', true);
expect(findTooltip(wrapper)).toHaveLength(0);
});
});

describe('onMouseEnter()', () => {
it('should not display tooltip immediately when clicked', () => {
findTrigger(wrapper).simulate('mouseenter');
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', true);
expect(findTooltip(wrapper)).toHaveLength(0);
});

it('should display tooltip after delay when clicked', () => {
findTrigger(wrapper).simulate('mouseenter');

jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', false);
expect(findTooltip(wrapper)).toHaveLength(1);
});

it('should clear open timeout if unmounted during interval', () => {
Expand All @@ -139,11 +130,11 @@ describe('TooltipContainer', () => {
findTrigger(wrapper).simulate('mouseenter');
jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', false);
expect(findTooltip(wrapper)).toHaveLength(1);

findTrigger(wrapper).simulate('mouseleave');
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', false);
expect(findTooltip(wrapper)).toHaveLength(1);
});

it('should hide tooltip aften delay when mouseleaved', () => {
Expand All @@ -152,39 +143,36 @@ describe('TooltipContainer', () => {

jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', true);
expect(findTooltip(wrapper)).toHaveLength(0);
});
});
});

describe('getTooltipProps', () => {
it('should have accessibility ID applied', () => {
findTrigger(wrapper).simulate('mouseover');
it('should not close tooltip if mouseenter during close delay period', () => {
findTrigger(wrapper).simulate('mouseenter');
jest.runOnlyPendingTimers();
wrapper.update();

expect(findTooltip(wrapper)).toHaveProp('id', 'custom-test-id--tooltip');
});

it('should not close tooltip if mouseenter during close delay period', () => {
findTrigger(wrapper).simulate('mouseenter');
findTrigger(wrapper).simulate('mouseleave');
findTooltip(wrapper).simulate('mouseenter');

jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', false);

expect(findTooltip(wrapper)).toHaveLength(1);
});

it('should close tooltip if mouseleaveed', () => {
findTrigger(wrapper).simulate('mouseenter');
findTrigger(wrapper).simulate('mouseleave');
jest.runOnlyPendingTimers();
wrapper.update();

findTooltip(wrapper).simulate('mouseenter');
findTooltip(wrapper).simulate('mouseleave');

jest.runOnlyPendingTimers();
wrapper.update();
expect(findTooltip(wrapper).parent()).toHaveProp('aria-hidden', true);

expect(findTooltip(wrapper)).toHaveLength(0);
});

it('should render tooltip within portal if appendToBody is provided', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/tooltips/src/elements/Tooltip.example.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ All props passed to the root element are proxied into the visible tooltip.
```jsx
const { Button } = require('@zendeskgarden/react-buttons/src');

<Tooltip trigger={<Button>Trigger top placement</Button>}>This an small tooltip</Tooltip>;
<Tooltip trigger={<Button>Trigger top placement</Button>}>This is a small tooltip</Tooltip>;
```

### Multiple Types and Sizes
Expand Down
12 changes: 9 additions & 3 deletions packages/tooltips/src/elements/Tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/

import React, { Component, cloneElement } from 'react';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import { ControlledComponent, IdManager } from '@zendeskgarden/react-selection';

import TooltipContainer from '../containers/TooltipContainer';
import TooltipView from '../views/TooltipView';
Expand All @@ -24,7 +25,7 @@ const TYPE = {
DARK: 'dark'
};

export default class Tooltip extends Component {
export default class Tooltip extends ControlledComponent {
static propTypes = {
/** Appends the tooltip to the body element */
appendToBody: PropTypes.bool,
Expand Down Expand Up @@ -71,10 +72,13 @@ export default class Tooltip extends Component {
type: TYPE.DARK
};

state = {
id: IdManager.generateId()
};

render() {
const {
appendToBody,
id,
trigger,
placement: defaultPlacement,
eventsEnabled,
Expand All @@ -88,6 +92,8 @@ export default class Tooltip extends Component {
...otherProps
} = this.props;

const { id } = this.getControlledState();

return (
<TooltipContainer
appendToBody={appendToBody}
Expand Down
19 changes: 18 additions & 1 deletion packages/tooltips/src/elements/Tooltip.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,41 @@ jest.mock('popper.js', () => {
};
});

jest.useFakeTimers();

describe('Tooltip', () => {
const basicExample = ({ placement, size, type } = {}) => (
<Tooltip placement={placement} size={size} type={type} trigger={<button>Trigger</button>}>
<Tooltip
placement={placement}
size={size}
type={type}
trigger={<button data-test-id="trigger">Trigger</button>}
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
</Tooltip>
);

const findTrigger = providedWrapper => {
return providedWrapper.find('[data-test-id="trigger"]');
};

describe('Types', () => {
it('renders light tooltip if provided', () => {
const wrapper = mountWithTheme(basicExample({ type: 'light' }));

findTrigger(wrapper).simulate('focus');
jest.runOnlyPendingTimers();
wrapper.update();
expect(wrapper.find(LightTooltip)).toHaveLength(1);
});

it('renders dark tooltip if provided', () => {
const wrapper = mountWithTheme(basicExample({ type: 'dark' }));

findTrigger(wrapper).simulate('focus');
jest.runOnlyPendingTimers();
wrapper.update();
expect(wrapper.find(TooltipView)).toHaveLength(1);
});
});
Expand Down