Skip to content

Commit

Permalink
test(tooltip): Add tooltip specifications
Browse files Browse the repository at this point in the history
* Add unit tests
* Add Cypress specifications
* Add visual tests
* Upgrade Popper to v2 and use canvas-kit Popper component instead of materia-ui's

Tooltip portion of Workday#286
  • Loading branch information
NicholasBoll committed Mar 20, 2020
1 parent 2e6db84 commit 83385ff
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 278 deletions.
75 changes: 75 additions & 0 deletions cypress/integration/Tooltip.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as h from '../helpers';

describe('Tooltip', () => {
before(() => {
h.stories.visit();
});

context('given Tooltip Default is rendered', () => {
beforeEach(() => {
h.stories.load('Tooltip', 'Default');
});

it('should not have any axe errors', () => {
cy.checkA11y();
});

it('should have an aria-label of "Close"', () => {
cy.get('button').should('have.attr', 'aria-label', 'Close');
});

context('when close icon is hovered', () => {
beforeEach(() => {
cy.get('button').trigger('mouseover');
});

it('should open the tooltip', () => {
cy.get('[role=tooltip]').should('be.visible');
});

it('should not have any axe errors', () => {
cy.checkA11y();
});

it('the button should be aria-describedby the tooltip', () => {
cy.get('[role=tooltip]', {log: false}).should($tooltip => {
const id = $tooltip.attr('id');
expect(Cypress.$('button')).to.have.attr('aria-describedby', id);
expect($tooltip).to.have.attr('id', id);
});
});
});

context('when close icon gains focus', () => {
beforeEach(() => {
cy.get('button').focus();
});

it('should open the tooltip', () => {
cy.get('[role=tooltip]').should('be.visible');
});

context('then the close icon loses focus', () => {
beforeEach(() => {
cy.get('button').blur();
});

it('should close the tooltip', () => {
cy.get('[role=tooltip]').should('not.be.visible');
});
});

context('then Escape key is pressed', () => {
beforeEach(() => {
cy.get('button').trigger('keydown', {
key: 'Escape',
});
});

it('should not remove focus from the close icon button', () => {
cy.get('button').should('have.focus');
});
});
});
});
});
2 changes: 1 addition & 1 deletion modules/common/react/lib/Popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface PopperProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* The element that contains the portal children when `portal` is true.
*/
containerElement?: Element;
containerElement?: Element | null;
/**
* If true, set the Popper to the open state.
* @default true
Expand Down
2 changes: 1 addition & 1 deletion modules/toast/react/stories/stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const toastData = {
};

class ToastWrapper extends React.Component<{state?: string}> {
public render() {
render() {
let toastMeta;

switch (this.props.state) {
Expand Down
8 changes: 3 additions & 5 deletions modules/tooltip/react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

A Tooltip component that renders information/text when the user hovers over an element.

Note: This Tooltip does not include a positioning engine. In our example we use Material UIs popper
component to wrap our Tooltip component and position it, which is a wrapper to Popper.js. For
reference: https://material-ui.com/api/popper/
Note: This Tooltip does not include a positioning engine. In our example we use our Popper

## Installation

Expand All @@ -22,7 +20,7 @@ yarn add @workday/canvas-kit-react-tooltip

```tsx
import * as React from 'react';
import Popper from '@material-ui/core/Popper';
import {Popper} from '@workday/canvas-kit-react-common';
import Tooltip from '@workday/canvas-kit-react-tooltip';

class TooltipExample extends React.Component<{}, TooltipExampleState> {
Expand Down Expand Up @@ -52,7 +50,7 @@ class TooltipExample extends React.Component<{}, TooltipExampleState> {
>
Hover Over Me
</div>
<Popper open={open} anchorEl={this.state.anchorEl} placement={'bottom'}>
<Popper open={open} anchorElement={this.state.anchorEl} placement={'bottom'}>
<Tooltip id={'tooltip-id'}>Close</Tooltip>
</Popper>
</div>
Expand Down
52 changes: 45 additions & 7 deletions modules/tooltip/react/lib/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import styled from '@emotion/styled';
import {borderRadius, colors, spacing, type} from '@workday/canvas-kit-react-core';
import {TransformOrigin, getTranslateFromOrigin} from '@workday/canvas-kit-react-common';
import {keyframes} from '@emotion/core';
import uuid from 'uuid/v4';

export interface TooltipProps {
/**
Expand All @@ -11,7 +12,8 @@ export interface TooltipProps {
*/
transformOrigin: TransformOrigin;
/**
* The unique id of the Tooltip.
* HTML id of the tooltip container - useful for accessibility.
* Should link the tooltip container to a `aria-describedby` on the target
*/
id?: string;
}
Expand Down Expand Up @@ -47,12 +49,12 @@ const TooltipContainer = styled('div')<TooltipProps>(
},
},
({transformOrigin}) => ({
animation: tooltipAnimation(transformOrigin),
animationDuration: '150ms',
animationTimingFunction: 'ease-out',
transformOrigin: transformOrigin
? `${transformOrigin.vertical} ${transformOrigin.horizontal}`
: 'top center',
// animation: tooltipAnimation(transformOrigin),
// animationDuration: '150ms',
// animationTimingFunction: 'ease-out',
// transformOrigin: transformOrigin
// ? `${transformOrigin.vertical} ${transformOrigin.horizontal}`
// : 'top center',
})
);

Expand All @@ -72,3 +74,39 @@ export default class Tooltip extends React.Component<TooltipProps, {}> {
);
}
}

/**
* Convenience hook for creating components with tooltips
*/
export function useTooltip() {
const [isOpen, setOpen] = React.useState(false);
const [ref, setRef] = React.useState<null | HTMLElement>(null);
const id = React.useRef<string>();
const onClose = () => {
setOpen(false);
};
const onOpen = (event: React.SyntheticEvent<HTMLElement>) => {
setRef(event.currentTarget);
if (!id.current) {
id.current = uuid();
}
setOpen(true);
};

return {
targetProps: {
'aria-describedby': id.current,
onMouseEnter: onOpen,
onMouseLeave: onClose,
onFocus: onOpen,
onBlur: onClose,
},
popperProps: {
open: isOpen,
anchorElement: ref,
},
tooltipProps: {
id: id.current,
},
};
}
1 change: 0 additions & 1 deletion modules/tooltip/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"@workday/canvas-kit-react-core": "^3.5.0"
},
"devDependencies": {
"@material-ui/core": "^3.9.2",
"@workday/canvas-system-icons-web": "^1.0.20"
}
}
45 changes: 38 additions & 7 deletions modules/tooltip/react/spec/Tooltip.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
/// <reference types="@testing-library/jest-dom/extend-expect" />

import * as React from 'react';
import {mount} from 'enzyme';
import Tooltip from '../lib/Tooltip';
import {render, cleanup, fireEvent} from '@testing-library/react';

import Tooltip, {useTooltip} from '../lib/Tooltip';

const TooltipWithHook = () => {
const {targetProps, tooltipProps} = useTooltip();

return (
<>
<button {...targetProps}>Hover</button>
<Tooltip {...tooltipProps}>Tooltip Content</Tooltip>
</>
);
};

describe('Tooltip', () => {
test('Tooltip should spread extra props', () => {
const component = mount(<Tooltip data-propspread="test" />);
const container = component.at(0).getDOMNode();
expect(container.getAttribute('data-propspread')).toBe('test');
component.unmount();
afterEach(cleanup);

it('should spread extra props on the element', async () => {
const {container} = render(<Tooltip data-propspread="test" />);
const tooltip = container.firstElementChild;

expect(tooltip).toHaveAttribute('role', 'tooltip');
});

describe('useToolip', () => {
it('should add aria attributes to correlate the target and the tooltip', async () => {
const rendered = render(<TooltipWithHook />);

const target = await rendered.findByText('Hover');
const tooltip = await rendered.findByRole('tooltip');

await fireEvent.mouseOver(target); // assign the ID to the tooltip

expect(tooltip).toHaveAttribute('id');
const id = tooltip.getAttribute('id');
expect(target).toHaveAttribute('aria-describedby', id);
});
});
});
Loading

0 comments on commit 83385ff

Please sign in to comment.