Skip to content
Open
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
@@ -1,6 +1,7 @@
import { waitFor, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FormProvider, useForm } from 'react-hook-form';
import { Link } from 'react-router-dom';
import { PreloadedState } from '@reduxjs/toolkit';
import { vi, type Mock } from 'vitest';

Expand All @@ -10,6 +11,7 @@ import {
mockedAppletId,
mockedCurrentWorkspace,
mockedFullSubjectId1,
mockedFullSubjectId2,
} from 'shared/mock';
import { Roles, JEST_TEST_TIMEOUT, MAX_LIMIT, ParticipantTag } from 'shared/consts';
import { initialStateData } from 'shared/state';
Expand All @@ -27,6 +29,7 @@ const activity2Id = '2';
const routePath = page.appletParticipantActivityDetailsDataReview;
const route1 = `/dashboard/${mockedAppletId}/participants/${mockedFullSubjectId1}/activities/${activity1Id}/responses?selectedDate=2023-12-27`;
const route2 = `/dashboard/${mockedAppletId}/participants/${mockedFullSubjectId1}/activities/${activity2Id}/responses?selectedDate=2023-12-15&answerId=answer-id-2-2&isFeedbackVisible=true`;
const route3 = `/dashboard/${mockedAppletId}/participants/${mockedFullSubjectId2}/activities/${activity1Id}/responses?selectedDate=2023-12-27`; // route1 with different participant
const routeWithoutSelectedDate = `/dashboard/${mockedAppletId}/participants/${mockedFullSubjectId1}/activities/${activity1Id}/responses`;
const preloadedState: PreloadedState<RootState> = {
workspaces: {
Expand Down Expand Up @@ -784,6 +787,74 @@ describe('RespondentDataReview', () => {
});
});

test('renders component after navigation to different participant', async () => {
const getMock = authApiClient.get as unknown as Mock;
getMock.mockImplementation((url: string) => {
if (url.endsWith(`/answers/applet/${mockedAppletId}/dates`)) {
return Promise.resolve(mockedGetWithDates);
}
if (url.endsWith(`/answers/applet/${mockedAppletId}/review/flows`)) {
return Promise.resolve(mockedGetWithFlows1);
}
if (url.endsWith(`/answers/applet/${mockedAppletId}/review/activities`)) {
return Promise.resolve(mockedGetWithActivities2);
}
if (url.includes('/assessment')) {
return Promise.resolve(mockAssessment);
}

return Promise.resolve(mockedGetWithResponses);
});

const getDecryptedActivityDataMock = vi.fn().mockReturnValue(mockDecryptedActivityData);

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
dashboardHooks.useDecryptedActivityData.mockReturnValue(getDecryptedActivityDataMock);

window.HTMLElement.prototype.scrollTo = () => {};

renderWithProviders(
<>
<Link to={route3} data-testid="navigate-to-participant-2">
Navigate
</Link>
<RespondentDataReviewWithForm />
</>,
{
preloadedState,
route: route1,
routePath,
},
);

await waitFor(() => {
expect(authApiClient.get).toHaveBeenCalledWith(
`/answers/applet/${mockedAppletId}/review/activities`,
expect.objectContaining({
params: expect.objectContaining({
createdDate: '2023-12-27',
targetSubjectId: mockedFullSubjectId1,
}),
}),
);
});

await userEvent.click(screen.getByTestId('navigate-to-participant-2'));

await waitFor(() => {
expect(authApiClient.get).toHaveBeenCalledWith(
`/answers/applet/${mockedAppletId}/review/activities`,
expect.objectContaining({
params: expect.objectContaining({
createdDate: '2023-12-27',
targetSubjectId: mockedFullSubjectId2,
}),
}),
);
});
});

test('test if default review date is equal to last activity completed date', async () => {
// This test verifies that when a route includes a selectedDate matching the user's
// lastSeen/lastActivityCompleted date, the date picker component renders correctly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import { ReviewMenu } from './ReviewMenu';
import { useActivityAnswersAndAssessment } from './hooks/useActivityAnswersAndAssessment/useActivityAnswersAndAssessment';
import { useReviewActivitiesAndFlows } from './hooks/useReviewActivitiesAndFlows/useReviewActivitiesAndFlows';

export const RespondentDataReview = () => {
const RespondentDataReviewInner = () => {
const { appletId, subjectId, activityId, activityFlowId } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const answerId = searchParams.get('answerId') || null;
Expand Down Expand Up @@ -271,11 +271,15 @@ export const RespondentDataReview = () => {
}

const selectedDate = parseDateToMidnightLocal(selectedDateParam);
if (
prevSelectedDateRef.current !== selectedDateParam &&
responseDates.some((date) => date.getTime() === selectedDate.getTime())
) {
handleSetInitialDate(selectedDate);
if (prevSelectedDateRef.current !== selectedDateParam) {
// Set last submit date if selected date is unavailable
const hasSelectedDate = responseDates.some(
(date) => date.getTime() === selectedDate.getTime(),
);

handleSetInitialDate(
hasSelectedDate ? selectedDate : responseDates[responseDates.length - 1],
);
}
}, [responseDates, selectedDateParam, handleSetInitialDate]);

Expand Down Expand Up @@ -379,3 +383,11 @@ export const RespondentDataReview = () => {
</StyledContainer>
);
};

export const RespondentDataReview = () => {
const { subjectId, activityId, activityFlowId } = useParams();

// Switching participant or activity keeps the same route pattern, so remount to
// drop the previous selection instead of leaving stale dates, answers and responses
return <RespondentDataReviewInner key={`${subjectId}-${activityId ?? activityFlowId}`} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ describe('Review Menu Item component', () => {
});
});

test('renders and functions correctly when an answer ID is present in the route and an answer is already selected', async () => {
renderComponent(routeWithAnswerId, { isSelected: true, selectedAnswer: latestAnswer });

await waitFor(() => {
expect(mockedOnSelectAnswer).toHaveBeenCalledWith({
answer: preselectedAnswer,
isRouteCreated: true,
});
});
expect(mockedOnSelectItem).toHaveBeenCalledWith(mockedActivity);
});

test('renders with already selected activity', async () => {
renderComponent(routeWithoutAnswerId, { isSelected: true });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,17 @@ export const ReviewMenuItem = ({
if (!answerId && !submitId) return;

const answerByRoute = answerDates.find(
(answer) => answer.answerId === answerId || answer.submitId === submitId,
(answer) =>
(answerId && answer.answerId === answerId) || (submitId && answer.submitId === submitId),
);
if (!answerByRoute) return;

setIsOpen(true);

if (selectedAnswer) return;
const isAnswerByRouteSelected =
(answerId && selectedAnswer?.answerId === answerId) ||
(submitId && selectedAnswer?.submitId === submitId);
if (isAnswerByRouteSelected) return;

onSelectAnswer({ answer: answerByRoute, isRouteCreated: true });
onSelectItem(item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { vi } from 'vitest';

import { mockedAlert, mockedFullSubjectId1 } from 'shared/mock';
import { renderWithProviders } from 'shared/utils/renderWithProviders';
import * as useEncryptionStorageFunc from 'shared/hooks/useEncryptionStorage';

import { Notification } from './Notification';

Expand Down Expand Up @@ -82,12 +81,7 @@ describe('Notification', () => {
fireEvent.click(screen.getByTestId(`notification-${mockedAlert.id}`));
expect(mockedSetCurrentId).toBeCalledWith('');
});
test('should navigate when click on response data button without existed encryption', async () => {
const mockedgGetAppletPrivateKey = vi.fn().mockReturnValue('');
vi.spyOn(useEncryptionStorageFunc, 'useEncryptionStorage').mockReturnValue({
getAppletPrivateKey: mockedgGetAppletPrivateKey,
});

test('should navigate to the answer associated with the alert when click on response data button', async () => {
renderWithProviders(
<Notification
{...{
Expand All @@ -104,35 +98,9 @@ describe('Notification', () => {
expect(button).toBeInTheDocument();
await userEvent.click(button);

expect(mockedUseNavigate).toBeCalledWith(
`/dashboard/${mockedAlert.appletId}/participants/${mockedFullSubjectId1}/activities/${mockedAlert.activityId}/responses`,
);
});

test('should navigate when click on response data button with existed encryption', async () => {
const mockedgGetAppletPrivateKey = vi.fn().mockReturnValue('123');
vi.spyOn(useEncryptionStorageFunc, 'useEncryptionStorage').mockReturnValue({
getAppletPrivateKey: mockedgGetAppletPrivateKey,
expect(mockedUseNavigate).toBeCalledWith({
pathname: `/dashboard/${mockedAlert.appletId}/participants/${mockedFullSubjectId1}/activities/${mockedAlert.activityId}/responses`,
search: `selectedDate=2023-08-03&answerId=${mockedAlert.answerId}`,
});

renderWithProviders(
<Notification
{...{
...mockedAlert,
currentId: mockedAlert.id,
setCurrentId: mockedSetCurrentId,
}}
/>,
);

const button = screen.getByRole('button', {
name: /takeMeToTheResponseData/i,
});
expect(button).toBeInTheDocument();
await userEvent.click(button);

expect(mockedUseNavigate).toBeCalledWith(
`/dashboard/${mockedAlert.appletId}/participants/${mockedFullSubjectId1}/activities/${mockedAlert.activityId}/responses`,
);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { generatePath, useNavigate } from 'react-router-dom';
import { createSearchParams, generatePath, useNavigate } from 'react-router-dom';

import { useAppDispatch } from 'redux/store';
import { page } from 'resources';
Expand Down Expand Up @@ -44,6 +44,7 @@ export const Notification = ({
type,
activityId,
answerId,
createdAt,
}: NotificationProps) => {
const { t } = useTranslation('app');
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -74,14 +75,17 @@ export const Notification = ({
};

const navigateToResponseData = () => {
navigate(
generatePath(page.appletParticipantActivityDetailsDataReview, {
navigate({
pathname: generatePath(page.appletParticipantActivityDetailsDataReview, {
appletId,
subjectId,
activityId,
answerId,
}),
);
search: createSearchParams({
selectedDate: createdAt.substring(0, 10), // ISO 8601 date in UTC
Comment thread
divbzero marked this conversation as resolved.
...(answerId && { answerId }),
}).toString(),
});
};

return (
Expand Down
Loading