Skip to content

Commit

Permalink
MPDX-8425 Require Task Action on Mass Actions (#1171)
Browse files Browse the repository at this point in the history
* Change result text

* Require task action if phase is selected

* Stop autohighlighting when field is opened

* Fix validation bug
  • Loading branch information
caleballdrin authored Nov 6, 2024
1 parent d8e2ed0 commit 01d172b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe('TasksMassActionsDropdown', () => {
queryByText,
getByLabelText,
getByRole,
findByLabelText,
findByRole,
} = render(<TaskComponents />);

expect(queryByText('Edit Tasks')).not.toBeInTheDocument();
Expand All @@ -120,7 +120,7 @@ describe('TasksMassActionsDropdown', () => {
'Appointment',
),
);
userEvent.click(await findByLabelText('Action'));
userEvent.click(await findByRole('combobox', { name: 'Action' }));
userEvent.click(
within(getByRole('listbox', { hidden: true, name: 'Action' })).getByText(
'In Person',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,47 +86,80 @@ describe('MassActionsEditTasksModal', () => {
);
expect(getByText('Blank fields will not be affected!')).toBeInTheDocument();
});
});
it('shows correct Action field options based on the Task Type', async () => {
const handleClose = jest.fn();
const { queryByText, findByRole } = render(
<LocalizationProvider dateAdapter={AdapterLuxon}>
<ThemeProvider theme={theme}>
<SnackbarProvider>
<GqlMockedProvider>
<MassActionsEditTasksModal
accountListId={accountListId}
ids={['task-1', 'task-2']}
selectedIdCount={2}
handleClose={handleClose}
/>
</GqlMockedProvider>
</SnackbarProvider>
</ThemeProvider>
</LocalizationProvider>,
);
const taskTypeField = await findByRole('combobox', { name: 'Task Type' });
const actionField = await findByRole('combobox', { name: 'Action' });

it('shows correct Action field options based on the Task Type', async () => {
const handleClose = jest.fn();
const { queryByText, findByRole } = render(
<LocalizationProvider dateAdapter={AdapterLuxon}>
<ThemeProvider theme={theme}>
<SnackbarProvider>
<GqlMockedProvider>
<MassActionsEditTasksModal
accountListId={accountListId}
ids={['task-1', 'task-2']}
selectedIdCount={2}
handleClose={handleClose}
/>
</GqlMockedProvider>
</SnackbarProvider>
</ThemeProvider>
</LocalizationProvider>,
);
const taskTypeField = await findByRole('combobox', { name: 'Task Type' });
const actionField = await findByRole('combobox', { name: 'Action' });
expect(actionField).toBeDisabled();

expect(actionField).toBeDisabled();
userEvent.click(taskTypeField);
userEvent.click(await findByRole('option', { name: 'Appointment' }));
userEvent.click(actionField);
userEvent.click(await findByRole('option', { name: 'In Person' }));

userEvent.click(taskTypeField);
userEvent.click(await findByRole('option', { name: 'Appointment' }));
userEvent.click(actionField);
userEvent.click(await findByRole('option', { name: 'In Person' }));
userEvent.click(taskTypeField);
userEvent.click(await findByRole('option', { name: 'Partner Care' }));
await waitFor(() => {
expect(queryByText('In Person')).not.toBeInTheDocument();
});
userEvent.click(actionField);
userEvent.click(await findByRole('option', { name: 'Digital Newsletter' }));

userEvent.click(taskTypeField);
userEvent.click(await findByRole('option', { name: 'Partner Care' }));
await waitFor(() => {
expect(queryByText('In Person')).not.toBeInTheDocument();
userEvent.clear(taskTypeField);
await waitFor(() => {
expect(queryByText('In Person')).not.toBeInTheDocument();
});
expect(actionField).toBeDisabled();
});
userEvent.click(actionField);
userEvent.click(await findByRole('option', { name: 'Digital Newsletter' }));

userEvent.clear(taskTypeField);
await waitFor(() => {
expect(queryByText('In Person')).not.toBeInTheDocument();
it('requires task action when task phase is selected', async () => {
const handleClose = jest.fn();
const { getByText, findByRole } = render(
<LocalizationProvider dateAdapter={AdapterLuxon}>
<ThemeProvider theme={theme}>
<SnackbarProvider>
<GqlMockedProvider>
<MassActionsEditTasksModal
accountListId={accountListId}
ids={['task-1', 'task-2']}
selectedIdCount={2}
handleClose={handleClose}
/>
</GqlMockedProvider>
</SnackbarProvider>
</ThemeProvider>
</LocalizationProvider>,
);
const taskTypeField = await findByRole('combobox', { name: 'Task Type' });
const actionField = await findByRole('combobox', { name: 'Action' });

userEvent.click(taskTypeField);
userEvent.click(await findByRole('option', { name: 'Appointment' }));

await waitFor(() => expect(getByText('Save')).toBeDisabled());

userEvent.click(taskTypeField);
userEvent.click(await findByRole('option', { name: 'Appointment' }));
userEvent.click(actionField);
userEvent.click(await findByRole('option', { name: 'In Person' }));

await waitFor(() => expect(getByText('Save')).not.toBeDisabled());
});
expect(actionField).toBeDisabled();
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactElement } from 'react';
import React, { ReactElement, useRef } from 'react';
import {
Alert,
Checkbox,
Expand Down Expand Up @@ -37,7 +37,13 @@ import { IncompleteWarning } from '../IncompleteWarning/IncompleteWarning';
const massActionsEditTasksSchema = yup.object({
subject: yup.string().nullable(),
taskPhase: yup.mixed<PhaseEnum>().nullable(),
activityType: yup.mixed<ActivityTypeEnum>().nullable(),
activityType: yup
.mixed<ActivityTypeEnum>()
.nullable()
.when('taskPhase', {
is: (value: any) => !!value,
then: yup.mixed<ActivityTypeEnum>().required('Must select a Task Action'),
}),
userId: yup.string().nullable(),
startAt: nullableDateTime(),
noDueDate: yup.boolean().required(),
Expand All @@ -62,9 +68,14 @@ export const MassActionsEditTasksModal: React.FC<
const [createTaskComment] = useCreateTaskCommentMutation();
const { update } = useUpdateTasksQueries();
const { taskPhases, activitiesByPhase } = usePhaseData();
const activityRef = useRef<HTMLInputElement | null>(null);

const { enqueueSnackbar } = useSnackbar();

const focusActivity = (): void => {
setTimeout(() => activityRef?.current?.focus(), 50);
};

const onSubmit = async (fields: Attributes) => {
const { noDueDate, body } = fields;
const formattedFields: Partial<TaskUpdateInput> = {};
Expand Down Expand Up @@ -142,9 +153,12 @@ export const MassActionsEditTasksModal: React.FC<
},
handleChange,
handleSubmit,
handleBlur,
setFieldValue,
isSubmitting,
isValid,
touched,
errors,
}): ReactElement => (
<form onSubmit={handleSubmit} noValidate data-testid="EditTasksModal">
<DialogContent dividers>
Expand All @@ -170,7 +184,9 @@ export const MassActionsEditTasksModal: React.FC<
onChange={(phase) => {
setFieldValue('taskPhase', phase);
setFieldValue('activityType', '');
focusActivity();
}}
onBlur={handleBlur('taskPhase')}
/>
</Grid>
<Grid item xs={12} sm={6}>
Expand All @@ -185,6 +201,10 @@ export const MassActionsEditTasksModal: React.FC<
}
// None and null are distinct values: null leaves the action unchanged and None changes the action to None
preserveNone
required={!!taskPhase}
touched={touched}
errors={errors}
inputRef={activityRef}
/>
</Grid>
<Grid item xs={12} sm={6}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ export const ActivityTypeAutocomplete: React.FC<ActivityTypeProps> = ({
return (
<Autocomplete<ActivityTypeEnum>
openOnFocus
autoHighlight
autoSelect
value={value === null || typeof value === 'undefined' ? null : value}
options={sortedOptions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export const AssigneeAutocomplete: React.FC<AssigneeAutocompleteProps> = ({
return (
<Autocomplete
autoSelect
autoHighlight
options={users.map(({ user }) => user.id)}
getOptionLabel={(userId) => {
const user = users.find(({ user }) => user.id === userId)?.user;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/functions/getLocalizedResultStrings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const getLocalizedResultString = (
return t('No Response Yet');

case DisplayResultEnum.AppointmentResultNotInterested:
return t('Does not want to meet');
return t('Not Interested');

case DisplayResultEnum.InitiationResultCircleBack:
return t("Can't meet right now - circle back");
Expand Down

0 comments on commit 01d172b

Please sign in to comment.