diff --git a/bc_obps/registration/api/v2/_operations/_operation_id/_registration/operation.py b/bc_obps/registration/api/v2/_operations/_operation_id/_registration/operation.py
index 880ca8616a..99b77fdb40 100644
--- a/bc_obps/registration/api/v2/_operations/_operation_id/_registration/operation.py
+++ b/bc_obps/registration/api/v2/_operations/_operation_id/_registration/operation.py
@@ -31,4 +31,5 @@
def register_edit_operation_information(
request: HttpRequest, operation_id: UUID, payload: OperationInformationIn
) -> Tuple[Literal[200], Operation]:
+ # raise Exception('d')
return 200, OperationServiceV2.register_operation_information(get_current_user_guid(request), operation_id, payload)
diff --git a/bc_obps/registration/api/v2/_operations/_operation_id/_registration/submission.py b/bc_obps/registration/api/v2/_operations/_operation_id/_registration/submission.py
index 37202e6707..53ffdcf19c 100644
--- a/bc_obps/registration/api/v2/_operations/_operation_id/_registration/submission.py
+++ b/bc_obps/registration/api/v2/_operations/_operation_id/_registration/submission.py
@@ -27,6 +27,7 @@ def operation_registration_submission(
request: HttpRequest, operation_id: UUID, payload: OperationRegistrationSubmissionIn
) -> Tuple[Literal[200], Operation]:
# Check if all checkboxes are checked
+ # raise Exception('d')
if not all(
[payload.acknowledgement_of_review, payload.acknowledgement_of_information, payload.acknowledgement_of_records]
):
diff --git a/bciers/apps/registration/app/components/operations/registration/OperationInformationForm.tsx b/bciers/apps/registration/app/components/operations/registration/OperationInformationForm.tsx
index 37b7744804..925bc19444 100644
--- a/bciers/apps/registration/app/components/operations/registration/OperationInformationForm.tsx
+++ b/bciers/apps/registration/app/components/operations/registration/OperationInformationForm.tsx
@@ -4,7 +4,7 @@ import MultiStepBase from "@bciers/components/form/MultiStepBase";
import { OperationInformationFormData } from "apps/registration/app/components/operations/registration/types";
import { actionHandler } from "@bciers/actions";
import { RJSFSchema } from "@rjsf/utils";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { IChangeEvent } from "@rjsf/core";
import { getOperationV2 } from "@bciers/actions/api";
import {
@@ -13,7 +13,6 @@ import {
} from "@bciers/components/form/formDataUtils";
import { registrationOperationInformationUiSchema } from "@/registration/app/data/jsonSchema/operationInformation/registrationOperationInformation";
import { useRouter } from "next/navigation";
-import { UUID } from "crypto";
interface OperationInformationFormProps {
rawFormData: OperationInformationFormData;
@@ -31,18 +30,6 @@ const OperationInformationForm = ({
const router = useRouter();
const [selectedOperation, setSelectedOperation] = useState("");
const [error, setError] = useState(undefined);
- const [onSubmitSuccessfulResponse, setOnSubmitSuccessfulResponse] = useState<
- { id: UUID } | undefined
- >(undefined);
-
- useEffect(() => {
- if (onSubmitSuccessfulResponse) {
- const nextStepUrl = `/register-an-operation/${
- onSubmitSuccessfulResponse.id
- }/${step + 1}`;
- router.push(nextStepUrl);
- }
- }, [onSubmitSuccessfulResponse]);
const nestedFormData = rawFormData
? createNestedFormData(rawFormData, schema)
@@ -85,8 +72,17 @@ const OperationInformationForm = ({
{
body,
},
- );
- // errors are handled in MultiStepBase
+ ).then((resolve) => {
+ console.log("!!!!!resolve", resolve);
+ if (resolve?.error) {
+ return { error: resolve.error };
+ } else if (resolve?.id) {
+ // this form step needs a custom push (can't use the push in MultiStepBase) because the resolve.id is in the url
+ const nextStepUrl = `/register-an-operation/${resolve.id}/${step + 1}`;
+ router.push(nextStepUrl);
+ return resolve;
+ }
+ });
return response;
};
const handleSelectOperationChange = async (data: any) => {
@@ -108,7 +104,6 @@ const OperationInformationForm = ({
cancelUrl="/"
formData={formState}
onSubmit={handleSubmit}
- setOnSubmitSuccessfulResponse={setOnSubmitSuccessfulResponse}
schema={schema}
step={step}
steps={steps}
diff --git a/bciers/apps/registration/app/components/operations/registration/RegistrationSubmissionForm.tsx b/bciers/apps/registration/app/components/operations/registration/RegistrationSubmissionForm.tsx
index 43e38e2f44..0535bf1a27 100644
--- a/bciers/apps/registration/app/components/operations/registration/RegistrationSubmissionForm.tsx
+++ b/bciers/apps/registration/app/components/operations/registration/RegistrationSubmissionForm.tsx
@@ -24,8 +24,7 @@ const RegistrationSubmissionForm = ({
}: OperationRegistrationFormProps) => {
const [formState, setFormState] = useState({});
const [submitButtonDisabled, setSubmitButtonDisabled] = useState(true);
- const [onSubmitSuccessfulResponse, setOnSubmitSuccessfulResponse] =
- useState(undefined);
+ const [isSubmitted, setIsSubmitted] = useState(false);
const handleChange = (e: IChangeEvent) => {
setFormState(e.formData);
@@ -43,14 +42,22 @@ const RegistrationSubmissionForm = ({
...e.formData,
}),
},
- );
- // errors are handled in MultiStepBase
+ ).then((resolve) => {
+ if (resolve?.error) {
+ setSubmitButtonDisabled(false);
+ return { error: resolve.error };
+ } else {
+ setIsSubmitted(true);
+ return resolve;
+ }
+ });
+
return response;
};
return (
<>
- {onSubmitSuccessfulResponse ? (
+ {isSubmitted ? (
) : (
{
},
async () => {
fetchFormEnums();
- actionHandler.mockReturnValueOnce({ id: "uuid2", name: "Operation 2" });
+ actionHandler.mockResolvedValueOnce({
+ id: "b974a7fc-ff63-41aa-9d57-509ebe2553a4",
+ }); // mock the POST response from the submit handler
render(
{
},
);
});
- expect(mockPush).toHaveBeenCalledWith("/register-an-operation/uuid2/2");
+ expect(mockPush).toHaveBeenCalledWith(
+ "/register-an-operation/b974a7fc-ff63-41aa-9d57-509ebe2553a4/2",
+ );
},
);
diff --git a/bciers/libs/components/src/form/MultiStepBase.test.tsx b/bciers/libs/components/src/form/MultiStepBase.test.tsx
index f0fb7b7051..cc7165cffc 100644
--- a/bciers/libs/components/src/form/MultiStepBase.test.tsx
+++ b/bciers/libs/components/src/form/MultiStepBase.test.tsx
@@ -82,297 +82,297 @@ describe("The MultiStepBase component", () => {
).not.toBeInTheDocument();
});
- // it("makes the form editable when the Edit button is clicked", () => {
- // useParams.mockReturnValue({
- // formSection: "1",
- // operation: "create",
- // } as QueryParams);
- // render();
- // expect(screen.getByText(/test field1/i)).toHaveAttribute(
- // "class",
- // "read-only-widget whitespace-pre-line",
- // );
- // const editButton = screen.getByRole("button", { name: /Edit/i });
- // fireEvent.click(editButton);
- // // this confirms the form is editable because the label is accompanied by an
- // expect(screen.getByLabelText(/field1/i)).toHaveValue("test field1");
- // });
-
- // it("shows the header with correct steps (formSectionTitles) and no submission step", () => {
- // useParams.mockReturnValue({
- // formSection: "1",
- // operation: "create",
- // } as QueryParams);
- // render();
- // const headerSteps = screen.getAllByTestId(/multistep-header-title/i);
- // expect(headerSteps).toHaveLength(3);
- // expect(headerSteps[0]).toHaveTextContent(/page1/i);
- // expect(headerSteps[1]).toHaveTextContent(/page2/i);
- // expect(headerSteps[2]).toHaveTextContent(/page3/i);
- // });
-
- // it("navigation buttons work on first form page when no baseUrl is given", async () => {
- // mockOnSubmit.mockReturnValue({ id: "uuid" });
- // render(
- // ,
- // );
- // expect(screen.getByTestId("field-template-label")).toHaveTextContent(
- // "page1",
- // );
-
- // const saveAndContinueButton = screen.getByRole("button", {
- // name: /Save and Continue/i,
- // });
- // expect(screen.getByLabelText(/field1*/i)).toHaveValue("test field1");
- // expect(screen.getByRole("button", { name: /Back/i })).toBeDisabled();
- // expect(screen.getByRole("button", { name: /Cancel/i })).not.toBeDisabled();
- // expect(screen.getByRole("link", { name: /Cancel/i })).toHaveAttribute(
- // "href",
- // "cancelurl.com",
- // );
- // expect(saveAndContinueButton).not.toBeDisabled();
- // fireEvent.click(saveAndContinueButton);
- // expect(mockOnSubmit).toHaveBeenCalled();
- // expect(mockPush).not.toHaveBeenCalled();
- // });
-
- // it("navigation and submit buttons work on subsequent form page", async () => {
- // mockOnSubmit.mockReturnValue({ id: 1 });
- // render(
- // ,
- // );
- // expect(screen.getByTestId("field-template-label")).toHaveTextContent(
- // "page2",
- // );
-
- // const saveAndContinueButton = screen.getByRole("button", {
- // name: /Save and Continue/i,
- // });
- // expect(screen.getByRole("button", { name: /Back/i })).not.toBeDisabled();
- // expect(screen.getByRole("link", { name: /Back/i })).toHaveAttribute(
- // "href",
- // "baseurl.com/1",
- // );
- // expect(screen.getByRole("button", { name: /Cancel/i })).not.toBeDisabled();
- // expect(screen.getByRole("link", { name: /Cancel/i })).toHaveAttribute(
- // "href",
- // "cancelurl.com",
- // );
- // expect(saveAndContinueButton).not.toBeDisabled();
- // fireEvent.click(saveAndContinueButton);
- // expect(mockOnSubmit).toHaveBeenCalled();
- // await waitFor(() => {
- // expect(mockPush).toHaveBeenCalledWith("baseurl.com/3");
- // });
- // });
-
- // it("navigation and submit buttons work on last form page", async () => {
- // render(
- // ,
- // );
- // expect(screen.getByTestId("field-template-label")).toHaveTextContent(
- // "page3",
- // );
-
- // const submitButton = screen.getByRole("button", {
- // name: /test submit button text/i,
- // });
-
- // expect(screen.getByRole("button", { name: /Back/i })).not.toBeDisabled();
- // expect(screen.getByRole("link", { name: /Back/i })).toHaveAttribute(
- // "href",
- // "baseurl.com/2",
- // );
- // expect(screen.getByRole("button", { name: /Cancel/i })).not.toBeDisabled();
- // expect(screen.getByRole("link", { name: /Cancel/i })).toHaveAttribute(
- // "href",
- // "cancelurl.com",
- // );
- // expect(
- // screen.getByRole("button", { name: /test submit button text/i }),
- // ).not.toBeDisabled();
- // fireEvent.click(submitButton);
- // expect(mockOnSubmit).toHaveBeenCalled();
- // expect(mockPush).not.toHaveBeenCalled();
- // });
-
- // it("submission is disabled if form is still submitting", async () => {
- // useParams.mockReturnValue({
- // formSection: "1",
- // operation: "create",
- // } as QueryParams);
- // const user = userEvent.setup();
- // let resolve: (v: unknown) => void;
-
- // const mockPromiseOnSubmit = vitest.fn().mockReturnValue(
- // new Promise((_resolve) => {
- // resolve = _resolve;
- // }),
- // );
-
- // render(
- // ,
- // );
- // const saveAndContinueButton = screen.getByRole("button", {
- // name: /Save and Continue/i,
- // });
-
- // await act(async () => {
- // await user.click(saveAndContinueButton);
- // });
-
- // waitFor(() => {
- // expect(saveAndContinueButton).toBeDisabled();
- // });
-
- // await act(async () => {
- // resolve(vitest.fn);
- // });
-
- // expect(saveAndContinueButton).not.toBeDisabled();
- // });
-
- // it("shows an error if there was a problem with form validation", () => {
- // useParams.mockReturnValue({
- // formSection: "1",
- // operation: "create",
- // } as QueryParams);
-
- // render();
- // const saveAndContinueButton = screen.getByRole("button", {
- // name: /Save and Continue/i,
- // });
- // fireEvent.click(saveAndContinueButton);
- // expect(screen.getByRole("alert")).toBeVisible();
- // expect(mockOnSubmit).not.toHaveBeenCalled(); // submit function is not called because we hit validation errors first
- // });
-
- // it("shows an error if passed one", () => {
- // render(
- // ,
- // );
- // expect(screen.getByRole("alert")).toBeVisible();
- // expect(screen.getByText("Test error")).toBeVisible();
- // });
-
- // it("shows an error if response returns an error", async () => {
- // mockOnSubmit.mockReturnValueOnce({ error: "whoopsie" });
- // render(
- // ,
- // );
-
- // const saveAndContinueButton = screen.getByRole("button", {
- // name: /Save and Continue/i,
- // });
-
- // fireEvent.click(saveAndContinueButton);
- // await waitFor(() => {
- // expect(mockOnSubmit).toHaveBeenCalled();
- // expect(screen.getByRole("alert")).toBeVisible();
- // expect(screen.getByText("whoopsie")).toBeVisible();
- // });
- // });
-
- // it("clears old errors on submission", async () => {
- // render(
- // ,
- // );
- // const saveAndContinueButton = screen.getByRole("button", {
- // name: /Save and Continue/i,
- // });
- // act(() => {
- // fireEvent.click(saveAndContinueButton);
- // });
- // await waitFor(() => {
- // expect(mockOnSubmit).toHaveBeenCalled();
- // expect(screen.queryByRole("alert")).not.toBeInTheDocument();
- // });
- // });
-
- // it("calls the onChange prop when the form changes", () => {
- // useParams.mockReturnValue({
- // formSection: "1",
- // operation: "create",
- // } as QueryParams);
-
- // const changeHandler = vi.fn();
- // render(
- // ,
- // );
- // const input = screen.getByLabelText(/field1*/i);
- // fireEvent.change(input, { target: { value: "new value" } });
-
- // expect(changeHandler).toHaveBeenCalled();
- // });
-
- // it("renders children", () => {
- // useParams.mockReturnValue({
- // formSection: "1",
- // operation: "create",
- // } as QueryParams);
-
- // render(
- //
- // Test child
- // ,
- // );
- // expect(screen.getByTestId("test-child")).toBeVisible();
- // });
+ it("makes the form editable when the Edit button is clicked", () => {
+ useParams.mockReturnValue({
+ formSection: "1",
+ operation: "create",
+ } as QueryParams);
+ render();
+ expect(screen.getByText(/test field1/i)).toHaveAttribute(
+ "class",
+ "read-only-widget whitespace-pre-line",
+ );
+ const editButton = screen.getByRole("button", { name: /Edit/i });
+ fireEvent.click(editButton);
+ // this confirms the form is editable because the label is accompanied by an
+ expect(screen.getByLabelText(/field1/i)).toHaveValue("test field1");
+ });
+
+ it("shows the header with correct steps (formSectionTitles) and no submission step", () => {
+ useParams.mockReturnValue({
+ formSection: "1",
+ operation: "create",
+ } as QueryParams);
+ render();
+ const headerSteps = screen.getAllByTestId(/multistep-header-title/i);
+ expect(headerSteps).toHaveLength(3);
+ expect(headerSteps[0]).toHaveTextContent(/page1/i);
+ expect(headerSteps[1]).toHaveTextContent(/page2/i);
+ expect(headerSteps[2]).toHaveTextContent(/page3/i);
+ });
+
+ it("navigation buttons work on first form page when no baseUrl is given", async () => {
+ mockOnSubmit.mockReturnValue({ id: "uuid" });
+ render(
+ ,
+ );
+ expect(screen.getByTestId("field-template-label")).toHaveTextContent(
+ "page1",
+ );
+
+ const saveAndContinueButton = screen.getByRole("button", {
+ name: /Save and Continue/i,
+ });
+ expect(screen.getByLabelText(/field1*/i)).toHaveValue("test field1");
+ expect(screen.getByRole("button", { name: /Back/i })).toBeDisabled();
+ expect(screen.getByRole("button", { name: /Cancel/i })).not.toBeDisabled();
+ expect(screen.getByRole("link", { name: /Cancel/i })).toHaveAttribute(
+ "href",
+ "cancelurl.com",
+ );
+ expect(saveAndContinueButton).not.toBeDisabled();
+ fireEvent.click(saveAndContinueButton);
+ expect(mockOnSubmit).toHaveBeenCalled();
+ expect(mockPush).not.toHaveBeenCalled();
+ });
+
+ it("navigation and submit buttons work on subsequent form page", async () => {
+ mockOnSubmit.mockReturnValue({ id: 1 });
+ render(
+ ,
+ );
+ expect(screen.getByTestId("field-template-label")).toHaveTextContent(
+ "page2",
+ );
+
+ const saveAndContinueButton = screen.getByRole("button", {
+ name: /Save and Continue/i,
+ });
+ expect(screen.getByRole("button", { name: /Back/i })).not.toBeDisabled();
+ expect(screen.getByRole("link", { name: /Back/i })).toHaveAttribute(
+ "href",
+ "baseurl.com/1",
+ );
+ expect(screen.getByRole("button", { name: /Cancel/i })).not.toBeDisabled();
+ expect(screen.getByRole("link", { name: /Cancel/i })).toHaveAttribute(
+ "href",
+ "cancelurl.com",
+ );
+ expect(saveAndContinueButton).not.toBeDisabled();
+ fireEvent.click(saveAndContinueButton);
+ expect(mockOnSubmit).toHaveBeenCalled();
+ await waitFor(() => {
+ expect(mockPush).toHaveBeenCalledWith("baseurl.com/3");
+ });
+ });
+
+ it("navigation and submit buttons work on last form page", async () => {
+ render(
+ ,
+ );
+ expect(screen.getByTestId("field-template-label")).toHaveTextContent(
+ "page3",
+ );
+
+ const submitButton = screen.getByRole("button", {
+ name: /test submit button text/i,
+ });
+
+ expect(screen.getByRole("button", { name: /Back/i })).not.toBeDisabled();
+ expect(screen.getByRole("link", { name: /Back/i })).toHaveAttribute(
+ "href",
+ "baseurl.com/2",
+ );
+ expect(screen.getByRole("button", { name: /Cancel/i })).not.toBeDisabled();
+ expect(screen.getByRole("link", { name: /Cancel/i })).toHaveAttribute(
+ "href",
+ "cancelurl.com",
+ );
+ expect(
+ screen.getByRole("button", { name: /test submit button text/i }),
+ ).not.toBeDisabled();
+ fireEvent.click(submitButton);
+ expect(mockOnSubmit).toHaveBeenCalled();
+ expect(mockPush).not.toHaveBeenCalled();
+ });
+
+ it("submission is disabled if form is still submitting", async () => {
+ useParams.mockReturnValue({
+ formSection: "1",
+ operation: "create",
+ } as QueryParams);
+ const user = userEvent.setup();
+ let resolve: (v: unknown) => void;
+
+ const mockPromiseOnSubmit = vitest.fn().mockReturnValue(
+ new Promise((_resolve) => {
+ resolve = _resolve;
+ }),
+ );
+
+ render(
+ ,
+ );
+ const saveAndContinueButton = screen.getByRole("button", {
+ name: /Save and Continue/i,
+ });
+
+ await act(async () => {
+ await user.click(saveAndContinueButton);
+ });
+
+ waitFor(() => {
+ expect(saveAndContinueButton).toBeDisabled();
+ });
+
+ await act(async () => {
+ resolve(vitest.fn);
+ });
+
+ expect(saveAndContinueButton).not.toBeDisabled();
+ });
+
+ it("shows an error if there was a problem with form validation", () => {
+ useParams.mockReturnValue({
+ formSection: "1",
+ operation: "create",
+ } as QueryParams);
+
+ render();
+ const saveAndContinueButton = screen.getByRole("button", {
+ name: /Save and Continue/i,
+ });
+ fireEvent.click(saveAndContinueButton);
+ expect(screen.getByRole("alert")).toBeVisible();
+ expect(mockOnSubmit).not.toHaveBeenCalled(); // submit function is not called because we hit validation errors first
+ });
+
+ it("shows an error if passed one", () => {
+ render(
+ ,
+ );
+ expect(screen.getByRole("alert")).toBeVisible();
+ expect(screen.getByText("Test error")).toBeVisible();
+ });
+
+ it("shows an error if response returns an error", async () => {
+ mockOnSubmit.mockReturnValueOnce({ error: "whoopsie" });
+ render(
+ ,
+ );
+
+ const saveAndContinueButton = screen.getByRole("button", {
+ name: /Save and Continue/i,
+ });
+
+ fireEvent.click(saveAndContinueButton);
+ await waitFor(() => {
+ expect(mockOnSubmit).toHaveBeenCalled();
+ expect(screen.getByRole("alert")).toBeVisible();
+ expect(screen.getByText("whoopsie")).toBeVisible();
+ });
+ });
+
+ it("clears old errors on submission", async () => {
+ render(
+ ,
+ );
+ const saveAndContinueButton = screen.getByRole("button", {
+ name: /Save and Continue/i,
+ });
+ act(() => {
+ fireEvent.click(saveAndContinueButton);
+ });
+ await waitFor(() => {
+ expect(mockOnSubmit).toHaveBeenCalled();
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
+ });
+ });
+
+ it("calls the onChange prop when the form changes", () => {
+ useParams.mockReturnValue({
+ formSection: "1",
+ operation: "create",
+ } as QueryParams);
+
+ const changeHandler = vi.fn();
+ render(
+ ,
+ );
+ const input = screen.getByLabelText(/field1*/i);
+ fireEvent.change(input, { target: { value: "new value" } });
+
+ expect(changeHandler).toHaveBeenCalled();
+ });
+
+ it("renders children", () => {
+ useParams.mockReturnValue({
+ formSection: "1",
+ operation: "create",
+ } as QueryParams);
+
+ render(
+
+ Test child
+ ,
+ );
+ expect(screen.getByTestId("test-child")).toBeVisible();
+ });
});
diff --git a/bciers/libs/components/src/form/MultiStepBase.tsx b/bciers/libs/components/src/form/MultiStepBase.tsx
index f3b8f666dc..26287ded25 100644
--- a/bciers/libs/components/src/form/MultiStepBase.tsx
+++ b/bciers/libs/components/src/form/MultiStepBase.tsx
@@ -29,7 +29,6 @@ interface MultiStepBaseProps {
uiSchema: UiSchema;
submitButtonDisabled?: boolean;
customValidate?: any;
- setOnSubmitSuccessfulResponse?: (error: undefined) => void; // Use this if the parent component needs the successful `onSubmit` response to do further actions, e.g. the RegistrationSubmissionForm shows a confirmation message after a successful submission
}
// Modified MultiStepFormBase meant to facilitate more modularized Multi-step forms
@@ -55,7 +54,6 @@ const MultiStepBase = ({
uiSchema,
submitButtonDisabled,
customValidate,
- setOnSubmitSuccessfulResponse,
}: MultiStepBaseProps) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
@@ -73,17 +71,17 @@ const MultiStepBase = ({
const response = await onSubmit(data);
if (response?.error) {
setError(response?.error);
+ setIsSubmitting(false);
return;
}
- if (setOnSubmitSuccessfulResponse) setOnSubmitSuccessfulResponse(response);
if (isNotFinalStep && baseUrl) {
const nextStepUrl = `${baseUrl}/${step + 1}${
baseUrlParams ? `?${baseUrlParams}` : ""
}`;
router.push(nextStepUrl);
+ setIsSubmitting(false);
}
- setIsSubmitting(false);
};
const isDisabled = (disabled && !isEditMode) || isSubmitting;