diff --git a/package.json b/package.json new file mode 100644 index 00000000..23b1fc70 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@testing-library/dom": "^7.30.3", + "@testing-library/user-event": "^13.1.5" + } +} diff --git a/web/src/layout/common/Alert.test.tsx b/web/src/layout/common/Alert.test.tsx index 2feeaa43..6592f70f 100644 --- a/web/src/layout/common/Alert.test.tsx +++ b/web/src/layout/common/Alert.test.tsx @@ -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(); + it('renders alert', async () => { + render(); + expect(await screen.findByRole('alert')).toBeInTheDocument(); + }); - const alertWrapper = getByTestId('alertWrapper'); + it('renders proper content', async () => { + const { rerender } = render(); + + const alertWrapper = screen.getByTestId('alertWrapper'); expect(alertWrapper).toBeInTheDocument(); expect(alertWrapper).not.toHaveClass('isAlertActive'); - expect(queryByRole('alert')).toBeNull(); + expect(screen.queryByRole('alert')).toBeNull(); rerender(); - 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(); + render(); - 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); }); diff --git a/web/src/layout/common/Alert.tsx b/web/src/layout/common/Alert.tsx index 6a70e41b..0c5f8f76 100644 --- a/web/src/layout/common/Alert.tsx +++ b/web/src/layout/common/Alert.tsx @@ -13,21 +13,25 @@ interface Props { const DEFAULT_ALERT_TYPE = 'warning'; const Alert: React.ElementType = (props: Props) => { - const [errorMessage, setErrorMessage] = useState(props.message); + const [errorMessage, setErrorMessage] = useState(null); const errorWrapper = useRef(null); const [isVisible, setIsVisible] = useState(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 (
{ - 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(); - expect(queryByRole('tooltip')).toBeNull(); + render(); + 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(); - 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(); - expect(queryByRole('tooltip')).toBeNull(); + render(); + 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(); + 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(); }); }); diff --git a/web/src/layout/common/ElementWithTooltip.test.tsx b/web/src/layout/common/ElementWithTooltip.test.tsx index 19e9a8c2..564d09f9 100644 --- a/web/src/layout/common/ElementWithTooltip.test.tsx +++ b/web/src/layout/common/ElementWithTooltip.test.tsx @@ -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(); + it('displays tooltip', async () => { + jest.useFakeTimers(); - const badge = getByTestId('elementWithTooltip'); - fireEvent.mouseEnter(badge); + render(); - 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(); + it('hides tooltip on mouse leave', async () => { + jest.useFakeTimers(); - const badge = getByTestId('elementWithTooltip'); - fireEvent.mouseEnter(badge); + render(); - 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(); + render(); - 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(); }); }); diff --git a/web/src/layout/common/OfficialBadge.test.tsx b/web/src/layout/common/OfficialBadge.test.tsx index 55b9e721..64468de7 100644 --- a/web/src/layout/common/OfficialBadge.test.tsx +++ b/web/src/layout/common/OfficialBadge.test.tsx @@ -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(); - expect(getByText('Official')).toBeInTheDocument(); + render(); + 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(); - expect(getByText('Official')).toBeInTheDocument(); + render(); + 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', () => { diff --git a/web/src/layout/common/OrganizationInfo.test.tsx b/web/src/layout/common/OrganizationInfo.test.tsx index fb9c384f..821b1455 100644 --- a/web/src/layout/common/OrganizationInfo.test.tsx +++ b/web/src/layout/common/OrganizationInfo.test.tsx @@ -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(); - expect(getByText(defaultProps.organizationName)).toBeInTheDocument(); - expect(getByTestId('orgLink')).toBeInTheDocument(); + render(); + expect(screen.getByText(defaultProps.organizationName)).toBeInTheDocument(); + expect(screen.getByTestId('orgLink')).toBeInTheDocument(); }); - it('calls history push to click org link', () => { - const { getByTestId } = render(); - fireEvent.click(getByTestId('orgLink')); + it('calls history push to click org link', async () => { + render(); + 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(); - fireEvent.mouseEnter(getByTestId('orgLink')); + render(); + 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(); - fireEvent.mouseEnter(getByTestId('orgLink')); + render(); + 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(); - fireEvent.mouseEnter(getByTestId('orgLink')); + render(); + userEvent.hover(screen.getByTestId('orgLink')); await waitFor(() => { expect(API.getOrganization).toHaveBeenCalledTimes(1); }); - await waitFor(() => { - expect(getByTestId('orgInfoDropdown')).toBeEmptyDOMElement(); - }); + expect(await screen.findByTestId('orgInfoDropdown')).toBeEmptyDOMElement(); }); }); diff --git a/web/src/layout/common/RepositoryInfo.test.tsx b/web/src/layout/common/RepositoryInfo.test.tsx index d3afd613..5cd3117d 100644 --- a/web/src/layout/common/RepositoryInfo.test.tsx +++ b/web/src/layout/common/RepositoryInfo.test.tsx @@ -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(); - expect(getAllByText(defaultProps.repository.displayName)).toHaveLength(2); - expect(getByTestId('repoLink')).toBeInTheDocument(); + render(); + expect(screen.getAllByText(defaultProps.repository.displayName)).toHaveLength(2); + expect(screen.getByTestId('repoLink')).toBeInTheDocument(); }); - it('calls history push to click repo link', () => { - const { getByTestId } = render(); - fireEvent.click(getByTestId('repoLink')); + it('calls history push to click repo link', async () => { + render(); + 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(); - 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(); + 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(); - fireEvent.mouseEnter(getByTestId('repoLink')); + jest.useFakeTimers(); - fireEvent.mouseEnter(getByTestId('repoInfoDropdown')); - fireEvent.mouseLeave(getByTestId('repoLink')); - await waitFor(() => { - expect(getByTestId('repoInfoDropdown')).toHaveClass('show'); + render(); + 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(); - expect(getByText('Verified Publisher')).toBeInTheDocument(); + render(); + expect(screen.getByText('Verified Publisher')).toBeInTheDocument(); }); }); diff --git a/web/src/layout/common/SecurityRating.test.tsx b/web/src/layout/common/SecurityRating.test.tsx index 356c39e9..e30c6bb4 100644 --- a/web/src/layout/common/SecurityRating.test.tsx +++ b/web/src/layout/common/SecurityRating.test.tsx @@ -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(); - expect(getByText('Images Security Rating')).toBeInTheDocument(); - expect(getByText('F')).toBeInTheDocument(); + render(); + 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( { onlyBadge /> ); - expect(getByText('A')).toBeInTheDocument(); + expect(screen.getByText('A')).toBeInTheDocument(); }); it('renders B label', () => { - const { getByText } = render( + render( { onlyBadge /> ); - expect(getByText('B')).toBeInTheDocument(); + expect(screen.getByText('B')).toBeInTheDocument(); }); it('renders C label', () => { - const { getByText } = render( + render( { onlyBadge /> ); - expect(getByText('C')).toBeInTheDocument(); + expect(screen.getByText('C')).toBeInTheDocument(); }); it('renders D label', () => { - const { getByText } = render( + render( { onlyBadge /> ); - expect(getByText('D')).toBeInTheDocument(); + expect(screen.getByText('D')).toBeInTheDocument(); }); it('renders F label', () => { - const { getByText } = render( + render( { onlyBadge /> ); - expect(getByText('F')).toBeInTheDocument(); + expect(screen.getByText('F')).toBeInTheDocument(); }); it('renders - label', () => { - const { getByText } = render( + render( { onlyBadge /> ); - expect(getByText('-')).toBeInTheDocument(); + expect(screen.getByText('-')).toBeInTheDocument(); }); it('does not full label when onlyBadge is true', () => { - const { queryByText } = render(); - expect(queryByText('Images Security Rating')).toBeNull(); + render(); + expect(screen.queryByText('Images Security Rating')).toBeNull(); }); it('does not render label', () => { diff --git a/web/src/layout/common/SignedBadge.test.tsx b/web/src/layout/common/SignedBadge.test.tsx index cfd555d3..7106f79c 100644 --- a/web/src/layout/common/SignedBadge.test.tsx +++ b/web/src/layout/common/SignedBadge.test.tsx @@ -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(); - expect(getByText('Signed')).toBeInTheDocument(); + render(); + 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(); - expect(getByText('Signed')).toBeInTheDocument(); + render(); + 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', () => { diff --git a/web/src/layout/common/VerifiedPublisherBadge.test.tsx b/web/src/layout/common/VerifiedPublisherBadge.test.tsx index c638930f..21a5be24 100644 --- a/web/src/layout/common/VerifiedPublisherBadge.test.tsx +++ b/web/src/layout/common/VerifiedPublisherBadge.test.tsx @@ -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(); - expect(getByText('Verified Publisher')).toBeInTheDocument(); + render(); + 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', () => { diff --git a/web/src/layout/common/userNotifications/index.test.tsx b/web/src/layout/common/userNotifications/index.test.tsx index 159b3f98..ffac57ed 100644 --- a/web/src/layout/common/userNotifications/index.test.tsx +++ b/web/src/layout/common/userNotifications/index.test.tsx @@ -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( ); - 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', () => { diff --git a/web/src/layout/controlPanel/ActionBtn.test.tsx b/web/src/layout/controlPanel/ActionBtn.test.tsx index 56eb55af..7f86ffe8 100644 --- a/web/src/layout/controlPanel/ActionBtn.test.tsx +++ b/web/src/layout/controlPanel/ActionBtn.test.tsx @@ -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(
button content
@@ -70,18 +65,18 @@ describe('ActionBtn', () => {
); - 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( { ); - 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( { ); - 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(); }); }); diff --git a/web/src/layout/controlPanel/repositories/Card.test.tsx b/web/src/layout/controlPanel/repositories/Card.test.tsx index 5ffe16e9..6b10d017 100644 --- a/web/src/layout/controlPanel/repositories/Card.test.tsx +++ b/web/src/layout/controlPanel/repositories/Card.test.tsx @@ -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( ); - 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( ); - 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( ); - 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( ); - 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( ); - 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( ); - 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( ); - 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( ); - 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', () => { ); - 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', () => { ); - 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', () => { ); - 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', () => { ); - 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(); diff --git a/web/src/layout/package/chartTemplates/ParamInfo.test.tsx b/web/src/layout/package/chartTemplates/ParamInfo.test.tsx index b6871666..99e4aeb0 100644 --- a/web/src/layout/package/chartTemplates/ParamInfo.test.tsx +++ b/web/src/layout/package/chartTemplates/ParamInfo.test.tsx @@ -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(); + render(); - 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(); + jest.useFakeTimers(); - expect(getByTestId('infoDropdown')).not.toHaveClass('visible'); + render(); - 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(); - fireEvent.mouseEnter(getByTestId('infoText')); + jest.useFakeTimers(); - fireEvent.mouseEnter(getByTestId('infoDropdown')); - fireEvent.mouseLeave(getByTestId('infoText')); - await waitFor(() => { - expect(getByTestId('infoDropdown')).toHaveClass('visible'); + render(); + + 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(); }); }); }); diff --git a/web/src/layout/package/valuesSchema/SchemaValuesSearch.test.tsx b/web/src/layout/package/valuesSchema/SchemaValuesSearch.test.tsx index c79a01a4..40ca035b 100644 --- a/web/src/layout/package/valuesSchema/SchemaValuesSearch.test.tsx +++ b/web/src/layout/package/valuesSchema/SchemaValuesSearch.test.tsx @@ -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(); - expect(getByTestId('typeaheadInput')).toBeInTheDocument(); + render(); + expect(screen.getByTestId('typeaheadInput')).toBeInTheDocument(); }); it('displays options', () => { - const { getByTestId, getAllByTestId } = render(); + render(); - 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(); + render(); - 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( - - ); + render(); - 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'); }); }); diff --git a/web/src/utils/alertDispatcher.test.tsx b/web/src/utils/alertDispatcher.test.tsx index bc76df76..d701001f 100644 --- a/web/src/utils/alertDispatcher.test.tsx +++ b/web/src/utils/alertDispatcher.test.tsx @@ -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(); }); }); diff --git a/web/src/utils/userNotificationsDispatcher.test.tsx b/web/src/utils/userNotificationsDispatcher.test.tsx index 16771f19..b62bd6da 100644 --- a/web/src/utils/userNotificationsDispatcher.test.tsx +++ b/web/src/utils/userNotificationsDispatcher.test.tsx @@ -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, diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..eb4c73c9 --- /dev/null +++ b/yarn.lock @@ -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"