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 { 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-v5-c-alert on the containing div', () => {
render(
Some alert
);
expect(screen.getByTestId('Alert-test-id')).toHaveClass('pf-v5-c-alert');
});
test('Renders with class pf-v5-c-alert__title on the div containing the title', () => {
render(Some alert);
expect(screen.getByRole('heading', { name: 'Custom alert: Some title' })).toHaveClass('pf-v5-c-alert__title');
});
test('Renders with Custom hidden text of "Custom alert:"', () => {
render(Some alert);
expect(screen.getByText('Custom alert:')).toBeInTheDocument();
});
['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 hidden text "${capitalize(variant)} alert:" when variant = ${variant}`, () => {
render(
Some alert
);
expect(screen.getByText(`${capitalize(variant)} alert:`)).toBeInTheDocument();
});
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(
}
>
Some alert
);
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.getByTestId('Alert-test-id')).not.toHaveClass('pf-m-inline');
});
test('Renders with class pf-m-inline when isInline = true', () => {
render(
Some alert
);
expect(screen.getByTestId('Alert-test-id')).toHaveClass('pf-m-inline');
});
test('Does not render with class pf-m-plain by default', () => {
render(
Some alert
);
expect(screen.getByTestId('Alert-test-id')).not.toHaveClass('pf-m-plain');
});
test('Renders with class pf-m-plain when isPlain = true', () => {
render(
Some alert
);
expect(screen.getByTestId('Alert-test-id')).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: 'Custom alert: Some title' })).toBeVisible();
});
test('Renders the title as other heading levels when one is passed using component', () => {
render(
Some alert
);
expect(screen.getByRole('heading', { level: 1, name: 'Custom 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-v5-c-alert__action', () => {
render(
Some alert
);
expect(screen.getByText('Action close')).toHaveClass('pf-v5-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-v5-c-alert__action-group', () => {
render(
Some alert
);
expect(screen.getByText('Action link')).toHaveClass('pf-v5-c-alert__action-group');
});
test('Renders children', () => {
render(Some alert);
expect(screen.getByText('Some alert')).toBeVisible();
});
test('Renders children inside pf-v5-c-alert__description', () => {
render(Some alert);
expect(screen.getByText('Some alert')).toHaveClass('pf-v5-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-v5-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.getByTestId('Alert-test-id')).not.toHaveClass('pf-m-expandable');
});
test('Renders with class pf-m-expandable when isExpandable = true', () => {
render(
Some alert
);
expect(screen.getByTestId('Alert-test-id')).toHaveClass('pf-m-expandable');
});
test('Renders AlertToggleExpandButton inside pf-v5-c-alert__toggle', () => {
render(
Some alert
);
expect(screen.getByRole('button').parentElement).toHaveClass('pf-v5-c-alert__toggle');
});
test('Does not render with class pf-m-expanded when AlertToggleExpandButton has not been clicked', () => {
render(
Some alert
);
expect(screen.getByTestId('Alert-test-id')).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.getByTestId('Alert-test-id')).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(
<>