Improve some frontend tests (#1264)

Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
This commit is contained in:
Cynthia S. Garcia 2021-04-22 21:03:10 +02:00 committed by GitHub
parent 89fb54dc89
commit 9176f9897e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 706 additions and 442 deletions

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"devDependencies": {
"@testing-library/dom": "^7.30.3",
"@testing-library/user-event": "^13.1.5"
}
}

View File

@ -1,4 +1,5 @@
import { fireEvent, render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import Alert from './Alert';
@ -15,13 +16,7 @@ const defaultProps = {
};
describe('Alert', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -30,33 +25,38 @@ describe('Alert', () => {
expect(asFragment).toMatchSnapshot();
});
it('renders proper content', () => {
const { rerender, getByTestId, getByRole, queryByRole, getByText } = render(<Alert {...defaultProps} />);
it('renders alert', async () => {
render(<Alert {...defaultProps} message="errorMessage" />);
expect(await screen.findByRole('alert')).toBeInTheDocument();
});
const alertWrapper = getByTestId('alertWrapper');
it('renders proper content', async () => {
const { rerender } = render(<Alert {...defaultProps} />);
const alertWrapper = screen.getByTestId('alertWrapper');
expect(alertWrapper).toBeInTheDocument();
expect(alertWrapper).not.toHaveClass('isAlertActive');
expect(queryByRole('alert')).toBeNull();
expect(screen.queryByRole('alert')).toBeNull();
rerender(<Alert {...defaultProps} message="errorMessage" />);
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
expect(await scrollIntoViewMock).toHaveBeenCalledTimes(1);
expect(alertWrapper).toHaveClass('isAlertActive');
const alert = getByRole('alert');
const alert = screen.getByRole('alert');
expect(alert).toBeInTheDocument();
expect(getByText('errorMessage')).toBeInTheDocument();
expect(getByTestId('closeAlertBtn')).toBeInTheDocument();
expect(screen.getByText('errorMessage')).toBeInTheDocument();
expect(screen.getByTestId('closeAlertBtn')).toBeInTheDocument();
});
it('calls close alert', () => {
const { getByTestId } = render(<Alert {...defaultProps} message="errorMessage" />);
render(<Alert {...defaultProps} message="errorMessage" />);
const alertWrapper = getByTestId('alertWrapper');
const alertWrapper = screen.getByTestId('alertWrapper');
expect(alertWrapper).toHaveClass('isAlertActive');
const closeBtn = getByTestId('closeAlertBtn');
fireEvent.click(closeBtn);
const closeBtn = screen.getByTestId('closeAlertBtn');
userEvent.click(closeBtn);
expect(onCloseMock).toHaveBeenCalledTimes(1);
});

View File

@ -13,21 +13,25 @@ interface Props {
const DEFAULT_ALERT_TYPE = 'warning';
const Alert: React.ElementType = (props: Props) => {
const [errorMessage, setErrorMessage] = useState<string | null>(props.message);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const errorWrapper = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState<boolean>(false);
useEffect(() => {
let timeout: NodeJS.Timeout;
if (isNull(props.message)) {
setIsVisible(false);
timeout = setTimeout(() => {
setErrorMessage(null);
}, 1000);
if (!isNull(errorMessage)) {
setIsVisible(false);
timeout = setTimeout(() => {
setErrorMessage(null);
}, 1000);
}
} else {
setErrorMessage(props.message);
setIsVisible(true);
errorWrapper.current!.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
if (props.message !== errorMessage) {
setErrorMessage(props.message);
setIsVisible(true);
errorWrapper.current!.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
}
}
return () => {
@ -35,7 +39,7 @@ const Alert: React.ElementType = (props: Props) => {
clearTimeout(timeout);
}
};
}, [props.message]);
}, [props.message]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<div

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import ButtonCopyToClipboard from './ButtonCopyToClipboard';
@ -16,13 +17,7 @@ Object.defineProperty(navigator, 'clipboard', {
});
describe('ButtonCopyToClipboard', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -32,52 +27,49 @@ describe('ButtonCopyToClipboard', () => {
});
it('renders tooltip after clicking button', async () => {
const { getByTestId, getByRole, queryByRole } = render(<ButtonCopyToClipboard text="Text to copy" />);
expect(queryByRole('tooltip')).toBeNull();
render(<ButtonCopyToClipboard text="Text to copy" />);
expect(screen.queryByRole('tooltip')).toBeNull();
const btn = getByTestId('ctcBtn');
fireEvent.click(btn);
const btn = screen.getByTestId('ctcBtn');
userEvent.click(btn);
await waitFor(() => {
expect(clipboardWriteTextMock).toHaveBeenCalledTimes(1);
expect(clipboardWriteTextMock).toHaveBeenCalledWith('Text to copy');
});
expect(clipboardWriteTextMock).toHaveBeenCalledTimes(1);
expect(clipboardWriteTextMock).toHaveBeenCalledWith('Text to copy');
waitFor(() => {
expect(getByRole('tooltip')).toBeInTheDocument();
});
});
it('hides tooltip after 2 seconds', async () => {
const { getByTestId, getByRole, queryByRole } = render(<ButtonCopyToClipboard text="Text to copy" />);
expect(queryByRole('tooltip')).toBeNull();
const btn = getByTestId('ctcBtn');
fireEvent.click(btn);
await waitFor(() => {
expect(getByRole('tooltip')).toBeInTheDocument();
});
waitFor(() => {
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 2000);
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
});
it('renders tooltip after clicking button when navidator.clipboard is undefined', async () => {
(navigator as any).clipboard = null;
const { getByTestId, getByRole, queryByRole } = render(<ButtonCopyToClipboard text="Text to copy" />);
expect(queryByRole('tooltip')).toBeNull();
render(<ButtonCopyToClipboard text="Text to copy" />);
expect(screen.queryByRole('tooltip')).toBeNull();
const btn = getByTestId('ctcBtn');
fireEvent.click(btn);
const btn = screen.getByTestId('ctcBtn');
userEvent.click(btn);
await waitFor(() => {
expect(copyToClipboardMock).toHaveBeenCalledTimes(1);
expect(copyToClipboardMock).toHaveBeenCalledWith('copy');
await waitFor(() => expect(copyToClipboardMock).toHaveBeenCalledWith('copy'));
expect(copyToClipboardMock).toHaveBeenCalledTimes(1);
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
});
it('hides tooltip after 2 seconds', async () => {
jest.useFakeTimers();
render(<ButtonCopyToClipboard text="Text to copy" />);
expect(screen.queryByRole('tooltip')).toBeNull();
const btn = screen.getByTestId('ctcBtn');
userEvent.click(btn);
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
act(() => {
jest.advanceTimersByTime(2000);
});
expect(getByRole('tooltip')).toBeInTheDocument();
expect(screen.queryByRole('tooltip')).toBeNull();
jest.useRealTimers();
});
});

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import ElementWithTooltip from './ElementWithTooltip';
@ -11,13 +12,7 @@ const defaultProps = {
};
describe('ElementWithTooltip', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -37,48 +32,52 @@ describe('ElementWithTooltip', () => {
expect(container).toBeEmptyDOMElement();
});
it('displays tootltip', async () => {
const { getByTestId, getByText, getByRole } = render(<ElementWithTooltip {...defaultProps} />);
it('displays tooltip', async () => {
jest.useFakeTimers();
const badge = getByTestId('elementWithTooltip');
fireEvent.mouseEnter(badge);
render(<ElementWithTooltip {...defaultProps} />);
await waitFor(() => {
expect(getByText(defaultProps.tooltipMessage)).toBeInTheDocument();
expect(getByRole('tooltip')).toBeInTheDocument();
});
const badge = screen.getByTestId('elementWithTooltip');
userEvent.hover(badge);
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText(defaultProps.tooltipMessage)).toBeInTheDocument();
jest.useRealTimers();
});
it('hides tootltip on mouse leave', async () => {
const { getByTestId, getByText, getByRole, queryByRole } = render(<ElementWithTooltip {...defaultProps} />);
it('hides tooltip on mouse leave', async () => {
jest.useFakeTimers();
const badge = getByTestId('elementWithTooltip');
fireEvent.mouseEnter(badge);
render(<ElementWithTooltip {...defaultProps} />);
await waitFor(() => {
expect(getByText(defaultProps.tooltipMessage)).toBeInTheDocument();
expect(getByRole('tooltip')).toBeInTheDocument();
const badge = screen.getByTestId('elementWithTooltip');
userEvent.hover(badge);
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText(defaultProps.tooltipMessage)).toBeInTheDocument();
userEvent.unhover(badge);
act(() => {
jest.advanceTimersByTime(50);
});
fireEvent.mouseLeave(badge);
expect(screen.queryByRole('tooltip')).toBeNull();
await waitFor(() => {
expect(queryByRole('tooltip')).toBeNull();
});
jest.useRealTimers();
});
it('does not display tootltip when visibleTooltip is false', () => {
it('does not display tooltip when visibleTooltip is false', () => {
const props = {
...defaultProps,
visibleTooltip: false,
};
const { getByTestId, queryByRole } = render(<ElementWithTooltip {...props} />);
render(<ElementWithTooltip {...props} />);
const badge = getByTestId('elementWithTooltip');
fireEvent.mouseEnter(badge);
const badge = screen.getByTestId('elementWithTooltip');
userEvent.hover(badge);
waitFor(() => {
expect(queryByRole('tooltip')).toBeNull();
});
expect(screen.queryByRole('tooltip')).toBeNull();
});
});

View File

@ -1,16 +1,11 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import OfficialBadge from './OfficialBadge';
describe('OfficialBadge', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -20,33 +15,31 @@ describe('OfficialBadge', () => {
});
it('renders label', async () => {
const { getByTestId, getByText, getByRole } = render(<OfficialBadge official type="repo" />);
expect(getByText('Official')).toBeInTheDocument();
render(<OfficialBadge official type="repo" />);
expect(screen.getByText('Official')).toBeInTheDocument();
const badge = getByTestId('elementWithTooltip');
const badge = screen.getByTestId('elementWithTooltip');
expect(badge).toBeInTheDocument();
fireEvent.mouseEnter(badge);
userEvent.hover(badge);
await waitFor(() => {
expect(
getByText('The publisher owns the software deployed by the packages in this repository')
).toBeInTheDocument();
expect(getByRole('tooltip')).toBeInTheDocument();
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(
screen.getByText('The publisher owns the software deployed by the packages in this repository')
).toBeInTheDocument();
});
it('renders label for package', async () => {
const { getByTestId, getByText, getByRole } = render(<OfficialBadge official type="package" />);
expect(getByText('Official')).toBeInTheDocument();
render(<OfficialBadge official type="package" />);
expect(screen.getByText('Official')).toBeInTheDocument();
const badge = getByTestId('elementWithTooltip');
const badge = screen.getByTestId('elementWithTooltip');
expect(badge).toBeInTheDocument();
fireEvent.mouseEnter(badge);
userEvent.hover(badge);
await waitFor(() => {
expect(getByText('The publisher owns the software deployed by this package')).toBeInTheDocument();
expect(getByRole('tooltip')).toBeInTheDocument();
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText('The publisher owns the software deployed by this package')).toBeInTheDocument();
});
it('does not render label', () => {

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { mocked } from 'ts-jest/utils';
@ -27,13 +28,7 @@ const getMockOrganization = (fixtureId: string): Organization => {
};
describe('OrganizationInfo', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -43,16 +38,16 @@ describe('OrganizationInfo', () => {
});
it('renders proper content', () => {
const { getByText, getByTestId } = render(<OrganizationInfo {...defaultProps} />);
expect(getByText(defaultProps.organizationName)).toBeInTheDocument();
expect(getByTestId('orgLink')).toBeInTheDocument();
render(<OrganizationInfo {...defaultProps} />);
expect(screen.getByText(defaultProps.organizationName)).toBeInTheDocument();
expect(screen.getByTestId('orgLink')).toBeInTheDocument();
});
it('calls history push to click org link', () => {
const { getByTestId } = render(<OrganizationInfo {...defaultProps} />);
fireEvent.click(getByTestId('orgLink'));
it('calls history push to click org link', async () => {
render(<OrganizationInfo {...defaultProps} />);
userEvent.click(screen.getByTestId('orgLink'));
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
await waitFor(() => expect(mockHistoryPush).toHaveBeenCalledTimes(1));
expect(mockHistoryPush).toHaveBeenCalledWith({
pathname: '/packages/search',
search: prepareQuerystring({
@ -65,71 +60,86 @@ describe('OrganizationInfo', () => {
});
it('displays org info to enter on link and hides on leave', async () => {
jest.useFakeTimers();
const mockOrganization = getMockOrganization('1');
mocked(API).getOrganization.mockResolvedValue(mockOrganization);
const { getByTestId, getByAltText, getByText } = render(<OrganizationInfo {...defaultProps} />);
fireEvent.mouseEnter(getByTestId('orgLink'));
render(<OrganizationInfo {...defaultProps} />);
userEvent.hover(screen.getByTestId('orgLink'));
await waitFor(() => {
expect(API.getOrganization).toHaveBeenCalledTimes(1);
});
expect(getByTestId('externalBtn')).toBeInTheDocument();
expect(getByAltText(mockOrganization.displayName!)).toBeInTheDocument();
expect(getByAltText(mockOrganization.displayName!)).toHaveProperty(
expect(screen.getByTestId('externalBtn')).toBeInTheDocument();
expect(screen.getByAltText(mockOrganization.displayName!)).toBeInTheDocument();
expect(screen.getByAltText(mockOrganization.displayName!)).toHaveProperty(
'src',
`http://localhost/image/${mockOrganization.logoImageId!}`
);
expect(getByText(mockOrganization.description!)).toBeInTheDocument();
expect(screen.getByText(mockOrganization.description!)).toBeInTheDocument();
await waitFor(() => {
expect(getByTestId('orgInfoDropdown')).toHaveClass('show');
act(() => {
jest.advanceTimersByTime(100);
});
fireEvent.mouseLeave(getByTestId('orgLink'));
expect(await screen.findByTestId('orgInfoDropdown')).toHaveClass('show');
await waitFor(() => {
expect(getByTestId('orgInfoDropdown')).not.toHaveClass('show');
userEvent.unhover(screen.getByTestId('orgLink'));
act(() => {
jest.advanceTimersByTime(50);
});
expect(await screen.findByTestId('orgInfoDropdown')).not.toHaveClass('show');
jest.useRealTimers();
});
it('hides org info to leave org dropdown', async () => {
jest.useFakeTimers();
const mockOrganization = getMockOrganization('1');
mocked(API).getOrganization.mockResolvedValue(mockOrganization);
const { getByTestId } = render(<OrganizationInfo {...defaultProps} />);
fireEvent.mouseEnter(getByTestId('orgLink'));
render(<OrganizationInfo {...defaultProps} />);
userEvent.hover(screen.getByTestId('orgLink'));
await waitFor(() => {
expect(API.getOrganization).toHaveBeenCalledTimes(1);
});
fireEvent.mouseEnter(getByTestId('orgInfoDropdown'));
fireEvent.mouseLeave(getByTestId('orgLink'));
userEvent.hover(screen.getByTestId('orgInfoDropdown'));
userEvent.unhover(screen.getByTestId('orgLink'));
await waitFor(() => {
expect(getByTestId('orgInfoDropdown')).toHaveClass('show');
act(() => {
jest.advanceTimersByTime(100);
});
fireEvent.mouseLeave(getByTestId('orgInfoDropdown'));
await waitFor(() => {
expect(getByTestId('orgInfoDropdown')).not.toHaveClass('show');
expect(await screen.findByTestId('orgInfoDropdown')).toHaveClass('show');
userEvent.unhover(screen.getByTestId('orgInfoDropdown'));
act(() => {
jest.advanceTimersByTime(50);
});
expect(await screen.findByTestId('orgInfoDropdown')).not.toHaveClass('show');
jest.useRealTimers();
});
it('does not render dropdown content when api call fails', async () => {
mocked(API).getOrganization.mockRejectedValue('');
const { getByTestId } = render(<OrganizationInfo {...defaultProps} />);
fireEvent.mouseEnter(getByTestId('orgLink'));
render(<OrganizationInfo {...defaultProps} />);
userEvent.hover(screen.getByTestId('orgLink'));
await waitFor(() => {
expect(API.getOrganization).toHaveBeenCalledTimes(1);
});
await waitFor(() => {
expect(getByTestId('orgInfoDropdown')).toBeEmptyDOMElement();
});
expect(await screen.findByTestId('orgInfoDropdown')).toBeEmptyDOMElement();
});
});

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import prepareQuerystring from '../../utils/prepareQueryString';
@ -29,13 +30,7 @@ const defaultProps = {
};
describe('RepositoryInfo', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -45,16 +40,16 @@ describe('RepositoryInfo', () => {
});
it('renders proper content', () => {
const { getAllByText, getByTestId } = render(<RepositoryInfo {...defaultProps} />);
expect(getAllByText(defaultProps.repository.displayName)).toHaveLength(2);
expect(getByTestId('repoLink')).toBeInTheDocument();
render(<RepositoryInfo {...defaultProps} />);
expect(screen.getAllByText(defaultProps.repository.displayName)).toHaveLength(2);
expect(screen.getByTestId('repoLink')).toBeInTheDocument();
});
it('calls history push to click repo link', () => {
const { getByTestId } = render(<RepositoryInfo {...defaultProps} />);
fireEvent.click(getByTestId('repoLink'));
it('calls history push to click repo link', async () => {
render(<RepositoryInfo {...defaultProps} />);
userEvent.click(screen.getByTestId('repoLink'));
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
await waitFor(() => expect(mockHistoryPush).toHaveBeenCalledTimes(1));
expect(mockHistoryPush).toHaveBeenCalledWith({
pathname: '/packages/search',
search: prepareQuerystring({
@ -68,38 +63,56 @@ describe('RepositoryInfo', () => {
});
it('displays repo info to enter on link and hides on leave', async () => {
const { getByTestId, getAllByText } = render(<RepositoryInfo {...defaultProps} />);
expect(getAllByText(defaultProps.repository.displayName!)).toHaveLength(2);
expect(getByTestId('repoUrl')).toBeInTheDocument();
expect(getByTestId('repoUrl')).toHaveTextContent(defaultProps.repository.url);
jest.useFakeTimers();
fireEvent.mouseEnter(getByTestId('repoLink'));
render(<RepositoryInfo {...defaultProps} />);
expect(screen.getAllByText(defaultProps.repository.displayName!)).toHaveLength(2);
expect(screen.getByTestId('repoUrl')).toBeInTheDocument();
expect(screen.getByTestId('repoUrl')).toHaveTextContent(defaultProps.repository.url);
await waitFor(() => {
expect(getByTestId('repoInfoDropdown')).toHaveClass('show');
userEvent.hover(screen.getByTestId('repoLink'));
act(() => {
jest.advanceTimersByTime(100);
});
fireEvent.mouseLeave(getByTestId('repoLink'));
expect(await screen.findByTestId('repoInfoDropdown')).toHaveClass('show');
await waitFor(() => {
expect(getByTestId('repoInfoDropdown')).not.toHaveClass('show');
userEvent.unhover(screen.getByTestId('repoLink'));
act(() => {
jest.advanceTimersByTime(50);
});
expect(await screen.findByTestId('repoInfoDropdown')).not.toHaveClass('show');
jest.useRealTimers();
});
it('hides repo info to leave dropdown', async () => {
const { getByTestId } = render(<RepositoryInfo {...defaultProps} />);
fireEvent.mouseEnter(getByTestId('repoLink'));
jest.useFakeTimers();
fireEvent.mouseEnter(getByTestId('repoInfoDropdown'));
fireEvent.mouseLeave(getByTestId('repoLink'));
await waitFor(() => {
expect(getByTestId('repoInfoDropdown')).toHaveClass('show');
render(<RepositoryInfo {...defaultProps} />);
userEvent.hover(screen.getByTestId('repoLink'));
userEvent.hover(screen.getByTestId('repoInfoDropdown'));
userEvent.unhover(screen.getByTestId('repoLink'));
act(() => {
jest.advanceTimersByTime(100);
});
fireEvent.mouseLeave(getByTestId('repoInfoDropdown'));
await waitFor(() => {
expect(getByTestId('repoInfoDropdown')).not.toHaveClass('show');
expect(await screen.findByTestId('repoInfoDropdown')).toHaveClass('show');
userEvent.unhover(screen.getByTestId('repoInfoDropdown'));
act(() => {
jest.advanceTimersByTime(50);
});
expect(await screen.findByTestId('repoInfoDropdown')).not.toHaveClass('show');
jest.useRealTimers();
});
it('renders Verified Publisher label', () => {
@ -110,7 +123,7 @@ describe('RepositoryInfo', () => {
verifiedPublisher: true,
},
};
const { getByText } = render(<RepositoryInfo {...props} />);
expect(getByText('Verified Publisher')).toBeInTheDocument();
render(<RepositoryInfo {...props} />);
expect(screen.getByText('Verified Publisher')).toBeInTheDocument();
});
});

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import SecurityRating from './SecutityRating';
@ -15,13 +16,7 @@ const defaultProps = {
};
describe('SecurityRating', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -31,23 +26,21 @@ describe('SecurityRating', () => {
});
it('renders label', async () => {
const { getByTestId, getByText, getByRole, getAllByText } = render(<SecurityRating {...defaultProps} />);
expect(getByText('Images Security Rating')).toBeInTheDocument();
expect(getByText('F')).toBeInTheDocument();
render(<SecurityRating {...defaultProps} />);
expect(screen.getByText('Images Security Rating')).toBeInTheDocument();
expect(screen.getByText('F')).toBeInTheDocument();
const badge = getByTestId('elementWithTooltip');
const badge = screen.getByTestId('elementWithTooltip');
expect(badge).toBeInTheDocument();
fireEvent.mouseEnter(badge);
userEvent.hover(badge);
await waitFor(() => {
expect(getByRole('tooltip')).toBeInTheDocument();
expect(getByText('No vulnerabilities found')).toBeInTheDocument();
expect(getAllByText(/Vulnerabilities of severity/g)).toHaveLength(5);
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText('No vulnerabilities found')).toBeInTheDocument();
expect(screen.getAllByText(/Vulnerabilities of severity/g)).toHaveLength(5);
});
it('renders A label', () => {
const { getByText } = render(
render(
<SecurityRating
summary={{
low: 0,
@ -59,11 +52,11 @@ describe('SecurityRating', () => {
onlyBadge
/>
);
expect(getByText('A')).toBeInTheDocument();
expect(screen.getByText('A')).toBeInTheDocument();
});
it('renders B label', () => {
const { getByText } = render(
render(
<SecurityRating
summary={{
low: 8,
@ -75,11 +68,11 @@ describe('SecurityRating', () => {
onlyBadge
/>
);
expect(getByText('B')).toBeInTheDocument();
expect(screen.getByText('B')).toBeInTheDocument();
});
it('renders C label', () => {
const { getByText } = render(
render(
<SecurityRating
summary={{
low: 8,
@ -91,11 +84,11 @@ describe('SecurityRating', () => {
onlyBadge
/>
);
expect(getByText('C')).toBeInTheDocument();
expect(screen.getByText('C')).toBeInTheDocument();
});
it('renders D label', () => {
const { getByText } = render(
render(
<SecurityRating
summary={{
low: 8,
@ -107,11 +100,11 @@ describe('SecurityRating', () => {
onlyBadge
/>
);
expect(getByText('D')).toBeInTheDocument();
expect(screen.getByText('D')).toBeInTheDocument();
});
it('renders F label', () => {
const { getByText } = render(
render(
<SecurityRating
summary={{
low: 8,
@ -123,11 +116,11 @@ describe('SecurityRating', () => {
onlyBadge
/>
);
expect(getByText('F')).toBeInTheDocument();
expect(screen.getByText('F')).toBeInTheDocument();
});
it('renders - label', () => {
const { getByText } = render(
render(
<SecurityRating
summary={{
low: 0,
@ -139,12 +132,12 @@ describe('SecurityRating', () => {
onlyBadge
/>
);
expect(getByText('-')).toBeInTheDocument();
expect(screen.getByText('-')).toBeInTheDocument();
});
it('does not full label when onlyBadge is true', () => {
const { queryByText } = render(<SecurityRating {...defaultProps} onlyBadge />);
expect(queryByText('Images Security Rating')).toBeNull();
render(<SecurityRating {...defaultProps} onlyBadge />);
expect(screen.queryByText('Images Security Rating')).toBeNull();
});
it('does not render label', () => {

View File

@ -1,16 +1,11 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import SignedBadge from './SignedBadge';
describe('SignedBadge', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -20,31 +15,28 @@ describe('SignedBadge', () => {
});
it('renders label for Helm package', async () => {
const { getByTestId, getByText, getByRole } = render(<SignedBadge repositoryKind={0} signed />);
expect(getByText('Signed')).toBeInTheDocument();
render(<SignedBadge repositoryKind={0} signed />);
expect(screen.getByText('Signed')).toBeInTheDocument();
const badge = getByTestId('elementWithTooltip');
const badge = screen.getByTestId('elementWithTooltip');
expect(badge).toBeInTheDocument();
fireEvent.mouseEnter(badge);
userEvent.hover(badge);
await waitFor(() => {
expect(getByText('This chart has a provenance file')).toBeInTheDocument();
expect(getByRole('tooltip')).toBeInTheDocument();
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText('This chart has a provenance file')).toBeInTheDocument();
});
it('does not render label for not helm package', () => {
const { getByTestId, getByText, queryByText, queryByRole } = render(<SignedBadge repositoryKind={1} signed />);
expect(getByText('Signed')).toBeInTheDocument();
render(<SignedBadge repositoryKind={1} signed />);
expect(screen.getByText('Signed')).toBeInTheDocument();
const badge = getByTestId('elementWithTooltip');
const badge = screen.getByTestId('elementWithTooltip');
expect(badge).toBeInTheDocument();
fireEvent.mouseEnter(badge);
userEvent.hover(badge);
waitFor(() => {
expect(queryByText('This chart has a provenance file')).toBeNull();
expect(queryByRole('tooltip')).toBeNull();
});
expect(screen.queryByText('This chart has a provenance file')).toBeNull();
expect(screen.queryByRole('tooltip')).toBeNull();
});
it('does not render label', () => {

View File

@ -1,16 +1,11 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import VerifiedPublisherBadge from './VerifiedPublisherBadge';
describe('VerifiedPublisherBadge', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -20,17 +15,16 @@ describe('VerifiedPublisherBadge', () => {
});
it('renders label', async () => {
const { getByTestId, getByText, getByRole } = render(<VerifiedPublisherBadge verifiedPublisher />);
expect(getByText('Verified Publisher')).toBeInTheDocument();
render(<VerifiedPublisherBadge verifiedPublisher />);
expect(screen.getByText('Verified Publisher')).toBeInTheDocument();
const badge = getByTestId('elementWithTooltip');
const badge = screen.getByTestId('elementWithTooltip');
expect(badge).toBeInTheDocument();
fireEvent.mouseEnter(badge);
userEvent.hover(badge);
await waitFor(() => {
expect(getByText('The publisher owns the repository')).toBeInTheDocument();
expect(getByRole('tooltip')).toBeInTheDocument();
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText('The publisher owns the repository')).toBeInTheDocument();
});
it('does not render label', () => {

View File

@ -1,4 +1,4 @@
import { render, waitFor } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { AppCtx } from '../../../context/AppCtx';
@ -33,8 +33,6 @@ const mockCtx = {
describe('UserNotificationsController', () => {
beforeEach(() => {
jest.useFakeTimers();
const mockMath = Object.create(global.Math);
mockMath.random = () => 0;
global.Math = mockMath;
@ -55,8 +53,6 @@ describe('UserNotificationsController', () => {
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -75,19 +71,18 @@ describe('UserNotificationsController', () => {
});
it('renders component', async () => {
const { getByRole } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<UserNotificationsController />
</AppCtx.Provider>
);
const component = getByRole('alert');
const component = screen.getByRole('alert');
expect(component).toBeInTheDocument();
expect(component).not.toHaveClass('show');
expect(component).toHaveClass('toast');
await waitFor(() => {
expect(userNotificationsDispatcher.start).toHaveBeenCalledTimes(1);
expect(userNotificationsDispatcher.start).toHaveBeenCalledWith(
{
displayed: [],
@ -97,6 +92,7 @@ describe('UserNotificationsController', () => {
'lg'
);
});
expect(userNotificationsDispatcher.start).toHaveBeenCalledTimes(1);
});
it('does not call userNotificationsDispatcher.start when user is undefined', () => {

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { AppCtx } from '../../context/AppCtx';
@ -40,13 +41,7 @@ const mockCtx = {
};
describe('ActionBtn', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -62,7 +57,7 @@ describe('ActionBtn', () => {
});
it('renders enabled button', () => {
const { getByTestId, getByText } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<ActionBtn {...defaultProps}>
<div>button content</div>
@ -70,18 +65,18 @@ describe('ActionBtn', () => {
</AppCtx.Provider>
);
const btn = getByTestId(defaultProps.testId);
const btn = screen.getByTestId(defaultProps.testId);
expect(btn).toBeInTheDocument();
expect(btn).not.toHaveClass('disabled');
fireEvent.click(btn);
userEvent.click(btn);
expect(onClickMock).toHaveBeenCalledTimes(1);
expect(getByText('button content')).toBeInTheDocument();
expect(screen.getByText('button content')).toBeInTheDocument();
});
it('renders disabled button', () => {
const { getByTestId, getByText } = render(
render(
<AppCtx.Provider
value={{
ctx: {
@ -97,18 +92,20 @@ describe('ActionBtn', () => {
</AppCtx.Provider>
);
const btn = getByTestId(defaultProps.testId);
const btn = screen.getByTestId(defaultProps.testId);
expect(btn).toBeInTheDocument();
expect(btn).toHaveClass('disabled');
fireEvent.click(btn);
userEvent.click(btn);
expect(onClickMock).toHaveBeenCalledTimes(0);
expect(getByText('button content')).toBeInTheDocument();
expect(screen.getByText('button content')).toBeInTheDocument();
});
it('displays tooltip', async () => {
const { getByTestId, getByRole, getByText } = render(
jest.useFakeTimers();
render(
<AppCtx.Provider
value={{
ctx: {
@ -124,12 +121,12 @@ describe('ActionBtn', () => {
</AppCtx.Provider>
);
const btn = getByTestId(defaultProps.testId);
fireEvent.mouseEnter(btn);
const btn = screen.getByTestId(defaultProps.testId);
userEvent.hover(btn);
await waitFor(() => {
expect(getByRole('tooltip')).toBeInTheDocument();
expect(getByText('You are not allowed to perform this action')).toBeInTheDocument();
});
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
expect(screen.getByText('You are not allowed to perform this action')).toBeInTheDocument();
jest.useRealTimers();
});
});

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import moment from 'moment';
import React from 'react';
@ -58,13 +59,7 @@ const mockCtx = {
};
describe('Repository Card - packages section', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -79,19 +74,21 @@ describe('Repository Card - packages section', () => {
describe('Render', () => {
it('renders component', () => {
const { getByText, getByTestId } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...defaultProps} />
</AppCtx.Provider>
);
expect(getByText(repoMock.displayName!)).toBeInTheDocument();
expect(getByTestId('getBadgeBtn')).toBeInTheDocument();
expect(getByTestId('updateRepoBtn')).toBeInTheDocument();
expect(getByTestId('transferRepoBtn')).toBeInTheDocument();
expect(getByTestId('deleteRepoDropdownBtn')).toBeInTheDocument();
expect(getByText(repoMock.url!)).toBeInTheDocument();
expect(getByText('Not processed yet, it will be processed automatically in ~ 3 minutes')).toBeInTheDocument();
expect(screen.getByText(repoMock.displayName!)).toBeInTheDocument();
expect(screen.getByTestId('getBadgeBtn')).toBeInTheDocument();
expect(screen.getByTestId('updateRepoBtn')).toBeInTheDocument();
expect(screen.getByTestId('transferRepoBtn')).toBeInTheDocument();
expect(screen.getByTestId('deleteRepoDropdownBtn')).toBeInTheDocument();
expect(screen.getByText(repoMock.url!)).toBeInTheDocument();
expect(
screen.getByText('Not processed yet, it will be processed automatically in ~ 3 minutes')
).toBeInTheDocument();
});
it('renders component with last tracking and scanning info', () => {
@ -105,15 +102,15 @@ describe('Repository Card - packages section', () => {
lastScanningErrors: 'errors scanning',
},
};
const { getByText, getAllByText } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...props} />
</AppCtx.Provider>
);
expect(getAllByText('a few seconds ago')).toHaveLength(2);
expect(getByText('Show tracking errors log')).toBeInTheDocument();
expect(getByText('Show scanning errors log')).toBeInTheDocument();
expect(screen.getAllByText('a few seconds ago')).toHaveLength(2);
expect(screen.getByText('Show tracking errors log')).toBeInTheDocument();
expect(screen.getByText('Show scanning errors log')).toBeInTheDocument();
});
it('renders verified publisher badge', () => {
@ -124,13 +121,13 @@ describe('Repository Card - packages section', () => {
verifiedPublisher: true,
},
};
const { getAllByText } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...props} />
</AppCtx.Provider>
);
expect(getAllByText('Verified Publisher')).toHaveLength(2);
expect(screen.getAllByText('Verified Publisher')).toHaveLength(2);
});
it('renders Official badge', () => {
@ -141,80 +138,72 @@ describe('Repository Card - packages section', () => {
official: true,
},
};
const { getAllByText } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...props} />
</AppCtx.Provider>
);
expect(getAllByText('Official')).toHaveLength(2);
expect(screen.getAllByText('Official')).toHaveLength(2);
});
it('renders deletion modal when delete button in dropdown is clicked', () => {
const { getByTestId } = render(
it('renders deletion modal when delete button in dropdown is clicked', async () => {
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...defaultProps} />
</AppCtx.Provider>
);
const btn = getByTestId('deleteRepoDropdownBtn');
fireEvent.click(btn);
const btn = screen.getByTestId('deleteRepoDropdownBtn');
userEvent.click(btn);
waitFor(() => {
expect(getByTestId('deleteRepoBtn')).toBeInTheDocument();
});
expect(await screen.findByTestId('deleteRepoBtn')).toBeInTheDocument();
});
it('opens Get Badge Modal when Get badge button is clicked', async () => {
const { getByTestId } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...defaultProps} />
</AppCtx.Provider>
);
const btn = getByTestId('getBadgeBtn');
const btn = screen.getByTestId('getBadgeBtn');
expect(btn).toBeInTheDocument();
fireEvent.click(btn);
userEvent.click(btn);
await waitFor(() => {
expect(getByTestId('badgeModalContent')).toBeInTheDocument();
});
expect(await screen.findByTestId('badgeModalContent')).toBeInTheDocument();
});
it('calls setModalStatus when Edit button is clicked', async () => {
const { getByTestId } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...defaultProps} />
</AppCtx.Provider>
);
const btn = getByTestId('updateRepoBtn');
const btn = screen.getByTestId('updateRepoBtn');
expect(btn).toBeInTheDocument();
fireEvent.click(btn);
userEvent.click(btn);
await waitFor(() => {
expect(setModalStatusMock).toHaveBeenCalledTimes(1);
expect(setModalStatusMock).toHaveBeenCalledWith({
open: true,
repository: repoMock,
});
await waitFor(() => expect(setModalStatusMock).toHaveBeenCalledTimes(1));
expect(setModalStatusMock).toHaveBeenCalledWith({
open: true,
repository: repoMock,
});
});
it('opens Transfer Repo when Transfer button is clicked', async () => {
const { getByTestId, getByText } = render(
render(
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
<Card {...defaultProps} />
</AppCtx.Provider>
);
const btn = getByTestId('transferRepoBtn');
const btn = screen.getByTestId('transferRepoBtn');
expect(btn).toBeInTheDocument();
fireEvent.click(btn);
userEvent.click(btn);
await waitFor(() => {
expect(getByText('Transfer repository')).toBeInTheDocument();
});
expect(await screen.findByText('Transfer repository')).toBeInTheDocument();
});
it('opens logs modal when visibleModal is tracking and repo has errors', () => {
@ -233,12 +222,12 @@ describe('Repository Card - packages section', () => {
<Card {...props} />
</AppCtx.Provider>
);
const { getByText, getByRole, rerender } = render(component);
expect(getByText('Show tracking errors log')).toBeInTheDocument();
const { rerender } = render(component);
expect(screen.getByText('Show tracking errors log')).toBeInTheDocument();
rerender(component);
expect(getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(mockHistoryReplace).toHaveBeenCalledTimes(1);
expect(mockHistoryReplace).toHaveBeenCalledWith({ search: '' });
});
@ -260,12 +249,12 @@ describe('Repository Card - packages section', () => {
<Card {...props} />
</AppCtx.Provider>
);
const { getByText, getByRole, rerender } = render(component);
expect(getByText('Show scanning errors log')).toBeInTheDocument();
const { rerender } = render(component);
expect(screen.getByText('Show scanning errors log')).toBeInTheDocument();
rerender(component);
expect(getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(mockHistoryReplace).toHaveBeenCalledTimes(1);
expect(mockHistoryReplace).toHaveBeenCalledWith({ search: '' });
});
@ -284,17 +273,17 @@ describe('Repository Card - packages section', () => {
<Card {...props} />
</AppCtx.Provider>
);
const { queryByText, getByText, getByRole, rerender } = render(component);
expect(queryByText('Show tracking errors log')).toBeNull();
const { rerender } = render(component);
expect(screen.queryByText('Show tracking errors log')).toBeNull();
rerender(component);
expect(getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(
getByText(/It looks like the last tracking of this repository worked fine and no errors were produced./g)
screen.getByText(/It looks like the last tracking of this repository worked fine and no errors were produced./g)
).toBeInTheDocument();
expect(
getByText(
screen.getByText(
/If you have arrived to this screen from an email listing some errors, please keep in mind those may have been already solved./g
)
).toBeInTheDocument();
@ -318,19 +307,19 @@ describe('Repository Card - packages section', () => {
<Card {...props} />
</AppCtx.Provider>
);
const { queryByText, getByText, getByRole, rerender } = render(component);
expect(queryByText('Show scanning errors log')).toBeNull();
const { rerender } = render(component);
expect(screen.queryByText('Show scanning errors log')).toBeNull();
rerender(component);
expect(getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(
getByText(
screen.getByText(
/It looks like the last security vulnerabilities scan of this repository worked fine and no errors were produced./g
)
).toBeInTheDocument();
expect(
getByText(
screen.getByText(
/If you have arrived to this screen from an email listing some errors, please keep in mind those may have been already solved./g
)
).toBeInTheDocument();

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import ParamInfo from './ParamInfo';
@ -10,13 +11,7 @@ const defaultProps = {
};
describe('ParamInfo', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -27,43 +22,67 @@ describe('ParamInfo', () => {
describe('Render', () => {
it('renders component', () => {
const { getByText, getByTestId } = render(<ParamInfo {...defaultProps} />);
render(<ParamInfo {...defaultProps} />);
expect(getByText('element')).toBeInTheDocument();
expect(getByTestId('infoText')).toBeInTheDocument();
expect(getByText('this is a sample')).toBeInTheDocument();
expect(screen.getByText('element')).toBeInTheDocument();
expect(screen.getByTestId('infoText')).toBeInTheDocument();
expect(screen.getByText('this is a sample')).toBeInTheDocument();
});
it('displays info dropdown to enter on info text and hides on leave', async () => {
const { getByTestId } = render(<ParamInfo {...defaultProps} />);
jest.useFakeTimers();
expect(getByTestId('infoDropdown')).not.toHaveClass('visible');
render(<ParamInfo {...defaultProps} />);
fireEvent.mouseEnter(getByTestId('infoText'));
await waitFor(() => {
expect(getByTestId('infoDropdown')).toHaveClass('visible');
const infoDropdown = screen.getByTestId('infoDropdown');
expect(infoDropdown).not.toHaveClass('visible');
userEvent.hover(screen.getByTestId('infoText'));
act(() => {
jest.advanceTimersByTime(100);
});
fireEvent.mouseLeave(getByTestId('infoText'));
await waitFor(() => {
expect(getByTestId('infoDropdown')).not.toHaveClass('visible');
expect(infoDropdown).toHaveClass('visible');
userEvent.unhover(screen.getByTestId('infoText'));
act(() => {
jest.advanceTimersByTime(50);
});
expect(infoDropdown).not.toHaveClass('visible');
jest.useRealTimers();
});
it('hides info dropdown to leave it', async () => {
const { getByTestId } = render(<ParamInfo {...defaultProps} />);
fireEvent.mouseEnter(getByTestId('infoText'));
jest.useFakeTimers();
fireEvent.mouseEnter(getByTestId('infoDropdown'));
fireEvent.mouseLeave(getByTestId('infoText'));
await waitFor(() => {
expect(getByTestId('infoDropdown')).toHaveClass('visible');
render(<ParamInfo {...defaultProps} />);
const infoDropdown = screen.getByTestId('infoDropdown');
userEvent.hover(screen.getByTestId('infoText'));
userEvent.hover(screen.getByTestId('infoDropdown'));
userEvent.unhover(screen.getByTestId('infoText'));
act(() => {
jest.advanceTimersByTime(100);
});
fireEvent.mouseLeave(getByTestId('infoDropdown'));
await waitFor(() => {
expect(getByTestId('infoDropdown')).not.toHaveClass('visible');
expect(infoDropdown).toHaveClass('visible');
userEvent.unhover(screen.getByTestId('infoDropdown'));
act(() => {
jest.advanceTimersByTime(50);
});
expect(infoDropdown).not.toHaveClass('visible');
jest.useRealTimers();
});
});
});

View File

@ -1,4 +1,5 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import SchemaValuesSearch from './SchemaValuesSearch';
@ -11,13 +12,7 @@ const defaultProps = {
};
describe('SchemaValuesSearch', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
@ -27,49 +22,40 @@ describe('SchemaValuesSearch', () => {
});
it('renders component', () => {
const { getByTestId } = render(<SchemaValuesSearch {...defaultProps} />);
expect(getByTestId('typeaheadInput')).toBeInTheDocument();
render(<SchemaValuesSearch {...defaultProps} />);
expect(screen.getByTestId('typeaheadInput')).toBeInTheDocument();
});
it('displays options', () => {
const { getByTestId, getAllByTestId } = render(<SchemaValuesSearch {...defaultProps} />);
render(<SchemaValuesSearch {...defaultProps} />);
const input = getByTestId('typeaheadInput');
fireEvent.change(input, { target: { value: 'sub' } });
userEvent.type(screen.getByTestId('typeaheadInput'), 'sub');
expect(getAllByTestId('typeaheadDropdownBtn')).toHaveLength(2);
expect(screen.getAllByTestId('typeaheadDropdownBtn')).toHaveLength(2);
});
it('calls onSearch with selected path', () => {
const { getByTestId, getAllByTestId } = render(<SchemaValuesSearch {...defaultProps} />);
render(<SchemaValuesSearch {...defaultProps} />);
const input = getByTestId('typeaheadInput');
fireEvent.change(input, { target: { value: 'sub' } });
userEvent.type(screen.getByTestId('typeaheadInput'), 'sub');
const opts = getAllByTestId('typeaheadDropdownBtn');
fireEvent.click(opts[0]);
const opts = screen.getAllByTestId('typeaheadDropdownBtn');
userEvent.click(opts[0]);
expect(onSearchMock).toHaveBeenCalledTimes(1);
expect(onSearchMock).toHaveBeenCalledWith('path1.subpath1');
});
it('calls onSearch twice', async () => {
const { getByTestId, getAllByTestId } = render(
<SchemaValuesSearch {...defaultProps} activePath="path1.subpath1" />
);
render(<SchemaValuesSearch {...defaultProps} activePath="path1.subpath1" />);
const input = getByTestId('typeaheadInput');
fireEvent.change(input, { target: { value: 'sub' } });
userEvent.type(screen.getByTestId('typeaheadInput'), 'sub');
const opts = getAllByTestId('typeaheadDropdownBtn');
fireEvent.click(opts[0]);
const opts = screen.getAllByTestId('typeaheadDropdownBtn');
userEvent.click(opts[0]);
await waitFor(() => {
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 10);
expect(onSearchMock).toHaveBeenCalledTimes(2);
expect(onSearchMock).toHaveBeenNthCalledWith(1, undefined);
expect(onSearchMock).toHaveBeenNthCalledWith(2, 'path1.subpath1');
});
await waitFor(() => expect(onSearchMock).toHaveBeenCalledTimes(2));
expect(onSearchMock).toHaveBeenNthCalledWith(1, undefined);
expect(onSearchMock).toHaveBeenNthCalledWith(2, 'path1.subpath1');
});
});

View File

@ -11,17 +11,13 @@ const alertSample: Alert = {
};
describe('alertDispatcher', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
jest.resetAllMocks();
});
it('receives alert after subscription', async () => {
jest.useFakeTimers();
alertDispatcher.subscribe({
updateAlertWrapper: (alert: Alert | null) => subscriptionMock(alert),
});
@ -35,22 +31,45 @@ describe('alertDispatcher', () => {
alertDispatcher.postAlert(null);
expect(clearTimeout).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(subscriptionMock).toHaveBeenCalledTimes(2);
expect(subscriptionMock).toHaveBeenCalledWith(null);
await waitFor(() => expect(subscriptionMock).toHaveBeenCalledWith(null));
expect(subscriptionMock).toHaveBeenCalledTimes(2);
jest.useRealTimers();
});
it('dismiss alert when default time has finished', async () => {
jest.useFakeTimers();
alertDispatcher.subscribe({
updateAlertWrapper: (alert: Alert | null) => subscriptionMock(alert),
});
alertDispatcher.postAlert(alertSample);
expect(subscriptionMock).toHaveBeenCalledTimes(1);
expect(subscriptionMock).toHaveBeenCalledWith(alertSample);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 5000);
await waitFor(() => expect(subscriptionMock).toHaveBeenCalledWith(null));
expect(subscriptionMock).toHaveBeenCalledTimes(2);
jest.useRealTimers();
});
it('dismiss alert with custom time', async () => {
jest.useFakeTimers();
alertDispatcher.subscribe({
updateAlertWrapper: (alert: Alert | null) => subscriptionMock(alert),
});
alertDispatcher.postAlert({ ...alertSample, dismissOn: 3000 });
await waitFor(() => {
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 3000);
});
await waitFor(() => expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 3000));
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
});

View File

@ -42,6 +42,19 @@ describe('userNotificationsDispatcher', () => {
jest.resetAllMocks();
});
it('starts dispatcher', async () => {
userNotificationsDispatcher.start({
lastDisplayedTime: null,
enabled: true,
displayed: [],
});
await waitFor(() => {
expect(updateUserNotificationMock).toHaveBeenCalledTimes(2);
expect(updateUserNotificationMock).toHaveBeenLastCalledWith(null);
});
});
it('dismiss notification', async () => {
userNotificationsDispatcher.updateSettings({
lastDisplayedTime: null,

249
yarn.lock Normal file
View File

@ -0,0 +1,249 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/code-frame@^7.10.4":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
dependencies:
"@babel/highlight" "^7.12.13"
"@babel/helper-validator-identifier@^7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
"@babel/highlight@^7.12.13":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/runtime-corejs3@^7.10.2":
version "7.13.17"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.13.17.tgz#9baf45f03d4d013f021760b992d6349a9d27deaf"
integrity sha512-RGXINY1YvduBlGrP+vHjJqd/nK7JVpfM4rmZLGMx77WoL3sMrhheA0qxii9VNn1VHnxJLEyxmvCB+Wqc+x/FMw==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5":
version "7.13.17"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec"
integrity sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==
dependencies:
regenerator-runtime "^0.13.4"
"@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@testing-library/dom@^7.30.3":
version "7.30.3"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519"
integrity sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^4.2.0"
aria-query "^4.2.2"
chalk "^4.1.0"
dom-accessibility-api "^0.5.4"
lz-string "^1.4.4"
pretty-format "^26.6.2"
"@testing-library/user-event@^13.1.5":
version "13.1.5"
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.1.5.tgz#1ce11c37bf4df8f264fb7999ded7139e092a29bd"
integrity sha512-dD1FRHuWhfdcnb6H9/oaIIZHx9LQKGxbTtYV3i5Zru8I3GWWJoG2WtlAlXZ/56djO+6TvfsWPj5cXQvoTFQATQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@types/aria-query@^4.2.0":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.1.tgz#78b5433344e2f92e8b306c06a5622c50c245bf6b"
integrity sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
"@types/istanbul-lib-report@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
dependencies:
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-reports@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821"
integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==
dependencies:
"@types/istanbul-lib-report" "*"
"@types/node@*":
version "14.14.41"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615"
integrity sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==
"@types/yargs-parser@*":
version "20.2.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9"
integrity sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==
"@types/yargs@^15.0.0":
version "15.0.13"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.13.tgz#34f7fec8b389d7f3c1fd08026a5763e072d3c6dc"
integrity sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==
dependencies:
"@types/yargs-parser" "*"
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
dependencies:
"@babel/runtime" "^7.10.2"
"@babel/runtime-corejs3" "^7.10.2"
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
core-js-pure@^3.0.0:
version "3.10.2"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.10.2.tgz#065304f8547bf42008d4528dfff973c38bd6a332"
integrity sha512-uu18pVHQ21n4mzfuSlCXpucu5VKsck3j2m5fjrBOBqqdgWAxwdCgUuGWj6cDDPN1zLj/qtiqKvBMxWgDeeu49Q==
dom-accessibility-api@^0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166"
integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
dependencies:
"@jest/types" "^26.6.2"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^17.0.1"
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"