diff --git a/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.global.css b/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.global.css
index 11e202499..124c98c3b 100644
--- a/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.global.css
+++ b/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.global.css
@@ -33,8 +33,9 @@
.ticket-reply-submission {
display: flex;
+ flex-direction: column;
justify-content: right;
- align-items: center;
+ align-items: end;
}
.ticket-model-content {
diff --git a/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.test.tsx b/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.test.tsx
index c3ddede69..3057581d3 100644
--- a/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.test.tsx
+++ b/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.test.tsx
@@ -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(
@@ -31,7 +34,7 @@ describe('Ticket Modal', () => {
)
);
const { getByLabelText, getByRole } = testRender(
-
+
);
const reply = getByLabelText(/Reply/);
await user.type(reply, 'it works!');
@@ -56,7 +59,7 @@ describe('Ticket Modal', () => {
)
);
const { getByLabelText, getByText, getByRole } = testRender(
-
+
);
const reply = getByLabelText(/Reply/);
@@ -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(
-
- );
-
- 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(
+
+ );
+
+ 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(
+
+ );
+
+ //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(
+
+ );
+
+ //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(
+
+ );
+
+ // 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
+
+ );
+
+ 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);
+ });
});
diff --git a/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.tsx b/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.tsx
index 8bae9dbb7..97daba451 100644
--- a/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.tsx
+++ b/libs/tup-components/src/tickets/TicketDetailModal/TicketModal.tsx
@@ -46,7 +46,10 @@ const TicketModal: React.FC<{ ticketId: string; baseRoute: string }> = ({
-
+
diff --git a/libs/tup-components/src/tickets/TicketDetailModal/TicketReplyForm.tsx b/libs/tup-components/src/tickets/TicketDetailModal/TicketReplyForm.tsx
index 22dfdf9ab..7ccd5a88a 100644
--- a/libs/tup-components/src/tickets/TicketDetailModal/TicketReplyForm.tsx
+++ b/libs/tup-components/src/tickets/TicketDetailModal/TicketReplyForm.tsx
@@ -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 {
@@ -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;
@@ -39,9 +41,24 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
{ resetForm }: FormikHelpers
) => {
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: () => {
@@ -55,11 +72,16 @@ export const TicketReplyForm: React.FC<{ ticketId: string }> = ({
return (
- {({ isSubmitting, isValid }) => {
+ {({ values }) => {
+ const isReplyEmpty = values.text.length === 0;
+ const isResolved = ticketStatus === 'resolved';
+ const isChecked = values.status;
+
return (
);