diff --git a/cypress/integration/Popup.spec.ts b/cypress/integration/Popup.spec.ts new file mode 100644 index 0000000000..92ca86bb84 --- /dev/null +++ b/cypress/integration/Popup.spec.ts @@ -0,0 +1,70 @@ +import * as h from '../helpers'; + +function getPopup() { + return cy.findByRole('dialog'); +} + +function getPopupTargetButton() { + return cy.contains('Delete Item'); +} + +function getCloseButton() { + return getPopup().find('[data-close]'); +} + +describe('Popup', () => { + before(() => { + h.stories.visit(); + }); + context(`given the open popup`, () => { + beforeEach(() => { + h.stories.load('Components|Popups/Popup/React', 'Open'); + }); + it('should not have any axe errors', () => { + cy.checkA11y(); + }); + + it('should have a role of dialog', () => { + getPopup().should('have.attr', 'role', 'dialog'); + }); + }); + + context(`given the default popup`, () => { + beforeEach(() => { + h.stories.load('Components|Popups/Popup/React', 'Default'); + }); + context('when the target buttton is clicked', () => { + beforeEach(() => { + getPopupTargetButton().click(); + }); + + it('should open the popup', () => { + getPopup().should('be.visible'); + }); + + it('should not have any axe errors', () => { + cy.checkA11y(); + }); + + context('popup', () => { + it('should have a role of dialog', () => { + getPopup().should('have.attr', 'role', 'dialog'); + }); + + it('should have an aria-labelledby attribute when a heading is provided', () => { + getPopup().should('have.attr', 'aria-labelledby'); + }); + }); + + context('when the close button is clicked', () => { + beforeEach(() => { + getCloseButton().click(); + }); + + it('should close the popup', () => { + getPopup().should('not.visible'); + }); + }); + }); + }); +}); diff --git a/modules/popup/react/lib/Popup.tsx b/modules/popup/react/lib/Popup.tsx index 55b5d94342..3b5c03b95b 100644 --- a/modules/popup/react/lib/Popup.tsx +++ b/modules/popup/react/lib/Popup.tsx @@ -26,7 +26,7 @@ export interface PopupProps extends React.HTMLAttributes { * The origin from which the Popup will animate. * @default {horizontal: 'center', vertical: 'top'} */ - transformOrigin: TransformOrigin; + transformOrigin: TransformOrigin | null; /** * The size of the Popup close button. Accepts `Small` or `Medium`. * @default IconButtonSize.Medium @@ -86,12 +86,17 @@ const Container = styled('div', { maxWidth: `calc(100vw - ${spacing.l})`, }, ({width}) => width && {width}, - ({transformOrigin}) => ({ - animation: popupAnimation(transformOrigin), - animationDuration: '150ms', - animationTimingFunction: 'ease-out', - transformOrigin: `${transformOrigin.vertical} ${transformOrigin.horizontal}`, - }) + ({transformOrigin}) => { + if (transformOrigin === null) { + return {}; + } + return { + animation: popupAnimation(transformOrigin), + animationDuration: '150ms', + animationTimingFunction: 'ease-out', + transformOrigin: `${transformOrigin.vertical} ${transformOrigin.horizontal}`, + }; + } ); const CloseIconContainer = styled('div')>( @@ -152,7 +157,6 @@ export default class Popup extends React.Component { size={closeIconSize} onClick={handleClose} icon={xIcon} - title={closeLabel} aria-label={closeLabel} /> diff --git a/modules/popup/react/package.json b/modules/popup/react/package.json index 521081d8cf..38cda6e344 100644 --- a/modules/popup/react/package.json +++ b/modules/popup/react/package.json @@ -35,6 +35,9 @@ "workday", "popup" ], + "devDependencies": { + "@workday/canvas-kit-labs-react-core": "^3.6.0" + }, "dependencies": { "@emotion/core": "^10.0.28", "@emotion/is-prop-valid": "^0.8.2", diff --git a/modules/popup/react/spec/Popup.spec.tsx b/modules/popup/react/spec/Popup.spec.tsx index 987e9153dd..b17ee72669 100644 --- a/modules/popup/react/spec/Popup.spec.tsx +++ b/modules/popup/react/spec/Popup.spec.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; +import {render, fireEvent} from '@testing-library/react'; import Popup from '../lib/Popup'; -import {mount} from 'enzyme'; describe('Popup', () => { const cb = jest.fn(); @@ -8,18 +8,81 @@ describe('Popup', () => { cb.mockReset(); }); - test('should call a callback function', () => { - const component = mount(Hello World); - const popup = component.find('button'); - popup.simulate('click'); - expect(cb.mock.calls.length).toBe(1); - component.unmount(); + describe('when rendered with a heading', () => { + it('should render popup with heading text', () => { + const headingText = 'Delete item'; + const {getByRole} = render( + +
Are you sure you'd like to delete the item titled 'My Item'?
+ + +
+ ); + expect(getByRole('dialog').querySelector('h3')).toContainHTML(headingText); + }); + }); + + describe('when rendered with a handleClose', () => { + it('should render popup with a close button', () => { + const closeButtonAriaLabel = 'close'; + const {getByRole} = render( + +
Are you sure you'd like to delete the item titled 'My Item'?
+ + +
+ ); + getByRole('dialog').querySelector('[data-close]'); + expect(getByRole('dialog').querySelector('[data-close]')).toHaveAttribute( + 'aria-label', + closeButtonAriaLabel + ); + }); + + it('should call the handleClose callback when clicked', () => { + const closeButtonAriaLabel = 'close'; + const {getByRole} = render( + +
Are you sure you'd like to delete the item titled 'My Item'?
+ + +
+ ); + + fireEvent.click(getByRole('dialog').querySelector('[data-close]')); + + expect(cb).toHaveBeenCalledTimes(1); + }); }); - test('Popup should spread extra props', () => { - const component = mount(); - const container = component.at(0).getDOMNode(); - expect(container.getAttribute('data-propspread')).toBe('test'); - component.unmount(); + describe('when rendered with extra props', () => { + it('should render popup with extra props', () => { + const {getByRole} = render( + +
Are you sure you'd like to delete the item titled 'My Item'?
+ + +
+ ); + + expect(getByRole('dialog')).toHaveAttribute('data-propspread', 'test'); + }); + }); + + describe('when rendered with a popup ref', () => { + it('should set the ref to the div element', () => { + const ref = React.createRef(); + + render( + +
Are you sure you'd like to delete the item titled 'My Item'?
+ + +
+ ); + + expect(ref.current).not.toBeNull(); + expect(ref.current).toHaveAttribute('role', 'dialog'); + }); }); }); diff --git a/modules/popup/react/stories/stories_VisualTesting.tsx b/modules/popup/react/stories/stories_VisualTesting.tsx new file mode 100644 index 0000000000..10087c5cad --- /dev/null +++ b/modules/popup/react/stories/stories_VisualTesting.tsx @@ -0,0 +1,102 @@ +/// +/** @jsx jsx */ +import {jsx} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import {StaticStates} from '@workday/canvas-kit-labs-react-core'; +import {action} from '@storybook/addon-actions'; +import {ComponentStatesTable} from '../../../../utils/storybook'; +import {Popup} from '../index'; +import {PopupPadding} from '../lib/Popup'; +import {depth} from '@workday/canvas-kit-react-core'; + +storiesOf('Components|Popups/Popup/React/Visual Testing', module) + .addParameters({ + component: Popup, + chromatic: { + disable: false, + }, + }) + .add('States', () => ( + + + {props => ( + + Your workbook was successfully processed. + + )} + + + )); diff --git a/modules/toast/react/spec/Toast.spec.tsx b/modules/toast/react/spec/Toast.spec.tsx index 7c3086372a..4ccd616b38 100644 --- a/modules/toast/react/spec/Toast.spec.tsx +++ b/modules/toast/react/spec/Toast.spec.tsx @@ -41,8 +41,8 @@ describe('Toast', () => { {toastMessage} ); - const closeIcon = container.querySelector('[data-close="close"]'); /*? */ - fireEvent.click(closeIcon); /*? */ + const closeIcon = container.querySelector('[data-close="close"]'); + fireEvent.click(closeIcon); expect(cb).toHaveBeenCalledTimes(1); }); });