Skip to content

Commit

Permalink
Merge branch 'task/tup-664-alt-ticket-form-resolution' into release/v…
Browse files Browse the repository at this point in the history
…1.1.4-20240122
  • Loading branch information
wesleyboar committed Jan 22, 2024
2 parents c58676c + b42256f commit 4b3fac0
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@

.ticket-reply-submission {
display: flex;
flex-direction: column;
justify-content: right;
align-items: center;
align-items: end;
}

.ticket-model-content {
Expand Down
164 changes: 142 additions & 22 deletions libs/tup-components/src/tickets/TicketDetailModal/TicketModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { vi } from 'vitest';

window.HTMLElement.prototype.scrollIntoView = vi.fn();

const resolvedStatus = 'resolved';
const unresolvedStatus = '';

describe('Ticket Modal', () => {
it('should render ticket history information and reply form', async () => {
const { getByText, getAllByText, getByTestId } = testRender(
Expand All @@ -31,7 +34,7 @@ describe('Ticket Modal', () => {
)
);
const { getByLabelText, getByRole } = testRender(
<TicketReplyForm ticketId="85411" />
<TicketReplyForm ticketId="85411" ticketStatus={resolvedStatus} />
);
const reply = getByLabelText(/Reply/);
await user.type(reply, 'it works!');
Expand All @@ -56,7 +59,7 @@ describe('Ticket Modal', () => {
)
);
const { getByLabelText, getByText, getByRole } = testRender(
<TicketReplyForm ticketId="85411" />
<TicketReplyForm ticketId="85411" ticketStatus={resolvedStatus} />
);

const reply = getByLabelText(/Reply/);
Expand All @@ -68,25 +71,142 @@ describe('Ticket Modal', () => {
expect(getByText('Something went wrong.')).toBeDefined()
);
});
});

it('should render an success message if an reply success is returned from the useMutation hook', async () => {
const user = userEvent.setup();
server.use(
rest.post('http://localhost:8001/tickets/85411/reply', (req, res, ctx) =>
res.once(ctx.status(200))
)
);
const { getByLabelText, getByText, getByRole } = testRender(
<TicketReplyForm ticketId="85411" />
);

const reply = getByLabelText(/Reply/);
const submit = getByRole('button', { name: 'Reply' });
await user.type(reply, 'success message?');
fireEvent.click(submit);

await waitFor(() =>
expect(getByText(/Your reply has been sent/)).toBeDefined()
);
it('should render an success message if an reply success is returned from the useMutation hook', async () => {
const user = userEvent.setup();
server.use(
rest.post('http://localhost:8001/tickets/85411/reply', (req, res, ctx) =>
res.once(ctx.status(200))
)
);
const { getByLabelText, getByText, getByRole } = testRender(
<TicketReplyForm ticketId="85411" ticketStatus={resolvedStatus} />
);

const reply = getByLabelText(/Reply/);
const submit = getByRole('button', { name: 'Reply' });
await user.type(reply, 'success message?');
fireEvent.click(submit);

await waitFor(() =>
expect(getByText(/Your reply has been sent/)).toBeDefined()
);
});

it('should disable the reply button on load and enable it when reply is entered', async () => {
const user = userEvent.setup();
server.use(
rest.post('http://localhost:8001/tickets/85411/reply', (req, res, ctx) =>
res.once(ctx.status(200))
)
);

const { getByLabelText, getByRole } = testRender(
<TicketReplyForm ticketId="85411" ticketStatus={unresolvedStatus} />
);

//If reply is empty, the button should be disabled
const replyButton = getByRole('button', {
name: 'Reply',
}) as HTMLButtonElement;
expect(replyButton.disabled).toBe(true);

//If reply is filled, button should be enabled
const reply = getByLabelText(/Reply/);
await user.type(reply, 'reply disabled?');
expect(replyButton.disabled).toBe(false);

//Changing back to empty reply
await user.clear(reply);
expect(replyButton.disabled).toBe(true);
});

it('should disable the reply button on load and enable it when checkbox is checked', async () => {
server.use(
rest.post('http://localhost:8001/tickets/85411/reply', (req, res, ctx) =>
res.once(ctx.status(200))
)
);

const { getByRole } = testRender(
<TicketReplyForm ticketId="85411" ticketStatus={unresolvedStatus} />
);

//If reply is empty, the button should be disabled
const replyButton = getByRole('button', {
name: 'Reply',
}) as HTMLButtonElement;
expect(replyButton.disabled).toBe(true);

//If checkbox is checked, button should be enabled
const checkbox = getByRole('checkbox', {
name: /My issue has been resolved/i,
}) as HTMLInputElement;
waitFor(() => {
fireEvent.click(checkbox);
});

expect(replyButton.disabled).toBe(false);

waitFor(() => {
fireEvent.click(checkbox);
});

expect(replyButton.disabled).toBe(true);
});

it('should remove required reply when checkbox is checked', () => {
server.use(
rest.post('http://localhost:8001/tickets/85411/reply', (req, res, ctx) =>
res.once(ctx.status(200))
)
);

const { getByRole, queryByText, getByText } = testRender(
<TicketReplyForm ticketId="85411" ticketStatus={unresolvedStatus} />
);

// The required label needs to be present
// Will result in error if not present - hence getbytext
getByText(/required/i);

//If checkbox is checked, button should be enabled
waitFor(() => {
const checkbox = getByRole('checkbox', {
name: /My issue has been resolved/i,
}) as HTMLInputElement;
fireEvent.click(checkbox);
});

// The required label should not be there - hence querybytext
const requiredLabelAfterClick = queryByText(/required/i);
expect(requiredLabelAfterClick).toBeNull();
});

it('should check the checkbox and disable it if status is resolved - replying will reopen the ticket should appear', async () => {
server.use(
rest.post('http://localhost:8001/tickets/85411/reply', (req, res, ctx) =>
res.once(ctx.status(200))
)
);

const { getByRole, getByText } = testRender(
//If ticket were not resolved, this test will fail
<TicketReplyForm ticketId="85411" ticketStatus={resolvedStatus} />
);

await waitFor(() => {
const checkbox = getByRole('checkbox', {
name: /My issue has been resolved/i,
}) as HTMLInputElement;

// Expect the checkbox to be checked
expect(checkbox.checked).toBe(true);

// Expect the checkbox to be disabled
expect(checkbox.disabled).toBe(true);
});

getByText(/replying will reopen this ticket/i);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ const TicketModal: React.FC<{ ticketId: string; baseRoute: string }> = ({
<TicketHistory ticketId={ticketId} />
</Col>
<Col lg="5">
<TicketReplyForm ticketId={ticketId} />
<TicketReplyForm
ticketId={ticketId}
ticketStatus={data?.Status || ''}
/>
</Col>
</Row>
</Container>
Expand Down
101 changes: 72 additions & 29 deletions libs/tup-components/src/tickets/TicketDetailModal/TicketReplyForm.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import React from 'react';

import { Formik, Form, FormikHelpers } from 'formik';
import {
FormikFileInput,
FormikTextarea,
FormikSelect,
} from '@tacc/core-wrappers';
import { Formik, Form, FormikHelpers, Field } from 'formik';
import { FormikFileInput, FormikTextarea } from '@tacc/core-wrappers';
import { FormGroup } from 'reactstrap';
import { Button, InlineMessage } from '@tacc/core-components';
import { useTicketReply } from '@tacc/tup-hooks';
import * as Yup from 'yup';
import './TicketModal.global.css';

interface TicketReplyFormValues {
Expand All @@ -18,13 +13,20 @@ interface TicketReplyFormValues {
status: string;
}

/* This validates the form for the first textarea in the form.
This will also show the red required text underneath the textarea.
If this is implemented, you must also add the `validationSchema={formSchema}` attribute to the Formik component.
***
const formSchema = Yup.object().shape({
text: Yup.string().required('Required'),
text: Yup.string().required('Required'),
});
***
*/

export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
ticketId,
}) => {
export const TicketReplyForm: React.FC<{
ticketId: string;
ticketStatus: string;
}> = ({ ticketId, ticketStatus }) => {
const mutation = useTicketReply(ticketId);
const { mutate, isSuccess, isLoading, isError } = mutation;

Expand All @@ -39,9 +41,24 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
{ resetForm }: FormikHelpers<TicketReplyFormValues>
) => {
const formData = new FormData();
formData.append('text', values['text']);
const isReplyEmpty = values.text.length === 0;

//if reply is empty and no checked box return
if (isReplyEmpty && !values.status) {
return;
}

if (isReplyEmpty) {
formData.append('text', '(Resolved with no reply.)');
} else {
formData.append('text', values['text']);
}

(values.files || []).forEach((file) => formData.append('files', file));
if (values.status) formData.append('status', values.status);
values.status
? formData.append('status', 'resolved')
: formData.append('status', '');

mutate(formData, {
onSuccess: () => resetForm(),
onSettled: () => {
Expand All @@ -55,11 +72,16 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
return (
<Formik
enableReinitialize
// enable this line 25 for form validation.
// validationSchema={formSchema}
initialValues={defaultValues}
validationSchema={formSchema}
onSubmit={onSubmit}
>
{({ isSubmitting, isValid }) => {
{({ values }) => {
const isReplyEmpty = values.text.length === 0;
const isResolved = ticketStatus === 'resolved';
const isChecked = values.status;

return (
<Form className="ticket-reply-form">
<FormikTextarea
Expand All @@ -68,12 +90,8 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
label="Reply"
description=""
style={{ maxWidth: '100%' }}
required
required={!isChecked}
/>
<FormikSelect name="status" label="Status">
<option value="">--</option>
<option value="resolved">Resolved</option>
</FormikSelect>
<FormikFileInput
name="files"
required={false}
Expand All @@ -82,8 +100,41 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
maxSizeMessage="Max File Size: 3MB"
maxSize={3145728}
/>

<div className="c-form__field">
<label>Ticket Status</label>
<menu>
<li>
<label>
<Field
type="checkbox"
name="status"
id="status"
checked={isResolved || values.status}
disabled={isResolved}
/>
My issue has been resolved
</label>
</li>
</menu>
<div className="c-form__help">
{isResolved
? '* Replying will reopen this ticket'
: 'This helps us determine which users still need assistance'}
</div>
</div>
<FormGroup className="ticket-reply-submission">
<Button
attr="submit"
type="primary"
disabled={isReplyEmpty && !isChecked}
isLoading={isLoading}
>
{isChecked
? isReplyEmpty
? 'Resolve'
: 'Resolve with Reply'
: 'Reply'}
</Button>
{isSuccess && (
<InlineMessage type="success">
Your reply has been sent.
Expand All @@ -94,14 +145,6 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
Something went wrong.
</InlineMessage>
)}
<Button
attr="submit"
type="primary"
disabled={!isValid || isSubmitting || isLoading || isError}
isLoading={isLoading}
>
Reply
</Button>
</FormGroup>
</Form>
);
Expand Down

0 comments on commit 4b3fac0

Please sign in to comment.