Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADM-747: [frontend] feat: handle error #968

Merged
merged 14 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,61 @@ import userEvent from '@testing-library/user-event';
import { act } from 'react-dom/test-utils';
import React from 'react';

const mockNotifications = [
{ id: '1', title: 'Notification', message: 'Notification Message 1' },
{
id: '2',
title: 'Notification',
message: 'Notification Message 2',
},
];
describe('Notification', () => {
const closeNotificationProps = {
open: false,
title: 'NotificationPopper',
message: 'Notification Message',
closeAutomatically: false,
};
const openNotificationProps = {
open: true,
title: 'NotificationPopper',
message: 'Notification Message',
closeAutomatically: false,
};
const { result } = renderHook(() => useNotificationLayoutEffect());

it('should show title and message given the "open" value is true', () => {
it('should render all notifications correctly', () => {
act(() => {
result.current.notificationProps = openNotificationProps;
result.current.notifications = mockNotifications;
});
render(<Notification {...result.current} />);

expect(screen.getByText('NotificationPopper')).toBeInTheDocument();
expect(screen.getByText('Notification Message')).toBeInTheDocument();
expect(screen.queryAllByText('Notification')).toHaveLength(2);
expect(screen.getByText('Notification Message 1')).toBeInTheDocument();
expect(screen.getByText('Notification Message 2')).toBeInTheDocument();
});

it('should not show title and message given the "open" value is false', () => {
it('should call closeNotification with corresponding id when clicking close button', async () => {
act(() => {
result.current.notificationProps = closeNotificationProps;
});
render(<Notification {...result.current} />);

expect(screen.queryByText('NotificationPopper')).not.toBeInTheDocument();
expect(screen.queryByText('Notification Message')).not.toBeInTheDocument();
});

it('should call updateProps when clicking close button given the "open" value is true', async () => {
act(() => {
result.current.notificationProps = openNotificationProps;
result.current.updateProps = jest.fn();
result.current.notifications = mockNotifications;
result.current.closeNotification = jest.fn();
});

render(<Notification {...result.current} />);

const closeButton = screen.getByRole('button', { name: 'Close' });
const closeButton = screen.getAllByRole('button', { name: 'Close' });

await userEvent.click(closeButton);
await userEvent.click(closeButton[0]);

await waitFor(() => {
expect(result.current.updateProps).toBeCalledWith(closeNotificationProps);
expect(result.current.closeNotification).toBeCalledWith('1');
});
});

it.each`
type | backgroundColor | icon | iconColor | borderColor
${'error'} | ${'#FFE7EA'} | ${'CancelIcon'} | ${'#D74257'} | ${'#F3B6BE'}
${'success'} | ${'#EFFFF1'} | ${'CheckCircleIcon'} | ${'#5E9E66'} | ${'#CFE2D1'}
${'warning'} | ${'#FFF4E3'} | ${'InfoIcon'} | ${'#D78D20'} | ${'#F3D5A9'}
${'info'} | ${'#E9ECFF'} | ${'InfoIcon'} | ${'#4050B5'} | ${'#939DDA'}
type | title | backgroundColor | icon | iconColor | borderColor
${'error'} | ${'Something went wrong!'} | ${'#FFE7EA'} | ${'CancelIcon'} | ${'#D74257'} | ${'#F3B6BE'}
${'success'} | ${'Successfully completed!'} | ${'#EFFFF1'} | ${'CheckCircleIcon'} | ${'#5E9E66'} | ${'#CFE2D1'}
${'warning'} | ${'Please note that'} | ${'#FFF4E3'} | ${'InfoIcon'} | ${'#D78D20'} | ${'#F3D5A9'}
${'info'} | ${'Help Information'} | ${'#E9ECFF'} | ${'InfoIcon'} | ${'#4050B5'} | ${'#939DDA'}
`(
`should render background color $backgroundColor and $icon in $iconColor given the "type" value is $type`,
async ({ type, backgroundColor, icon, iconColor, borderColor }) => {
`should render title $title background color $backgroundColor, $icon in $iconColor, border color $borderColor given the "type" value is $type`,
async ({ type, title, backgroundColor, icon, iconColor, borderColor }) => {
act(() => {
result.current.notificationProps = { ...openNotificationProps, type };
result.current.notifications = [{ id: '1', message: 'Notification Message 1', type }];
});

render(<Notification {...result.current} />);

expect(screen.getByText(title)).toBeInTheDocument();
const alertElement = screen.getByRole('alert');
expect(alertElement).toHaveStyle({ 'background-color': backgroundColor });
expect(alertElement).toHaveStyle({ border: `0.0625rem solid ${borderColor}` });
Expand Down
10 changes: 5 additions & 5 deletions frontend/__tests__/containers/MetricsStep/MetricsStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import {
CYCLE_TIME_SETTINGS_SECTION,
DEPLOYMENT_FREQUENCY_SETTINGS,
LIST_OPEN,
MOCK_BUILD_KITE_GET_INFO_RESPONSE,
MOCK_JIRA_VERIFY_RESPONSE,
MOCK_PIPELINE_GET_INFO_URL,
REAL_DONE,
REAL_DONE_SETTING_SECTION,
REQUIRED_DATA_LIST,
SELECT_CONSIDER_AS_DONE_MESSAGE,
MOCK_PIPELINE_GET_INFO_URL,
MOCK_BUILD_KITE_GET_INFO_RESPONSE,
} from '../../fixtures';
import { saveCycleTimeSettings, saveDoneColumn } from '@src/context/Metrics/metricsSlice';
import { updateJiraVerifyResponse, updateMetrics } from '@src/context/config/configSlice';
Expand Down Expand Up @@ -93,9 +93,9 @@ describe('MetricsStep', () => {
expect(getByText(DEPLOYMENT_FREQUENCY_SETTINGS)).toBeInTheDocument();
});

it('should call resetProps when resetProps is not undefined', async () => {
it('should call closeAllNotifications', async () => {
act(() => {
result.current.resetProps = jest.fn();
result.current.closeAllNotifications = jest.fn();
});

await waitFor(() =>
Expand All @@ -106,7 +106,7 @@ describe('MetricsStep', () => {
),
);

expect(result.current.resetProps).toBeCalled();
expect(result.current.closeAllNotifications).toBeCalled();
});

describe('with pre-filled cycle time data', () => {
Expand Down
6 changes: 4 additions & 2 deletions frontend/__tests__/containers/ReportButtonGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect';
import { EXPORT_METRIC_DATA, MOCK_REPORT_RESPONSE } from '../fixtures';
import { ReportButtonGroup } from '@src/containers/ReportButtonGroup';
import { render, screen } from '@testing-library/react';
import { render, renderHook, screen } from '@testing-library/react';

describe('test', () => {
const { result: notificationHook } = renderHook(() => useNotificationLayoutEffect());
const mockHandler = jest.fn();
const mockData = {
...MOCK_REPORT_RESPONSE,
Expand All @@ -25,6 +27,7 @@ describe('test', () => {
it('test', () => {
render(
<ReportButtonGroup
notification={notificationHook.current}
isShowSave={true}
isShowExportMetrics={true}
isShowExportBoardButton={true}
Expand All @@ -35,7 +38,6 @@ describe('test', () => {
startDate={''}
endDate={''}
csvTimeStamp={1239013}
setErrorMessage={mockHandler}
/>,
);

Expand Down
147 changes: 116 additions & 31 deletions frontend/__tests__/containers/ReportStep/ReportStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
BOARD_METRICS_TITLE,
CLASSIFICATION,
EMPTY_REPORT_VALUES,
ERROR_PAGE_ROUTE,
EXPORT_BOARD_DATA,
EXPORT_METRIC_DATA,
EXPORT_PIPELINE_DATA,
Expand Down Expand Up @@ -32,7 +31,6 @@ import { setupStore } from '../../utils/setupStoreUtil';
import userEvent from '@testing-library/user-event';
import ReportStep from '@src/containers/ReportStep';
import { MESSAGE } from '@src/constants/resources';
import { navigateMock } from '../../setupTests';
import { Provider } from 'react-redux';
import React from 'react';

Expand All @@ -44,7 +42,6 @@ jest.mock('@src/context/stepper/StepperSlice', () => ({
jest.mock('@src/hooks/useExportCsvEffect', () => ({
useExportCsvEffect: jest.fn().mockReturnValue({
fetchExportData: jest.fn(),
errorMessage: 'failed export csv',
isExpired: false,
}),
}));
Expand Down Expand Up @@ -76,7 +73,7 @@ jest.mock('@src/utils/util', () => ({
let store = null;
describe('Report Step', () => {
const { result: notificationHook } = renderHook(() => useNotificationLayoutEffect());
const { result: reportHook } = renderHook(() => useGenerateReportEffect());
const { result: reportHook } = renderHook(() => useGenerateReportEffect(notificationHook.current));
beforeEach(() => {
resetReportHook();
});
Expand All @@ -87,7 +84,6 @@ describe('Report Step', () => {
reportHook.current.startToRequestBoardData = jest.fn();
reportHook.current.startToRequestDoraData = jest.fn();
reportHook.current.stopPollingReports = jest.fn();
reportHook.current.isServerError = false;
reportHook.current.reportData = { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 };
};
const handleSaveMock = jest.fn();
Expand Down Expand Up @@ -229,44 +225,29 @@ describe('Report Step', () => {
expect(handleSaveMock).toHaveBeenCalledTimes(1);
});

it('should check error page show when isreportMetricsError is true', () => {
reportHook.current.isServerError = true;

setup([REQUIRED_DATA_LIST[1]]);

expect(navigateMock).toHaveBeenCalledWith(ERROR_PAGE_ROUTE);
});

it('should call updateProps when remaining time is less than or equal to 5 minutes', () => {
const resetProps = jest.fn();
const updateProps = jest.fn();
notificationHook.current.resetProps = resetProps;
notificationHook.current.updateProps = updateProps;
it('should call addNotification when remaining time is less than or equal to 5 minutes', () => {
const closeAllNotifications = jest.fn();
const addNotification = jest.fn();
notificationHook.current.closeAllNotifications = closeAllNotifications;
notificationHook.current.addNotification = addNotification;
jest.useFakeTimers();

setup(['']);

expect(updateProps).not.toBeCalledWith({
open: true,
expect(addNotification).not.toBeCalledWith({
title: MESSAGE.EXPIRE_INFORMATION(5),
closeAutomatically: true,
});

jest.advanceTimersByTime(500000);

expect(updateProps).not.toBeCalledWith({
open: true,
expect(addNotification).not.toBeCalledWith({
title: MESSAGE.EXPIRE_INFORMATION(5),
closeAutomatically: true,
});

jest.advanceTimersByTime(1000000);

expect(updateProps).toBeCalledWith({
open: true,
title: 'Help Information',
expect(addNotification).toBeCalledWith({
message: MESSAGE.EXPIRE_INFORMATION(5),
closeAutomatically: true,
});

jest.useRealTimers();
Expand Down Expand Up @@ -350,7 +331,7 @@ describe('Report Step', () => {
);

it('should call fetchExportData when clicking "Export pipeline data"', async () => {
const { result } = renderHook(() => useExportCsvEffect());
const { result } = renderHook(() => useExportCsvEffect(notificationHook.current));
setup([REQUIRED_DATA_LIST[6]]);

const exportButton = screen.getByText(EXPORT_PIPELINE_DATA);
Expand Down Expand Up @@ -387,7 +368,7 @@ describe('Report Step', () => {
);

it('should call fetchExportData when clicking "Export board data"', async () => {
const { result } = renderHook(() => useExportCsvEffect());
const { result } = renderHook(() => useExportCsvEffect(notificationHook.current));
setup([REQUIRED_DATA_LIST[2]]);

const exportButton = screen.getByText(EXPORT_BOARD_DATA);
Expand All @@ -413,7 +394,7 @@ describe('Report Step', () => {
});

it('should call fetchExportData when clicking "Export metric data"', async () => {
const { result } = renderHook(() => useExportCsvEffect());
const { result } = renderHook(() => useExportCsvEffect(notificationHook.current));
setup(['']);

const exportButton = screen.getByText(EXPORT_METRIC_DATA);
Expand All @@ -434,4 +415,108 @@ describe('Report Step', () => {
expect(screen.getByText('Export metric data')).toBeInTheDocument();
});
});

describe('error notification', () => {
const addNotification = jest.fn();
const timeoutError = 'time out error';
beforeEach(() => {
notificationHook.current.addNotification = addNotification;
});

it('should call addNotification when having timeout4Board error', () => {
reportHook.current.timeout4Board = timeoutError;

setup(REQUIRED_DATA_LIST);

expect(addNotification).toBeCalledWith({
message: MESSAGE.LOADING_TIMEOUT('Board metrics'),
type: 'error',
});
});

it('should call addNotification when having timeout4Dora error', () => {
reportHook.current.timeout4Dora = timeoutError;

setup(REQUIRED_DATA_LIST);

expect(addNotification).toBeCalledWith({
message: MESSAGE.LOADING_TIMEOUT('DORA metrics'),
type: 'error',
});
});

it('should call addNotification when having timeout4Report error', () => {
reportHook.current.timeout4Report = timeoutError;

setup(REQUIRED_DATA_LIST);

expect(addNotification).toBeCalledWith({
message: MESSAGE.LOADING_TIMEOUT('Report'),
type: 'error',
});
});

it('should call addNotification when having boardMetricsError', () => {
reportHook.current.reportData = {
...MOCK_REPORT_RESPONSE,
reportMetricsError: {
boardMetricsError: {
status: 400,
message: 'Board metrics error',
},
pipelineMetricsError: null,
sourceControlMetricsError: null,
},
};

setup(REQUIRED_DATA_LIST);

expect(addNotification).toBeCalledWith({
message: MESSAGE.FAILED_TO_GET_DATA('Board Metrics'),
type: 'error',
});
});

it('should call addNotification when having pipelineMetricsError', () => {
reportHook.current.reportData = {
...MOCK_REPORT_RESPONSE,
reportMetricsError: {
boardMetricsError: null,
pipelineMetricsError: {
status: 400,
message: 'Pipeline metrics error',
},
sourceControlMetricsError: null,
},
};

setup(REQUIRED_DATA_LIST);

expect(addNotification).toBeCalledWith({
message: MESSAGE.FAILED_TO_GET_DATA('Buildkite'),
type: 'error',
});
});

it('should call addNotification when having sourceControlMetricsError', () => {
reportHook.current.reportData = {
...MOCK_REPORT_RESPONSE,
reportMetricsError: {
boardMetricsError: null,
pipelineMetricsError: null,
sourceControlMetricsError: {
status: 400,
message: 'source control metrics error',
},
},
};

setup(REQUIRED_DATA_LIST);

expect(addNotification).toBeCalledWith({
message: MESSAGE.FAILED_TO_GET_DATA('Github'),
type: 'error',
});
});
});
});
Loading
Loading