diff --git a/packages/react-core/src/components/Alert/AlertActionCloseButton.tsx b/packages/react-core/src/components/Alert/AlertActionCloseButton.tsx index 21d54532724..fefac110991 100644 --- a/packages/react-core/src/components/Alert/AlertActionCloseButton.tsx +++ b/packages/react-core/src/components/Alert/AlertActionCloseButton.tsx @@ -15,8 +15,7 @@ export interface AlertActionCloseButtonProps extends ButtonProps { } export const AlertActionCloseButton: React.FunctionComponent = ({ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - className = '', + className, onClose = () => undefined as any, 'aria-label': ariaLabel = '', variantLabel, @@ -28,6 +27,7 @@ export const AlertActionCloseButton: React.FunctionComponent diff --git a/packages/react-core/src/components/Alert/AlertToggleExpandButton.tsx b/packages/react-core/src/components/Alert/AlertToggleExpandButton.tsx index 842d0fdfb27..3dd7b96b1cd 100644 --- a/packages/react-core/src/components/Alert/AlertToggleExpandButton.tsx +++ b/packages/react-core/src/components/Alert/AlertToggleExpandButton.tsx @@ -17,10 +17,10 @@ export interface AlertToggleExpandButtonProps extends ButtonProps { } export const AlertToggleExpandButton: React.FunctionComponent = ({ - 'aria-label': ariaLabel, + 'aria-label': ariaLabel = '', variantLabel, onToggleExpand, - isExpanded, + isExpanded = false, ...props }: AlertToggleExpandButtonProps) => { const { title, variantLabel: alertVariantLabel } = React.useContext(AlertContext); diff --git a/packages/react-core/src/components/Alert/__tests__/Alert.test.tsx b/packages/react-core/src/components/Alert/__tests__/Alert.test.tsx index 21fff0c47d1..f482b53062e 100644 --- a/packages/react-core/src/components/Alert/__tests__/Alert.test.tsx +++ b/packages/react-core/src/components/Alert/__tests__/Alert.test.tsx @@ -1,210 +1,724 @@ import * as React from 'react'; - import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { act } from 'react-dom/test-utils'; import { Alert, AlertVariant } from '../Alert'; -import { AlertActionLink } from '../AlertActionLink'; -import { AlertActionCloseButton } from '../AlertActionCloseButton'; -import { UsersIcon } from '@patternfly/react-icons'; - -describe('Alert', () => { - test('default Alert variant is default', () => { - render(Alert testing); - expect(screen.getByText('this is a test')).toHaveClass('pf-c-alert__title'); - }); - - Object.values(AlertVariant).forEach(variant => { - describe(`Alert - ${variant}`, () => { - test('Description', () => { - const { asFragment } = render( - - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('Title', () => { - const { asFragment } = render( - - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('Heading level', () => { - const { asFragment } = render( - - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('Action Link', () => { - const { asFragment } = render( - test]} title=""> - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('Action Close Button', async () => { - const onClose = jest.fn(); - const user = userEvent.setup(); - - render( - } - title={`Sample ${variant} alert`} - > - Some alert - - ); - - await user.click(screen.getByLabelText('Close')); - expect(onClose).toHaveBeenCalled(); - }); - - test('Action and Title', () => { - const { asFragment } = render( - test]} - title="Some title" - > - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('Custom aria label', () => { - const { asFragment } = render( - test]} - title="Some title" - > - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('inline variation', () => { - const { asFragment } = render( - - Some alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('expandable variation', () => { - const { asFragment } = render( - -

Success alert description. This should tell the user more information about the alert.

-
- ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('expandable variation description hidden', () => { - const description = 'Success alert description.'; - - render( - -

{description}

-
- ); - - expect(screen.queryByText(description)).toBeNull(); - }); - - test('Toast alerts match snapsnot', () => { - const { asFragment } = render( - - Some toast alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - test('Toast alerts contain default live region', () => { - const ariaLabel = `${variant} toast alert`; - - render( - - Some toast alert - - ); - - expect(screen.getByLabelText(ariaLabel)).toHaveAttribute('aria-live', 'polite'); - }); - - test('Toast alert live regions are not atomic', () => { - const ariaLabel = `${variant} toast alert`; - - render( - - Some toast alert - - ); - - expect(screen.getByLabelText(ariaLabel)).toHaveAttribute('aria-atomic', 'false'); - }); - - test('Non-toast alerts can have custom live region settings', () => { - const ariaLabel = `${variant} toast alert`; - - render( - - Some noisy alert - - ); - const alert = screen.getByLabelText(ariaLabel); - - expect(alert).toHaveAttribute('aria-live', 'assertive'); - expect(alert).toHaveAttribute('aria-relevant', 'all'); - expect(alert).toHaveAttribute('aria-atomic', 'true'); - }); - - test('Custom icon', () => { - const { asFragment } = render( - } - variant={variant} - aria-label={`${variant} custom icon alert`} - title="custom icon alert title" - > - Some noisy alert - - ); - expect(asFragment()).toMatchSnapshot(); - }); - }); - }); - - test('Alert truncate title', () => { +import { AlertContext } from '../AlertContext'; +import { capitalize } from '../../../helpers'; + +jest.mock('../AlertToggleExpandButton', () => ({ + AlertToggleExpandButton: ({ isExpanded, onToggleExpand, ...props }) => ( + + ) +})); + +jest.mock('../AlertIcon', () => ({ + AlertIcon: ({ variant, className, customIcon, ...props }) => ( +
+

custom icon: {customIcon}

+

variant: {variant}

+
+ ) +})); + +test('Renders without children', () => { + render( +
+ +
+ ); + + expect(screen.getByTestId('container').firstChild).toBeVisible(); +}); + +test('Renders with class pf-c-alert on the containing div', () => { + render( + + Some alert + + ); + expect(screen.getByTestId('Alert-test-id')).toHaveClass('pf-c-alert'); +}); + +test('Renders with class pf-c-alert__title on the div containing the title', () => { + render(Some alert); + expect(screen.getByRole('heading', { name: 'Default alert: Some title' })).toHaveClass('pf-c-alert__title'); +}); + +test('Renders with a default aria label of Default Alert', () => { + render( + + Some alert + + ); + expect(screen.getByTestId('Alert-test-id')).toHaveAccessibleName('Default Alert'); +}); + +['success', 'danger', 'warning', 'info'].forEach(variant => { + test(`Does not render with class pf-m-${variant} by default`, () => { + render( + + Some alert + + ); + expect(screen.getByTestId('Alert-test-id')).not.toHaveClass(`pf-m-${variant}`); + }); + + test(`Renders with class pf-m-${variant} when variant = ${variant}`, () => { + render( + + Some alert + + ); + expect(screen.getByTestId('Alert-test-id')).toHaveClass(`pf-m-${variant}`); + }); + + test(`Renders with aria label ${capitalize(variant)} Alert when variant = ${variant}`, () => { + render( + + Some alert + + ); + expect(screen.getByTestId('Alert-test-id')).toHaveAccessibleName(`${capitalize(variant)} Alert`); + }); + + test(`Renders the title with an accessible name of '${capitalize( + variant + )} alert: Some title' when variant = ${variant}`, () => { + render( + + Some alert + + ); + + expect(screen.getByRole('heading')).toHaveAccessibleName(`${capitalize(variant)} alert: Some title`); + }); + + test(`Passes AlertToggleExpandButton an aria label of '${capitalize( + variant + )} alert details' when variant = ${variant} and it is expandable`, () => { + render( + + Some alert + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName(`${capitalize(variant)} alert details`); + }); + + test(`Provides a variantLabel of '${capitalize(variant)} alert:' in a context when variant = ${variant}`, () => { + const TestComponent: React.FunctionComponent = () => { + const context = React.useContext(AlertContext); + + return ( +

+ {context.title} +

+ ); + }; + render( - - Alert testing + } + > + Some alert ); - expect(screen.getByText('this is a test')).toHaveClass('pf-m-truncate'); + expect(screen.getByTestId('Test-component')).toHaveAccessibleName(`${capitalize(variant)} alert:`); + }); +}); + +test('Does not render with class pf-m-inline by default', () => { + render(Some alert); + expect(screen.getByLabelText('Default Alert')).not.toHaveClass('pf-m-inline'); +}); + +test('Renders with class pf-m-inline when isInline = true', () => { + render( + + Some alert + + ); + expect(screen.getByLabelText('Default Alert')).toHaveClass('pf-m-inline'); +}); + +test('Does not render with class pf-m-plain by default', () => { + render(Some alert); + expect(screen.getByLabelText('Default Alert')).not.toHaveClass('pf-m-plain'); +}); + +test('Renders with class pf-m-plain when isPlain = true', () => { + render( + + Some alert + + ); + expect(screen.getByLabelText('Default Alert')).toHaveClass('pf-m-plain'); +}); + +test('Renders the title', () => { + render(Some alert); + + expect(screen.getByText('Some title')).toBeVisible(); +}); + +test('Renders the title as an h4 by default', () => { + render(Some alert); + + expect(screen.getByRole('heading', { level: 4, name: 'Default alert: Some title' })).toBeVisible(); +}); + +test('Renders the title as other heading levels when one is passed using titleHeadingLevel', () => { + render( + + Some alert + + ); + + expect(screen.getByRole('heading', { level: 1, name: 'Default alert: Some title' })).toBeVisible(); +}); + +test('Renders the element passed via the actionClose prop', () => { + render( + Action close}> + Some alert + + ); + + expect(screen.getByRole('button', { name: 'Action close' })).toBeVisible(); +}); + +test('Renders the actionClose element inside pf-c-alert__action', () => { + render( + + Some alert + + ); + + expect(screen.getByText('Action close')).toHaveClass('pf-c-alert__action'); +}); + +test('Provides the actionClose element access to the title via a context', () => { + const TestComponent: React.FunctionComponent = () => { + const context = React.useContext(AlertContext); + + return ( +

+ {context.title} +

+ ); + }; + + render( + }> + Some alert + + ); + + expect(screen.getByTestId('Test-component')).toHaveTextContent('Some title'); +}); + +test('Renders the element passed via the actionLinks prop', () => { + render( + Action link}> + Some alert + + ); + + expect(screen.getByRole('button', { name: 'Action link' })).toBeVisible(); +}); + +test('Renders the actionLinks element inside pf-c-alert__action-group', () => { + render( + + Some alert + + ); + + expect(screen.getByText('Action link')).toHaveClass('pf-c-alert__action-group'); +}); + +test('Renders children', () => { + render(Some alert); + + expect(screen.getByText('Some alert')).toBeVisible(); +}); + +test('Renders children inside pf-c-alert__description', () => { + render(Some alert); + + expect(screen.getByText('Some alert')).toHaveClass('pf-c-alert__description'); +}); + +test('Renders with the aria label passed via prop', () => { + render( + + Some alert + + ); + + expect(screen.getByTestId('Alert-test-id')).toHaveAccessibleName('Test label'); +}); + +test('Renders with the variantLabel passed via prop', () => { + const TestComponent: React.FunctionComponent = () => { + const context = React.useContext(AlertContext); + + return ( +

+ {context.title} +

+ ); + }; + + render( + }> + Some alert + + ); + + expect(screen.getByTestId('Test-component')).toHaveAccessibleName('Test label'); +}); + +test('Renders without aria-live and aria-atomic attributes by default', () => { + render( + + Some alert + + ); + + const alertContainer = screen.getByTestId('Alert-test-id'); + + expect(alertContainer).not.toHaveAttribute('aria-live'); + expect(alertContainer).not.toHaveAttribute('aria-atomic'); +}); + +test('Has an aria-live value of polite and aria-atomic value of false when isLiveRegion = true', () => { + render( + + Some alert + + ); + + const alertContainer = screen.getByTestId('Alert-test-id'); + + expect(alertContainer).toHaveAttribute('aria-live', 'polite'); + expect(alertContainer).toHaveAttribute('aria-atomic', 'false'); +}); + +test('Renders with no timeout by default', () => { + jest.useFakeTimers(); + + render(Some alert); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + expect(screen.getByText('Some alert')).toBeVisible(); + jest.useRealTimers(); +}); + +test('Removes the alert after 8000ms when timeout = true', () => { + jest.useFakeTimers(); + + render( + + Some alert + + ); + + act(() => { + jest.advanceTimersByTime(7999); + }); + + expect(screen.getByText('Some alert')).toBeVisible(); + + act(() => { + jest.advanceTimersByTime(1); + }); + + expect(screen.queryByText('Some alert')).not.toBeInTheDocument(); + jest.useRealTimers(); +}); + +test('Removes the alert after a custom time when timeout is passed with a number', () => { + jest.useFakeTimers(); + + render( + + Some alert + + ); + + act(() => { + jest.advanceTimersByTime(1999); + }); + + expect(screen.getByText('Some alert')).toBeVisible(); + + act(() => { + jest.advanceTimersByTime(1); }); + + expect(screen.queryByText('Some alert')).not.toBeInTheDocument(); + jest.useRealTimers(); +}); + +test('Does not remove the alert on timeout if the user is focused on the alert', async () => { + const user = userEvent.setup({ + advanceTimers: delay => jest.advanceTimersByTime(delay) + }); + jest.useFakeTimers(); + + render( + + Some alert + + ); + + const alert = screen.getByText('Some alert'); + + await user.click(alert); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + expect(screen.getByText('Some alert')).toBeVisible(); + jest.useRealTimers(); +}); + +test('Does not remove the alert on timeout if the user is hovered over the alert', async () => { + const user = userEvent.setup({ + advanceTimers: delay => jest.advanceTimersByTime(delay) + }); + jest.useFakeTimers(); + + render( + + Some alert + + ); + + const alert = screen.getByText('Some alert'); + + await user.hover(alert); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + expect(alert).toBeVisible(); + jest.useRealTimers(); +}); + +test('Removes the alert after the user removes focus from the alert and 3000ms have passed', async () => { + const user = userEvent.setup({ + advanceTimers: delay => jest.advanceTimersByTime(delay) + }); + jest.useFakeTimers(); + + render( +
+ + + Some alert + +
+ ); + + const alert = screen.getByText('Some alert'); + + await user.click(alert); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + await user.click(screen.getByRole('textbox')); + + act(() => { + jest.advanceTimersByTime(3000); + }); + + expect(screen.queryByText('Some alert')).not.toBeInTheDocument(); + jest.useRealTimers(); +}); + +test('Removes the alert after the user removes hover from the alert and 3000ms have passed', async () => { + const user = userEvent.setup({ + advanceTimers: delay => jest.advanceTimersByTime(delay) + }); + jest.useFakeTimers(); + + render( +
+ + + Some alert + +
+ ); + + const alert = screen.getByText('Some alert'); + + await user.hover(alert); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + await user.hover(screen.getByRole('textbox')); + + act(() => { + jest.advanceTimersByTime(3000); + }); + + expect(screen.queryByText('Some alert')).not.toBeInTheDocument(); + jest.useRealTimers(); +}); + +test('Removes the alert after the user removes hover from the alert and timeoutAnimation time has passed', async () => { + const user = userEvent.setup({ + advanceTimers: delay => jest.advanceTimersByTime(delay) + }); + jest.useFakeTimers(); + + render( +
+ + + Some alert + +
+ ); + + const alert = screen.getByText('Some alert'); + + await user.hover(alert); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + await user.hover(screen.getByRole('textbox')); + + act(() => { + jest.advanceTimersByTime(1000); + }); + + expect(screen.queryByText('Some alert')).not.toBeInTheDocument(); + jest.useRealTimers(); +}); + +test('Does not call the onTimeout callback before the timeout period has expired', () => { + jest.useFakeTimers(); + const onTimeoutMock = jest.fn(); + + render( + + Some alert + + ); + + act(() => { + jest.advanceTimersByTime(7999); + }); + + expect(onTimeoutMock).not.toHaveBeenCalled(); + jest.useRealTimers(); +}); + +test('Calls the onTimeout callback after the timeout period has expired', () => { + jest.useFakeTimers(); + const onTimeoutMock = jest.fn(); + + render( + + Some alert + + ); + + act(() => { + jest.advanceTimersByTime(8000); + }); + + expect(onTimeoutMock).toHaveBeenCalledTimes(1); + jest.useRealTimers(); +}); + +test('Renders titles without truncation styling by default', () => { + render(Some alert); + + const title = screen.getByRole('heading'); + + expect(title).not.toHaveClass('pf-m-truncate'); + expect(title).not.toHaveAttribute('style'); +}); + +test('Renders titles with the expected truncation styling when truncateTitle is a value', () => { + render( + + Some alert + + ); + + const title = screen.getByRole('heading'); + + expect(title).toHaveClass('pf-m-truncate'); + expect(title).toHaveAttribute('style', '--pf-c-alert__title--max-lines: 3;'); +}); + +test('Passes customIcon value to AlertIcon', () => { + render( + + Some alert + + ); + + expect(screen.getByText('custom icon: custom-icon-test')).toBeVisible(); +}); + +test('Does not render with class pf-m-expandable by default', () => { + render(Some alert); + expect(screen.getByLabelText('Default Alert')).not.toHaveClass('pf-m-expandable'); +}); + +test('Renders with class pf-m-expandable when isExpandable = true', () => { + render( + + Some alert + + ); + expect(screen.getByLabelText('Default Alert')).toHaveClass('pf-m-expandable'); +}); + +test('Renders AlertToggleExpandButton inside pf-c-alert__toggle', () => { + render( + + Some alert + + ); + + expect(screen.getByRole('button').parentElement).toHaveClass('pf-c-alert__toggle'); +}); + +test('Does not render with class pf-m-expanded when AlertToggleExpandButton has not been clicked', () => { + render( + + Some alert + + ); + + expect(screen.getByLabelText('Default Alert')).not.toHaveClass('pf-m-expanded'); +}); + +test('Renders with class pf-m-expanded once the AlertToggleExpandButton is clicked', async () => { + const user = userEvent.setup(); + + render( + + Some alert + + ); + + await user.click(screen.getByRole('button')); + + expect(screen.getByLabelText('Default Alert')).toHaveClass('pf-m-expanded'); +}); + +test('Does not render children when isExpandable = true and AlertToggleExpandButton has not been clicked', () => { + render( + + Some alert + + ); + + expect(screen.queryByText('Some alert')).not.toBeInTheDocument(); +}); + +test('Renders children when isExpandable = true and AlertToggleExpandButton has been clicked', async () => { + const user = userEvent.setup(); + + render( + + Some alert + + ); + + await user.click(screen.getByRole('button')); + + expect(screen.getByText('Some alert')).toBeVisible(); +}); + +test('Passes AlertToggleExpandButton isExpanded = false when AlertToggleExpandButton has not been clicked', () => { + render( + + Some alert + + ); + + expect(screen.getByText('isExpanded: false')).toBeVisible(); +}); + +test('Passes AlertToggleExpandButton isExpanded = true once the AlertToggleExpandButton is clicked', async () => { + const user = userEvent.setup(); + + render( + + Some alert + + ); + + await user.click(screen.getByRole('button')); + + expect(screen.getByText('isExpanded: true')).toBeVisible(); +}); + +test('Passes AlertToggleExpandButton the aria label provided via toggleAriaLabel', () => { + render( + + Some alert + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Test label'); +}); + +test('Passes the id prop to the top level returned element', () => { + render( + + Some alert + + ); + + expect(screen.getByLabelText('Test label')).toHaveAttribute('id', 'test-id'); +}); + +test('Renders with inherited element props spread to the component', () => { + render( + <> + +

Described by text

+ + ); + + expect(screen.getByLabelText('Test label')).toHaveAccessibleDescription('Described by text'); +}); + +test('Matches snapshot', () => { + const { asFragment } = render( + + Some toast alert + + ); + expect(asFragment()).toMatchSnapshot(); }); diff --git a/packages/react-core/src/components/Alert/__tests__/AlertActionCloseButton.test.tsx b/packages/react-core/src/components/Alert/__tests__/AlertActionCloseButton.test.tsx new file mode 100644 index 00000000000..be395f8e64a --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/AlertActionCloseButton.test.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { AlertActionCloseButton } from '../AlertActionCloseButton'; +import { AlertContext } from '../AlertContext'; + +jest.mock('../../Button', () => ({ + Button: ({ children, variant, isInline, onClick, ...props }) => ( + <> + +

{`variant: ${variant}`}

+

Test label

+ + ), + ButtonVariant: { plain: 'plain' } +})); + +test('Renders without children', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toBeVisible(); +}); + +test('Renders with custom class names provided via prop', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveClass('custom-class'); +}); + +test('Renders with inherited element props spread to the component', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Test label'); +}); + +test('Renders a Button with variant: ButtonVariant.plain', () => { + render( + + + + ); + + expect(screen.getByText('variant: plain')).toBeVisible(); +}); + +test('Does not call the callback provided via onClose when it is not clicked', () => { + const onCloseMock = jest.fn(); + + render( + + + + ); + + expect(onCloseMock).not.toHaveBeenCalled(); +}); + +test('Calls the callback provided via onClose when clicked', async () => { + const onCloseMock = jest.fn(); + const user = userEvent.setup(); + + render( + + + + ); + + await user.click(screen.getByRole('button')); + + expect(onCloseMock).toHaveBeenCalledTimes(1); +}); + +test('Renders with an aria label composed with the title and variantLabel provided via a context by default', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Close variantLabel alert: title'); +}); + +test('Renders with an aria label composed with the title provided via a context and variantLabel provided via prop', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Close variant label prop alert: title'); +}); + +test('Renders with the aria label provided via prop when one is provided', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Aria label prop'); + }); + +test('Matches the snapshot', () => { + const { asFragment } = render( + + + + ); + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Alert/__tests__/AlertActionLink.test.tsx b/packages/react-core/src/components/Alert/__tests__/AlertActionLink.test.tsx new file mode 100644 index 00000000000..9fccb898171 --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/AlertActionLink.test.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { AlertActionLink } from '../AlertActionLink'; + +jest.mock('../../Button', () => ({ + Button: ({ children, variant, isInline, ...props }) => ( + <> + +

{`variant: ${variant}`}

+

{`isInline: ${isInline}`}

+ + ), + ButtonVariant: { link: 'link' } +})); + +test('Renders without children', () => { + render( +
+ +
+ ); + + expect(screen.getByTestId('container').firstChild).toBeVisible(); +}); + +test('Renders children', () => { + render(Test); + + expect(screen.getByRole('button')).toBeVisible(); +}); + +test('Renders with custom class names provided via prop', () => { + render(Test); + + expect(screen.getByRole('button')).toHaveClass('custom-class'); +}); + +test('Renders with inherited element props spread to the component', () => { + render(Test); + + expect(screen.getByRole('button')).toHaveAccessibleName('Test label'); +}); + +test('Renders a Button with variant: ButtonVariant.link', () => { + render(Test); + + expect(screen.getByText('variant: link')).toBeVisible(); +}); + +test('Renders a Button with isInline: true', () => { + render(Test); + + expect(screen.getByText('isInline: true')).toBeVisible(); +}); + +test('Matches the snapshot', () => { + const { asFragment } = render(Test); + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Alert/__tests__/AlertIcon.test.tsx b/packages/react-core/src/components/Alert/__tests__/AlertIcon.test.tsx new file mode 100644 index 00000000000..1b4d94b0fc0 --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/AlertIcon.test.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { AlertIcon } from '../AlertIcon'; + +jest.mock('@patternfly/react-icons/dist/esm/icons/check-circle-icon', () => () => 'Check circle icon mock'); +jest.mock('@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon', () => () => 'Exclamation circle icon mock'); +jest.mock('@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon', () => () => + 'Exclamation triangle icon mock' +); +jest.mock('@patternfly/react-icons/dist/esm/icons/info-circle-icon', () => () => 'Info circle icon mock'); +jest.mock('@patternfly/react-icons/dist/esm/icons/bell-icon', () => () => 'Bell icon mock'); + +test('Renders without children', () => { + render( +
+ +
+ ); + + expect(screen.getByTestId('container').firstChild).toBeVisible(); +}); + +test('Renders with the bell icon when variant = default', () => { + render(); + + expect(screen.getByText('Bell icon mock')).toBeVisible(); +}); + +test('Renders with the check circle icon when variant = success', () => { + render(); + + expect(screen.getByText('Check circle icon mock')).toBeVisible(); +}); + +test('Renders with the exclamation circle icon when variant = danger', () => { + render(); + + expect(screen.getByText('Exclamation circle icon mock')).toBeVisible(); +}); + +test('Renders with the exclamation triangle icon when variant = warning', () => { + render(); + + expect(screen.getByText('Exclamation triangle icon mock')).toBeVisible(); +}); + +test('Renders with the info circle icon when variant = info', () => { + render(); + + expect(screen.getByText('Info circle icon mock')).toBeVisible(); +}); + +test('Renders with custom class names provided via prop', () => { + render(); + + expect(screen.getByText('Bell icon mock')).toHaveClass('test-class'); +}); + +test('Renders with the passed custom icon when one is passed rather than the icon determined by the passed variant', () => { + render(); + + expect(screen.queryByText('Bell icon mock')).not.toBeInTheDocument(); + expect(screen.getByText('Custom icon')).toBeVisible(); +}); + +test('Renders the icon inside class pf-c-alert__icon', () => { + render(); + + expect(screen.getByText('Bell icon mock')).toHaveClass('pf-c-alert__icon'); +}); + +test('Renders with inherited element props spread to the component', () => { + render(); + + expect(screen.getByText('Bell icon mock')).toHaveAccessibleName('Test label'); +}); + +test('Matches snapshot', () => { + const { asFragment } = render(); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Alert/__tests__/AlertToggleExpandButton.test.tsx b/packages/react-core/src/components/Alert/__tests__/AlertToggleExpandButton.test.tsx new file mode 100644 index 00000000000..8ae6e321253 --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/AlertToggleExpandButton.test.tsx @@ -0,0 +1,150 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { AlertToggleExpandButton } from '../AlertToggleExpandButton'; +import { AlertContext } from '../AlertContext'; + +jest.mock('../../Button', () => ({ + Button: ({ children, variant, isInline, onClick, ...props }) => ( + <> + +

{`variant: ${variant}`}

+

Test label

+ + ), + ButtonVariant: { plain: 'plain' } +})); + +jest.mock('@patternfly/react-icons/dist/esm/icons/angle-right-icon', () => () => 'Icon mock'); + +test('Renders without children', () => { + render( +
+ + + +
+ ); + + expect(screen.getByTestId('container').firstChild).toBeVisible(); +}); + +test('Renders with an aria label composed with the title and variantLabel provided via a context by default', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Toggle variantLabel alert: title'); +}); + +test('Renders with the aria label provided via prop when one is provided', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Aria label prop'); +}); + +test('Does not call the callback provided via onToggleExpand when it is not clicked', () => { + const onToggleExpandMock = jest.fn(); + + render( + + + + ); + + expect(onToggleExpandMock).not.toHaveBeenCalled(); +}); + +test('Calls the callback provided via onToggleExpand when clicked', async () => { + const onToggleExpandMock = jest.fn(); + const user = userEvent.setup(); + + render( + + + + ); + + await user.click(screen.getByRole('button')); + + expect(onToggleExpandMock).toHaveBeenCalledTimes(1); +}); + +test('Does not render the button as aria-expanded by default', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false'); +}); + +test('Renders the button as aria-expanded when isExpanded = true', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true'); +}); + +test('Renders with an aria label composed with the title provided via a context and variantLabel provided via prop', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Toggle variantLabelProp alert: title'); + }); + +test('Renders a Button with variant: ButtonVariant.plain', () => { + render( + + + + ); + + expect(screen.getByText('variant: plain')).toBeVisible(); +}); + +test('Renders with the toggle icon inside class pf-c-alert__toggle-icon', () => { + render( + + + + ); + + expect(screen.getByText('Icon mock')).toHaveClass('pf-c-alert__toggle-icon'); +}); + +test('Renders with inherited element props spread to the component', () => { + render( + + + + ); + + expect(screen.getByRole('button')).toHaveAccessibleName('Test label'); + }); + +test('Matches snapshot', () => { + const { asFragment } = render( + + + + ); + + expect(asFragment()).toMatchSnapshot(); +}); diff --git a/packages/react-core/src/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap b/packages/react-core/src/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap index 6d3f59a100e..45f02af0872 100644 --- a/packages/react-core/src/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap +++ b/packages/react-core/src/components/Alert/__tests__/__snapshots__/Alert.test.tsx.snap @@ -1,2463 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Alert Alert - danger Action Link 1`] = ` - -
-
- -
-

- - Danger alert: - -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - danger Action and Title 1`] = ` - -
-
- -
-

- - Danger alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - danger Custom aria label 1`] = ` - -
-
- -
-

- - Danger alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - danger Custom icon 1`] = ` - -
-
- -
-

- - Danger alert: - - custom icon alert title -

-
- Some noisy alert -
-
-
-`; - -exports[`Alert Alert - danger Description 1`] = ` - -
-
- -
-

- - Danger alert: - -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - danger Heading level 1`] = ` - -
-
- -
-

- - Danger alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - danger Title 1`] = ` - -
-
- -
-

- - Danger alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - danger Toast alerts match snapsnot 1`] = ` - -
-
- -
-

- - Danger alert: - - Some title -

-
- Some toast alert -
-
-
-`; - -exports[`Alert Alert - danger expandable variation 1`] = ` - -
-
- -
-
- -
-

- - Danger alert: - - Some title -

-
-
-`; - -exports[`Alert Alert - danger inline variation 1`] = ` - -
-
- -
-

- - Danger alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - default Action Link 1`] = ` - -
-
- -
-

- - Default alert: - -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - default Action and Title 1`] = ` - -
-
- -
-

- - Default alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - default Custom aria label 1`] = ` - -
-
- -
-

- - Default alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - default Custom icon 1`] = ` - -
-
- -
-

- - Default alert: - - custom icon alert title -

-
- Some noisy alert -
-
-
-`; - -exports[`Alert Alert - default Description 1`] = ` - -
-
- -
-

- - Default alert: - -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - default Heading level 1`] = ` - -
-
- -
-

- - Default alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - default Title 1`] = ` - -
-
- -
-

- - Default alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - default Toast alerts match snapsnot 1`] = ` - -
-
- -
-

- - Default alert: - - Some title -

-
- Some toast alert -
-
-
-`; - -exports[`Alert Alert - default expandable variation 1`] = ` - -
-
- -
-
- -
-

- - Default alert: - - Some title -

-
-
-`; - -exports[`Alert Alert - default inline variation 1`] = ` - -
-
- -
-

- - Default alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - info Action Link 1`] = ` - -
-
- -
-

- - Info alert: - -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - info Action and Title 1`] = ` - -
-
- -
-

- - Info alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - info Custom aria label 1`] = ` - -
-
- -
-

- - Info alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - info Custom icon 1`] = ` - -
-
- -
-

- - Info alert: - - custom icon alert title -

-
- Some noisy alert -
-
-
-`; - -exports[`Alert Alert - info Description 1`] = ` - -
-
- -
-

- - Info alert: - -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - info Heading level 1`] = ` - -
-
- -
-

- - Info alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - info Title 1`] = ` - -
-
- -
-

- - Info alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - info Toast alerts match snapsnot 1`] = ` - -
-
- -
-

- - Info alert: - - Some title -

-
- Some toast alert -
-
-
-`; - -exports[`Alert Alert - info expandable variation 1`] = ` - -
-
- -
-
- -
-

- - Info alert: - - Some title -

-
-
-`; - -exports[`Alert Alert - info inline variation 1`] = ` - -
-
- -
-

- - Info alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - success Action Link 1`] = ` - -
-
- -
-

- - Success alert: - -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - success Action and Title 1`] = ` - -
-
- -
-

- - Success alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - success Custom aria label 1`] = ` - -
-
- -
-

- - Success alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - success Custom icon 1`] = ` - -
-
- -
-

- - Success alert: - - custom icon alert title -

-
- Some noisy alert -
-
-
-`; - -exports[`Alert Alert - success Description 1`] = ` - -
-
- -
-

- - Success alert: - -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - success Heading level 1`] = ` - -
-
- -
-

- - Success alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - success Title 1`] = ` - -
-
- -
-

- - Success alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - success Toast alerts match snapsnot 1`] = ` - -
-
- -
-

- - Success alert: - - Some title -

-
- Some toast alert -
-
-
-`; - -exports[`Alert Alert - success expandable variation 1`] = ` - -
-
- -
-
- -
-

- - Success alert: - - Some title -

-
-
-`; - -exports[`Alert Alert - success inline variation 1`] = ` - -
-
- -
-

- - Success alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - warning Action Link 1`] = ` - -
-
- -
-

- - Warning alert: - -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - warning Action and Title 1`] = ` - -
-
- -
-

- - Warning alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - warning Custom aria label 1`] = ` - -
-
- -
-

- - Warning alert: - - Some title -

-
- Some alert -
-
- -
-
-
-`; - -exports[`Alert Alert - warning Custom icon 1`] = ` - -
-
- -
-

- - Warning alert: - - custom icon alert title -

-
- Some noisy alert -
-
-
-`; - -exports[`Alert Alert - warning Description 1`] = ` - -
-
- -
-

- - Warning alert: - -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - warning Heading level 1`] = ` - -
-
- -
-

- - Warning alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - warning Title 1`] = ` - -
-
- -
-

- - Warning alert: - - Some title -

-
- Some alert -
-
-
-`; - -exports[`Alert Alert - warning Toast alerts match snapsnot 1`] = ` +exports[`Matches snapshot 1`] = `
-
- +
+

+ custom icon: +

+

+ variant: default +

- Warning alert: + Default alert: Some title

@@ -2477,120 +37,3 @@ exports[`Alert Alert - warning Toast alerts match snapsnot 1`] = `
`; - -exports[`Alert Alert - warning expandable variation 1`] = ` - -
-
- -
-
- -
-

- - Warning alert: - - Some title -

-
-
-`; - -exports[`Alert Alert - warning inline variation 1`] = ` - -
-
- -
-

- - Warning alert: - - Some title -

-
- Some alert -
-
-
-`; diff --git a/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertActionCloseButton.test.tsx.snap b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertActionCloseButton.test.tsx.snap new file mode 100644 index 00000000000..82f1e3a7d65 --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertActionCloseButton.test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches the snapshot 1`] = ` + + +

+ variant: plain +

+

+ Test label +

+
+`; diff --git a/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertActionLink.test.tsx.snap b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertActionLink.test.tsx.snap new file mode 100644 index 00000000000..fd53fe19fee --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertActionLink.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches the snapshot 1`] = ` + + +

+ variant: link +

+

+ isInline: true +

+
+`; diff --git a/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertIcon.test.tsx.snap b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertIcon.test.tsx.snap new file mode 100644 index 00000000000..40f9b402f6c --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertIcon.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches snapshot 1`] = ` + +
+ Bell icon mock +
+
+`; diff --git a/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertToggleExpandButton.test.tsx.snap b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertToggleExpandButton.test.tsx.snap new file mode 100644 index 00000000000..c1a42f67acc --- /dev/null +++ b/packages/react-core/src/components/Alert/__tests__/__snapshots__/AlertToggleExpandButton.test.tsx.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Matches snapshot 1`] = ` + + +

+ variant: plain +

+

+ Test label +

+
+`; diff --git a/packages/react-integration/cypress/integration/alertcustomtimeout.spec.ts b/packages/react-integration/cypress/integration/alertcustomtimeout.spec.ts deleted file mode 100644 index 5676281a835..00000000000 --- a/packages/react-integration/cypress/integration/alertcustomtimeout.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -describe('Alert Demo Test', () => { - it('Verify alert timeout', () => { - cy.clock(); - cy.visit('http://localhost:3000/alert-custom-timeout-demo-nav-link'); - cy.get('#alert-custom-timeout').should('not.exist'); - cy.get('#custom-button').click(); - cy.get('#alert-custom-timeout').should('exist'); - cy.tick(16000); - cy.get('#alert-custom-timeout').should('not.exist'); - cy.on('window:alert', msg => expect(msg).to.contains('Timeout')); - }); -}); diff --git a/packages/react-integration/cypress/integration/alertdefaulttimeout.spec.ts b/packages/react-integration/cypress/integration/alertdefaulttimeout.spec.ts deleted file mode 100644 index e50f8a2abb9..00000000000 --- a/packages/react-integration/cypress/integration/alertdefaulttimeout.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -describe('Alert Demo Test', () => { - it('Verify alert timeout', () => { - cy.clock(); - cy.visit('http://localhost:3000/alert-default-timeout-demo-nav-link'); - cy.get('#alert-default-timeout').should('not.exist'); - cy.get('#default-button').click(); - cy.get('#alert-default-timeout').should('exist'); - cy.tick(8000); - cy.get('#alert-default-timeout').should('not.exist'); - }); -}); diff --git a/packages/react-integration/demo-app-ts/src/Demos.ts b/packages/react-integration/demo-app-ts/src/Demos.ts index 04953db5eb2..5d428b634ac 100644 --- a/packages/react-integration/demo-app-ts/src/Demos.ts +++ b/packages/react-integration/demo-app-ts/src/Demos.ts @@ -37,16 +37,6 @@ export const Demos: DemoInterface[] = [ name: 'Alert Group Timeout From Bottom Demo', componentType: Examples.AlertGroupTimeoutFromBottomDemo }, - { - id: 'alert-custom-timeout-demo', - name: 'Alert Custom Timeout Demo', - componentType: Examples.AlertCustomTimeoutDemo - }, - { - id: 'alert-default-timeout-demo', - name: 'Alert Default Timeout Demo', - componentType: Examples.AlertDefaultTimeoutDemo - }, { id: 'application-launcher-favorites-demo', name: 'Application Launcher Favorites Demo', diff --git a/packages/react-integration/demo-app-ts/src/components/demos/AlertDemo/AlertCustomTimeoutDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/AlertDemo/AlertCustomTimeoutDemo.tsx deleted file mode 100644 index 551ceec396a..00000000000 --- a/packages/react-integration/demo-app-ts/src/components/demos/AlertDemo/AlertCustomTimeoutDemo.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Alert, Button } from '@patternfly/react-core'; -import React from 'react'; - -interface AlertCustomTimeoutDemoState { - isOpenAlert: boolean; -} - -export class AlertCustomTimeoutDemo extends React.Component<{}, AlertCustomTimeoutDemoState> { - static displayName = 'AlertCustomTimeoutDemo'; - constructor(props: {}) { - super(props); - this.state = { isOpenAlert: false }; - } - - componentDidMount() { - window.scrollTo(0, 0); - } - - onClick = () => this.setState({ isOpenAlert: true }); - onTimeout = () => alert('Timeout!'); - - render() { - const { isOpenAlert } = this.state; - return ( - - - {isOpenAlert && ( - - custom 16 second timeout - - )} - - ); - } -} diff --git a/packages/react-integration/demo-app-ts/src/components/demos/AlertDemo/AlertDefaultTimeoutDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/AlertDemo/AlertDefaultTimeoutDemo.tsx deleted file mode 100644 index 1901de1cb4c..00000000000 --- a/packages/react-integration/demo-app-ts/src/components/demos/AlertDemo/AlertDefaultTimeoutDemo.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Alert, Button } from '@patternfly/react-core'; -import React from 'react'; - -interface AlertDefaultTimeoutDemoState { - isOpenAlert: boolean; -} - -export class AlertDefaultTimeoutDemo extends React.Component<{}, AlertDefaultTimeoutDemoState> { - static displayName = 'AlertDefaultTimeoutDemo'; - constructor(props: {}) { - super(props); - this.state = { isOpenAlert: false }; - } - - componentDidMount() { - window.scrollTo(0, 0); - } - - onClick = () => this.setState({ isOpenAlert: true }); - - render() { - const { isOpenAlert } = this.state; - return ( - - - {isOpenAlert && ( - - 8 second timeout by default - - )} - - ); - } -} diff --git a/packages/react-integration/demo-app-ts/src/components/demos/index.ts b/packages/react-integration/demo-app-ts/src/components/demos/index.ts index 408d5a8a844..a8fa6e80d6c 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/index.ts +++ b/packages/react-integration/demo-app-ts/src/components/demos/index.ts @@ -3,8 +3,6 @@ export * from './AlertDemo/AlertDemo'; export * from './AlertDemo/AlertTimeoutCloseButtonDemo'; export * from './AlertGroupDemo/AlertGroupDemo'; export * from './AlertGroupDemo/AlertGroupTimeoutFromBottomDemo'; -export * from './AlertDemo/AlertCustomTimeoutDemo'; -export * from './AlertDemo/AlertDefaultTimeoutDemo'; export * from './ApplicationLauncherDemo/ApplicationLauncherFavoritesDemo'; export * from './BackToTopDemo/BackToTopDemo'; export * from './BreadcrumbDemo/BreadcrumbDemo';