diff --git a/coral/src/app/features/connectors/request/ConnectorRequest.test.tsx b/coral/src/app/features/connectors/request/ConnectorRequest.test.tsx index c64b67c53c..9bd60df6c0 100644 --- a/coral/src/app/features/connectors/request/ConnectorRequest.test.tsx +++ b/coral/src/app/features/connectors/request/ConnectorRequest.test.tsx @@ -24,6 +24,12 @@ jest.mock("react-router-dom", () => ({ useNavigate: () => mockedUsedNavigate, })); +const mockedUseToast = jest.fn(); +jest.mock("@aivenio/aquarium", () => ({ + ...jest.requireActual("@aivenio/aquarium"), + useToast: () => mockedUseToast, +})); + describe("", () => { const originalConsoleError = console.error; let user: ReturnType; @@ -45,7 +51,12 @@ describe("", () => { createEnvironment({ id: "2", name: "TST" }), createEnvironment({ id: "3", name: "PROD" }), ]); - customRender(, { queryClient: true }); + customRender( + + + , + { queryClient: true } + ); }); afterEach(cleanup); @@ -96,7 +107,12 @@ describe("", () => { createEnvironment({ id: "2", name: "TST" }), createEnvironment({ id: "3", name: "PROD" }), ]); - customRender(, { queryClient: true }); + customRender( + + + , + { queryClient: true } + ); }); afterEach(cleanup); @@ -405,7 +421,10 @@ describe("", () => { }); }); - afterEach(() => cleanup()); + afterEach(() => { + mockedUseToast.mockReset(); + cleanup(); + }); it("creates a new connector request when input was valid", async () => { await user.click( @@ -424,6 +443,7 @@ describe("", () => { environment: "1", remarks: "", }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("errors and does not create a new connector request when input was invalid", async () => { @@ -440,12 +460,13 @@ describe("", () => { ); expect(createConnectorRequest).not.toHaveBeenCalled(); + expect(mockedUseToast).not.toHaveBeenCalled(); expect( screen.getByRole("button", { name: "Submit request" }) ).toBeEnabled(); }); - it("shows a dialog informing user that request was successful", async () => { + it("shows a notification informing user that request was successful and redirects them", async () => { await user.click( screen.getByRole("button", { name: "Submit request" }) ); @@ -456,32 +477,13 @@ describe("", () => { }); expect(createConnectorRequest).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Connector request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - await user.click( - screen.getByRole("button", { name: "Submit request" }) + await waitFor(() => + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Connector request successful!", + position: "bottom-left", + variant: "default", + }) ); - - await waitFor(() => { - const btn = screen.getByRole("button", { name: "Submit request" }); - expect(btn).toBeDisabled(); - }); - - expect(createConnectorRequest).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", - }); - - await userEvent.click(button); - expect(mockedUsedNavigate).toHaveBeenCalledWith( "/requests/connectors?status=CREATED" ); diff --git a/coral/src/app/features/connectors/request/ConnectorRequest.tsx b/coral/src/app/features/connectors/request/ConnectorRequest.tsx index afa62fdade..e8d687d6b4 100644 --- a/coral/src/app/features/connectors/request/ConnectorRequest.tsx +++ b/coral/src/app/features/connectors/request/ConnectorRequest.tsx @@ -6,6 +6,7 @@ import { Grid, Label, Typography, + useToast, } from "@aivenio/aquarium"; import MonacoEditor from "@monaco-editor/react"; import { useMutation, useQuery } from "@tanstack/react-query"; @@ -34,9 +35,10 @@ import { parseErrorMsg } from "src/services/mutation-utils"; function ConnectorRequest() { const [cancelDialogVisible, setCancelDialogVisible] = useState(false); - const [successModalOpen, setSuccessModalOpen] = useState(false); const navigate = useNavigate(); + const toast = useToast(); + const form = useForm({ schema: connectorRequestFormSchema, }); @@ -51,17 +53,15 @@ function ConnectorRequest() { const connectorRequestMutation = useMutation(createConnectorRequest, { onSuccess: () => { - setSuccessModalOpen(true); - setTimeout(() => { - redirectToMyRequests(); - }, 5 * 1000); + navigate("/requests/connectors?status=CREATED"); + toast({ + message: "Connector request successful!", + position: "bottom-left", + variant: "default", + }); }, }); - function redirectToMyRequests() { - navigate("/requests/connectors?status=CREATED"); - } - function onSubmitForm(userInput: ConnectorRequestFormSchema) { connectorRequestMutation.mutate(userInput); } @@ -73,20 +73,6 @@ function ConnectorRequest() { return ( <> - {successModalOpen && ( - - Redirecting to My team's request page shortly. Select - "Continue" for an immediate redirect. - - )} - {connectorRequestMutation.isError && ( diff --git a/coral/src/app/features/topics/acl-request/TopicAclRequest.test.tsx b/coral/src/app/features/topics/acl-request/TopicAclRequest.test.tsx index 0d62819ef3..dd6c04502a 100644 --- a/coral/src/app/features/topics/acl-request/TopicAclRequest.test.tsx +++ b/coral/src/app/features/topics/acl-request/TopicAclRequest.test.tsx @@ -1,3 +1,4 @@ +import { Context as AquariumContext } from "@aivenio/aquarium"; import { cleanup, screen, waitFor, within } from "@testing-library/react"; import { waitForElementToBeRemoved } from "@testing-library/react/pure"; import userEvent from "@testing-library/user-event"; @@ -31,6 +32,12 @@ jest.mock("src/domain/acl/acl-api", () => ({ getAivenServiceAccounts: jest.fn().mockReturnValue(["account"]), })); +const mockedUseToast = jest.fn(); +jest.mock("@aivenio/aquarium", () => ({ + ...jest.requireActual("@aivenio/aquarium"), + useToast: () => mockedUseToast, +})); + const dataSetup = ({ isAivenCluster }: { isAivenCluster: boolean }) => { mockgetAllEnvironmentsForTopicAndAcl({ mswInstance: server, @@ -97,7 +104,11 @@ describe("", () => { } + element={ + + + + } /> , { @@ -173,7 +184,11 @@ describe("", () => { } + element={ + + + + } /> , { @@ -450,7 +465,11 @@ describe("", () => { } + element={ + + + + } /> , { @@ -729,7 +748,11 @@ describe("", () => { } + element={ + + + + } /> , { @@ -928,7 +951,7 @@ describe("", () => { }); describe("enables user to create a new acl request", () => { - beforeEach(async () => { + beforeEach(() => { mockCreateAclRequest({ mswInstance: server, response: { @@ -940,6 +963,7 @@ describe("", () => { afterEach(() => { jest.clearAllMocks(); + mockedUseToast.mockReset(); }); it("creates a new acl request when input was valid", async () => { @@ -985,6 +1009,7 @@ describe("", () => { requestOperationType: "CREATE", teamId: 1, }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("renders errors and does not submit when input was invalid", async () => { @@ -1017,9 +1042,10 @@ describe("", () => { expect(spyPost).not.toHaveBeenCalled(); expect(submitButton).toBeEnabled(); + expect(mockedUseToast).not.toHaveBeenCalled(); }); - it("shows a dialog informing user that request was successful", async () => { + it("shows a notification informing user that request was successful and redirects them", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); const submitButton = screen.getByRole("button", { @@ -1052,55 +1078,16 @@ describe("", () => { expect(spyPost).toHaveBeenCalledTimes(1); - const successModal = await screen.findByRole("dialog"); - - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Acl request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - const spyPost = jest.spyOn(api, "post"); - await assertSkeleton(); - const submitButton = screen.getByRole("button", { - name: "Submit request", - }); - - // Fill form with valid data - await selectTestEnvironment(); - await userEvent.click(screen.getByRole("radio", { name: "Literal" })); - await userEvent.click( - screen.getByRole("radio", { name: "Service account" }) - ); - - const principalsField = await screen.findByRole("combobox", { - name: "Service accounts *", - }); - - expect(principalsField).toBeVisible(); - expect(principalsField).toBeEnabled(); - - await userEvent.type(principalsField, "Alice"); - await userEvent.tab(); - - await waitFor(() => expect(submitButton).toBeEnabled()); - await userEvent.click(submitButton); - await waitFor(() => { - expect(submitButton).toBeDisabled(); + expect(mockedNavigate).toHaveBeenLastCalledWith( + "/requests/acls?status=CREATED" + ); }); - - expect(spyPost).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Acl request successful!", + position: "bottom-left", + variant: "default", }); - - await userEvent.click(button); - - expect(mockedNavigate).toHaveBeenLastCalledWith( - "/requests/acls?status=CREATED" - ); }); }); }); @@ -1113,7 +1100,11 @@ describe("", () => { } + element={ + + + + } /> , { @@ -1337,6 +1328,10 @@ describe("", () => { }); }); + afterEach(() => { + mockedUseToast.mockReset(); + }); + it("creates a new acl request when input was valid", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); @@ -1398,6 +1393,7 @@ describe("", () => { requestOperationType: "CREATE", transactionalId: undefined, }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("renders errors and does not submit when input was invalid", async () => { @@ -1430,7 +1426,7 @@ describe("", () => { expect(submitButton).toBeEnabled(); }); - it("shows a dialog informing user that request was successful", async () => { + it("shows a notification informing user that request was successful and redirects them", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); @@ -1478,70 +1474,16 @@ describe("", () => { }); expect(spyPost).toHaveBeenCalledTimes(1); - const successModal = await screen.findByRole("dialog"); - - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Acl request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - const spyPost = jest.spyOn(api, "post"); - await assertSkeleton(); - - const aclConsumerTypeInput = screen.getByRole("radio", { - name: "Consumer", - }); - await userEvent.click(aclConsumerTypeInput); - - const submitButton = screen.getByRole("button", { - name: "Submit request", - }); - - // Fill form with valid data - await selectTestEnvironment(); - - expect( - screen.getByRole("radio", { name: "Principal" }) - ).toBeInTheDocument(); - - await userEvent.click(screen.getByRole("radio", { name: "Principal" })); - expect(screen.getByRole("radio", { name: "Principal" })).toBeChecked(); - - const principalsField = await screen.findByRole("textbox", { - name: "SSL DN strings / Usernames *", - }); - - expect(principalsField).toBeVisible(); - expect(principalsField).toBeEnabled(); - - await userEvent.type(principalsField, "Alice"); - await userEvent.tab(); - - const consumerGroupField = await screen.findByRole("textbox", { - name: "Consumer group *", - }); - - await userEvent.type(consumerGroupField, "group"); - await userEvent.tab(); - - await waitFor(() => expect(submitButton).toBeEnabled()); - await userEvent.click(submitButton); - await waitFor(() => { - expect(submitButton).toBeDisabled(); + expect(mockedNavigate).toHaveBeenLastCalledWith( + "/requests/acls?status=CREATED" + ); }); - - expect(spyPost).toHaveBeenCalledTimes(1); - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Acl request successful!", + position: "bottom-left", + variant: "default", }); - - await userEvent.click(button); - - expect(mockedNavigate).toHaveBeenLastCalledWith( - "/requests/acls?status=CREATED" - ); }); }); }); @@ -1550,10 +1492,15 @@ describe("", () => { beforeEach(() => { dataSetup({ isAivenCluster: true }); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); }); afterEach(cleanup); @@ -1634,10 +1581,15 @@ describe("", () => { beforeEach(() => { dataSetup({ isAivenCluster: false }); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); }); afterEach(() => { @@ -1951,10 +1903,15 @@ describe("", () => { beforeEach(() => { dataSetup({ isAivenCluster: false }); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); }); afterEach(cleanup); @@ -2239,10 +2196,15 @@ describe("", () => { beforeEach(async () => { dataSetup({ isAivenCluster: true }); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); }); afterEach(() => { @@ -2443,6 +2405,10 @@ describe("", () => { }); }); + afterEach(() => { + mockedUseToast.mockReset(); + }); + it("creates a new acl request when input was valid", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); @@ -2486,6 +2452,7 @@ describe("", () => { requestOperationType: "CREATE", teamId: 1, }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("renders errors and does not submit when input was invalid", async () => { @@ -2520,7 +2487,7 @@ describe("", () => { expect(submitButton).toBeEnabled(); }); - it("shows a dialog informing user that request was successful", async () => { + it("shows a notification informing user that request was successful and redirects them", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); const submitButton = screen.getByRole("button", { @@ -2552,56 +2519,16 @@ describe("", () => { }); expect(spyPost).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Acl request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - const spyPost = jest.spyOn(api, "post"); - await assertSkeleton(); - const submitButton = screen.getByRole("button", { - name: "Submit request", - }); - - // Fill form with valid data - await selectTestEnvironment(); - await userEvent.click(screen.getByRole("radio", { name: "Literal" })); - await userEvent.click( - screen.getByRole("radio", { name: "Service account" }) - ); - - const principalsField = await screen.findByRole("combobox", { - name: "Service accounts *", - }); - - expect(principalsField).toBeVisible(); - expect(principalsField).toBeEnabled(); - - await userEvent.type(principalsField, "Alice"); - await userEvent.tab(); - - await waitFor(() => expect(submitButton).toBeEnabled()); - await userEvent.click(submitButton); - await waitFor(() => { - expect(submitButton).toBeDisabled(); + expect(mockedNavigate).toHaveBeenLastCalledWith( + "/requests/acls?status=CREATED" + ); }); - - expect(spyPost).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Acl request successful!", + position: "bottom-left", + variant: "default", }); - - await userEvent.click(button); - - expect(mockedNavigate).toHaveBeenLastCalledWith( - "/requests/acls?status=CREATED" - ); }); }); }); @@ -2610,10 +2537,15 @@ describe("", () => { beforeEach(async () => { dataSetup({ isAivenCluster: false }); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); }); afterEach(() => { @@ -2838,6 +2770,10 @@ describe("", () => { }); }); + afterEach(() => { + mockedUseToast.mockReset(); + }); + it("creates a new acl request when input was valid", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); @@ -2910,6 +2846,7 @@ describe("", () => { requestOperationType: "CREATE", transactionalId: undefined, }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("renders errors and does not submit when input was invalid", async () => { @@ -2942,7 +2879,7 @@ describe("", () => { expect(submitButton).toBeEnabled(); }); - it("shows a dialog informing user that request was successful", async () => { + it("shows a notification informing user that request was successful and redirects them", async () => { const spyPost = jest.spyOn(api, "post"); await assertSkeleton(); @@ -3001,81 +2938,16 @@ describe("", () => { }); expect(spyPost).toHaveBeenCalledTimes(1); - const successModal = await screen.findByRole("dialog"); - - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Acl request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - const spyPost = jest.spyOn(api, "post"); - await assertSkeleton(); - - const aclConsumerTypeInput = screen.getByRole("radio", { - name: "Consumer", - }); - await userEvent.click(aclConsumerTypeInput); - - const submitButton = screen.getByRole("button", { - name: "Submit request", - }); - - // Fill form with valid data - await selectTestEnvironment(); - - const visibleTopicNameField = await screen.findByRole("combobox", { - name: "Topic name *", - }); - - await userEvent.selectOptions( - visibleTopicNameField, - mockedResponseTopicNames[0] - ); - - expect(visibleTopicNameField).toHaveValue(mockedResponseTopicNames[0]); - - expect( - screen.getByRole("radio", { name: "Principal" }) - ).toBeInTheDocument(); - - await userEvent.click(screen.getByRole("radio", { name: "Principal" })); - expect(screen.getByRole("radio", { name: "Principal" })).toBeChecked(); - - const principalsField = await screen.findByRole("textbox", { - name: "SSL DN strings / Usernames *", - }); - - expect(principalsField).toBeVisible(); - expect(principalsField).toBeEnabled(); - - await userEvent.type(principalsField, "Alice"); - await userEvent.tab(); - - const consumerGroupField = await screen.findByRole("textbox", { - name: "Consumer group *", - }); - - await userEvent.type(consumerGroupField, "group"); - await userEvent.tab(); - - await waitFor(() => expect(submitButton).toBeEnabled()); - await userEvent.click(submitButton); - await waitFor(() => { - expect(submitButton).toBeDisabled(); + expect(mockedNavigate).toHaveBeenLastCalledWith( + "/requests/acls?status=CREATED" + ); }); - - expect(spyPost).toHaveBeenCalledTimes(1); - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Acl request successful!", + position: "bottom-left", + variant: "default", }); - - await userEvent.click(button); - - expect(mockedNavigate).toHaveBeenLastCalledWith( - "/requests/acls?status=CREATED" - ); }); }); }); diff --git a/coral/src/app/features/topics/acl-request/forms/TopicConsumerForm.tsx b/coral/src/app/features/topics/acl-request/forms/TopicConsumerForm.tsx index 5dfe734e30..6abfdbf59e 100644 --- a/coral/src/app/features/topics/acl-request/forms/TopicConsumerForm.tsx +++ b/coral/src/app/features/topics/acl-request/forms/TopicConsumerForm.tsx @@ -6,6 +6,7 @@ import { GridItem, Input, SecondaryButton, + useToast, } from "@aivenio/aquarium"; import { useMutation } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; @@ -47,9 +48,10 @@ const TopicConsumerForm = ({ isSubscription, }: TopicConsumerFormProps) => { const [cancelDialogVisible, setCancelDialogVisible] = useState(false); - const [successModalOpen, setSuccessModalOpen] = useState(false); const navigate = useNavigate(); + const toast = useToast(); + const { aclIpPrincipleType, environment, topicname } = topicConsumerForm.getValues(); const { current: initialAclIpPrincipleType } = useRef(aclIpPrincipleType); @@ -70,17 +72,15 @@ const TopicConsumerForm = ({ const { mutate, isLoading, isError, error } = useMutation({ mutationFn: createAclRequest, onSuccess: () => { - setSuccessModalOpen(true); - setTimeout(() => { - redirectToMyRequests(); - }, 5 * 1000); + navigate("/requests/acls?status=CREATED"); + toast({ + message: "Acl request successful!", + position: "bottom-left", + variant: "default", + }); }, }); - function redirectToMyRequests() { - navigate("/requests/acls?status=CREATED"); - } - const onSubmitTopicConsumer: SubmitHandler = ( formData ) => { @@ -119,19 +119,6 @@ const TopicConsumerForm = ({ {parseErrorMsg(error)} )} - {successModalOpen && ( - - Redirecting to My team's request page shortly. Select - "Continue" for an immediate redirect. - - )}
", () => { }) ); customRender( - , + + + , { queryClient: true, memoryRouter: true } ); }); @@ -207,10 +213,12 @@ describe("", () => { ); customRender( - , + + + , { queryClient: true, memoryRouter: true } ); }); @@ -336,10 +344,12 @@ describe("", () => { }) ); customRender( - , + + + , { queryClient: true, memoryRouter: true } ); }); @@ -484,10 +494,12 @@ describe("", () => { }) ); customRender( - , + + + , { queryClient: true, memoryRouter: true } ); }); @@ -619,10 +631,12 @@ describe("", () => { ); customRender( - , + + + , { queryClient: true, memoryRouter: true } ); }); @@ -750,10 +764,12 @@ describe("", () => { }) ); customRender( - , + + + , { queryClient: true, memoryRouter: true } ); }); diff --git a/coral/src/app/features/topics/acl-request/forms/TopicProducerForm.tsx b/coral/src/app/features/topics/acl-request/forms/TopicProducerForm.tsx index da083579c1..4d01b5ec0a 100644 --- a/coral/src/app/features/topics/acl-request/forms/TopicProducerForm.tsx +++ b/coral/src/app/features/topics/acl-request/forms/TopicProducerForm.tsx @@ -6,6 +6,7 @@ import { GridItem, RadioButton as BaseRadioButton, SecondaryButton, + useToast, } from "@aivenio/aquarium"; import { useMutation } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; @@ -48,9 +49,10 @@ const TopicProducerForm = ({ isSubscription, }: TopicProducerFormProps) => { const [cancelDialogVisible, setCancelDialogVisible] = useState(false); - const [successModalOpen, setSuccessModalOpen] = useState(false); const navigate = useNavigate(); + const toast = useToast(); + const { aclIpPrincipleType, aclPatternType, topicname, environment } = topicProducerForm.getValues(); const { current: initialAclIpPrincipleType } = useRef(aclIpPrincipleType); @@ -85,17 +87,15 @@ const TopicProducerForm = ({ const { mutate, isLoading, isError, error } = useMutation({ mutationFn: createAclRequest, onSuccess: () => { - setSuccessModalOpen(true); - setTimeout(() => { - redirectToMyRequests(); - }, 5 * 1000); + navigate("/requests/acls?status=CREATED"); + toast({ + message: "Acl request successful!", + position: "bottom-left", + variant: "default", + }); }, }); - function redirectToMyRequests() { - navigate("/requests/acls?status=CREATED"); - } - const onSubmitTopicProducer: SubmitHandler = ( formData ) => { @@ -117,19 +117,6 @@ const TopicProducerForm = ({ {parseErrorMsg(error)} )} - {successModalOpen && ( - - Redirecting to My team's request page shortly. Select - "Continue" for an immediate redirect. - - )} ({ useNavigate: () => mockedUsedNavigate, })); +const mockedUseToast = jest.fn(); +jest.mock("@aivenio/aquarium", () => ({ + ...jest.requireActual("@aivenio/aquarium"), + useToast: () => mockedUseToast, +})); + describe("", () => { const originalConsoleError = console.error; let user: ReturnType; @@ -57,7 +63,12 @@ describe("", () => { data: defaultgetTopicAdvancedConfigOptionsResponse, }, }); - customRender(, { queryClient: true }); + customRender( + + + , + { queryClient: true } + ); }); afterAll(() => { cleanup(); @@ -118,7 +129,12 @@ describe("", () => { data: defaultgetTopicAdvancedConfigOptionsResponse, }, }); - customRender(, { queryClient: true }); + customRender( + + + , + { queryClient: true } + ); }); afterEach(() => { @@ -1076,7 +1092,7 @@ describe("", () => { }); afterEach(() => { - jest.clearAllMocks(); + jest.resetAllMocks(); }); describe("handles an error from the api", () => { @@ -1121,6 +1137,13 @@ describe("", () => { describe("enables user to create a new topic request", () => { beforeEach(async () => { + // these two need to be reset in beforeEach + // because they are async and resetting them + // afterEach may lead to false results when + // tests run fast + + mockedUsedNavigate.mockReset(); + mockRequestTopic({ mswInstance: server, response: { @@ -1130,6 +1153,10 @@ describe("", () => { }); }); + afterEach(() => { + mockedUseToast.mockReset(); + }); + it("creates a new topic request when input was valid", async () => { const spyPost = jest.spyOn(api, "post"); @@ -1153,6 +1180,7 @@ describe("", () => { remarks: "", requestOperationType: "CREATE", }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("errors and does not create a new topic request when input was invalid", async () => { @@ -1169,12 +1197,13 @@ describe("", () => { ); expect(spyPost).not.toHaveBeenCalled(); + expect(mockedUseToast).not.toHaveBeenCalled(); expect( screen.getByRole("button", { name: "Submit request" }) ).toBeEnabled(); }); - it("shows a dialog informing user that request was successful", async () => { + it("shows a notification that request was successful and redirects user", async () => { const spyPost = jest.spyOn(api, "post"); await user.click( @@ -1187,37 +1216,17 @@ describe("", () => { }); expect(spyPost).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Topic request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - const spyPost = jest.spyOn(api, "post"); - - await user.click( - screen.getByRole("button", { name: "Submit request" }) - ); - await waitFor(() => { - const btn = screen.getByRole("button", { name: "Submit request" }); - expect(btn).toBeDisabled(); + expect(mockedUsedNavigate).toHaveBeenCalledWith( + "/requests/topics?status=CREATED" + ); }); - expect(spyPost).toHaveBeenCalledTimes(1); - - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Topic request successful!", + position: "bottom-left", + variant: "default", }); - - await userEvent.click(button); - - expect(mockedUsedNavigate).toHaveBeenCalledWith( - "/requests/topics?status=CREATED" - ); }); }); }); diff --git a/coral/src/app/features/topics/request/TopicRequest.tsx b/coral/src/app/features/topics/request/TopicRequest.tsx index 88eefecf26..4e36116e15 100644 --- a/coral/src/app/features/topics/request/TopicRequest.tsx +++ b/coral/src/app/features/topics/request/TopicRequest.tsx @@ -7,6 +7,7 @@ import { Divider, Flexbox, FlexboxItem, + useToast, } from "@aivenio/aquarium"; import { Form, @@ -33,10 +34,10 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; function TopicRequest() { - const [cancelDialogVisible, setCancelDialogVisible] = useState(false); const navigate = useNavigate(); + const toast = useToast(); - const [successModalOpen, setSuccessModalOpen] = useState(false); + const [cancelDialogVisible, setCancelDialogVisible] = useState(false); const { data: environments } = useQuery( ["environments-for-team"], @@ -62,16 +63,15 @@ function TopicRequest() { const { mutate, isLoading, isError, error } = useMutation(requestTopic, { onSuccess: () => { - setSuccessModalOpen(true); - setTimeout(() => { - redirectToMyRequests(); - }, 5 * 1000); + navigate("/requests/topics?status=CREATED"); + toast({ + message: "Topic request successful!", + position: "bottom-left", + variant: "default", + }); }, }); - const redirectToMyRequests = () => - navigate("/requests/topics?status=CREATED"); - const onSubmit: SubmitHandler = (data) => mutate(data); const [selectedEnvironment] = form.getValues(["environment"]); @@ -86,17 +86,20 @@ function TopicRequest() { return ( <> - {successModalOpen && ( + {cancelDialogVisible && ( cancelRequest(), }} - type={"confirmation"} + secondaryAction={{ + text: "Continue with request", + onClick: () => setCancelDialogVisible(false), + }} + type={"warning"} > - Redirecting to My team's request page shortly. Select - "Continue" for an immediate redirect. + Do you want to cancel this request? The data added will be lost. )} @@ -204,22 +207,6 @@ function TopicRequest() {
- {cancelDialogVisible && ( - cancelRequest(), - }} - secondaryAction={{ - text: "Continue with request", - onClick: () => setCancelDialogVisible(false), - }} - type={"warning"} - > - Do you want to cancel this request? The data added will be lost. - - )} ); diff --git a/coral/src/app/features/topics/schema-request/TopicSchemaRequest.test.tsx b/coral/src/app/features/topics/schema-request/TopicSchemaRequest.test.tsx index 8ca534d761..e7d38d25e8 100644 --- a/coral/src/app/features/topics/schema-request/TopicSchemaRequest.test.tsx +++ b/coral/src/app/features/topics/schema-request/TopicSchemaRequest.test.tsx @@ -1,3 +1,4 @@ +import { Context as AquariumContext } from "@aivenio/aquarium"; import * as ReactQuery from "@tanstack/react-query"; import { waitFor, within } from "@testing-library/react"; import { @@ -35,6 +36,12 @@ jest.mock("react-router-dom", () => ({ useNavigate: () => mockedUsedNavigate, })); +const mockedUseToast = jest.fn(); +jest.mock("@aivenio/aquarium", () => ({ + ...jest.requireActual("@aivenio/aquarium"), + useToast: () => mockedUseToast, +})); + const useQuerySpy = jest.spyOn(ReactQuery, "useQuery"); const testTopicName = "my-awesome-topic"; @@ -86,10 +93,15 @@ describe("TopicSchemaRequest", () => { testTopicName, ]); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); const form = getForm(); expect(form).toBeVisible(); @@ -105,10 +117,15 @@ describe("TopicSchemaRequest", () => { it("redirects user if topicName prop does not exist in list of topicNames", async () => { mockGetTopicNames.mockResolvedValue(["topic-1", "topic-2"]); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); await waitFor(() => { expect(mockedUsedNavigate).toHaveBeenCalledWith(-1); @@ -132,11 +149,16 @@ describe("TopicSchemaRequest", () => { ...mockedGetSchemaRegistryEnvironments, ]); - customRender(, { - queryClient: true, - memoryRouter: true, - customRoutePath: "/topic/testtopic/request-schema?env=1", - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + customRoutePath: "/topic/testtopic/request-schema?env=1", + } + ); const form = getForm(); expect(form).toBeVisible(); @@ -166,11 +188,16 @@ describe("TopicSchemaRequest", () => { ...mockedGetSchemaRegistryEnvironments, ]); - customRender(, { - queryClient: true, - memoryRouter: true, - customRoutePath: "/topic/testtopic/request-schema?env=INFRA", - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + customRoutePath: "/topic/testtopic/request-schema?env=INFRA", + } + ); const form = getForm(); expect(form).toBeVisible(); @@ -200,11 +227,16 @@ describe("TopicSchemaRequest", () => { ...mockedGetSchemaRegistryEnvironments, ]); - customRender(, { - queryClient: true, - memoryRouter: true, - customRoutePath: "/topic/testtopic/request-schema?env=999", - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + customRoutePath: "/topic/testtopic/request-schema?env=999", + } + ); await waitFor(() => { expect(mockedUsedNavigate).toHaveBeenCalledWith(-1); @@ -216,11 +248,16 @@ describe("TopicSchemaRequest", () => { ...mockedGetSchemaRegistryEnvironments, ]); - customRender(, { - queryClient: true, - memoryRouter: true, - customRoutePath: "/topic/testtopic/request-schema?env=HELLO", - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + customRoutePath: "/topic/testtopic/request-schema?env=HELLO", + } + ); await waitFor(() => { expect(mockedUsedNavigate).toHaveBeenCalledWith(-1); @@ -237,10 +274,15 @@ describe("TopicSchemaRequest", () => { mockCreateSchemaRequest.mockImplementation(jest.fn()); mockGetTopicNames.mockResolvedValue([testTopicName]); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); }); afterAll(() => { @@ -272,10 +314,15 @@ describe("TopicSchemaRequest", () => { mockCreateSchemaRequest.mockImplementation(jest.fn()); mockGetTopicNames.mockResolvedValue([testTopicName]); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); await waitForElementToBeRemoved( screen.getByTestId("environments-select-loading") ); @@ -396,10 +443,15 @@ describe("TopicSchemaRequest", () => { mockCreateSchemaRequest.mockImplementation(jest.fn()); mockGetTopicNames.mockResolvedValue([testTopicName]); - customRender(, { - queryClient: true, - memoryRouter: true, - }); + customRender( + + + , + { + queryClient: true, + memoryRouter: true, + } + ); await waitForElementToBeRemoved( screen.getByTestId("environments-select-loading") ); @@ -709,6 +761,7 @@ describe("TopicSchemaRequest", () => { }); afterEach(() => { + mockedUseToast.mockReset(); cleanup(); }); @@ -804,6 +857,7 @@ describe("TopicSchemaRequest", () => { schemafull: "{}", topicname: "my-awesome-topic", }); + await waitFor(() => expect(mockedUseToast).toHaveBeenCalled()); }); it("does not submit on button click if user did not fill all required inputs", async () => { @@ -824,9 +878,10 @@ describe("TopicSchemaRequest", () => { await userEvent.tab(); expect(mockCreateSchemaRequest).not.toHaveBeenCalled(); + expect(mockedUseToast).not.toHaveBeenCalled(); }); - it("shows a dialog informing user that schema request was successful", async () => { + it("shows a notification informing user that schema request was successful and redirects them", async () => { const form = getForm(); const select = within(form).getByRole("combobox", { @@ -849,43 +904,13 @@ describe("TopicSchemaRequest", () => { await userEvent.click(button); expect(mockCreateSchemaRequest).toHaveBeenCalled(); - - const successModal = await screen.findByRole("dialog"); - expect(successModal).toBeVisible(); - expect(successModal).toHaveTextContent("Schema request successful!"); - }); - - it("user can continue to the next page without waiting for redirect in the dialog", async () => { - const form = getForm(); - - const select = within(form).getByRole("combobox", { - name: /Environment/i, - }); - const option = within(select).getByRole("option", { - name: mockedEnvironments[0].name, - }); - const fileInput = - within(form).getByLabelText(/Upload AVRO Schema/i); - - const submitButton = within(form).getByRole("button", { - name: "Submit request", - }); - expect(submitButton).toBeEnabled(); - - await userEvent.selectOptions(select, option); - await userEvent.tab(); - await userEvent.upload(fileInput, testFile); - await userEvent.click(submitButton); - - expect(mockCreateSchemaRequest).toHaveBeenCalled(); - - const successModal = await screen.findByRole("dialog"); - const button = within(successModal).getByRole("button", { - name: "Continue", - }); - - await userEvent.click(button); - + await waitFor(() => + expect(mockedUseToast).toHaveBeenCalledWith({ + message: "Schema request successful!", + position: "bottom-left", + variant: "default", + }) + ); expect(mockedUsedNavigate).toHaveBeenCalledWith( "/requests/schemas?status=CREATED" ); diff --git a/coral/src/app/features/topics/schema-request/TopicSchemaRequest.tsx b/coral/src/app/features/topics/schema-request/TopicSchemaRequest.tsx index 6f3b33051a..0cf2d64656 100644 --- a/coral/src/app/features/topics/schema-request/TopicSchemaRequest.tsx +++ b/coral/src/app/features/topics/schema-request/TopicSchemaRequest.tsx @@ -1,4 +1,4 @@ -import { Alert, Box, Button } from "@aivenio/aquarium"; +import { Alert, Box, Button, useToast } from "@aivenio/aquarium"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; @@ -42,9 +42,10 @@ function TopicSchemaRequest(props: TopicSchemaRequestProps) { const presetEnvironment = searchParams.get("env"); const [cancelDialogVisible, setCancelDialogVisible] = useState(false); - const [successModalOpen, setSuccessModalOpen] = useState(false); const navigate = useNavigate(); + const toast = useToast(); + const form = useForm({ schema: topicRequestFormSchema, defaultValues: { @@ -103,17 +104,15 @@ function TopicSchemaRequest(props: TopicSchemaRequestProps) { const schemaRequestMutation = useMutation(createSchemaRequest, { onSuccess: () => { - setSuccessModalOpen(true); - setTimeout(() => { - redirectToMyRequests(); - }, 5 * 1000); + navigate("/requests/schemas?status=CREATED"); + toast({ + message: "Schema request successful!", + position: "bottom-left", + variant: "default", + }); }, }); - function redirectToMyRequests() { - navigate("/requests/schemas?status=CREATED"); - } - function onSubmitForm(userInput: TopicRequestFormSchema) { schemaRequestMutation.mutate(userInput); } @@ -136,19 +135,6 @@ function TopicSchemaRequest(props: TopicSchemaRequestProps) { Could not fetch environments. )} - {successModalOpen && ( - - Redirecting to My team's request page shortly. Select - "Continue" for an immediate redirect. - - )} {schemaRequestMutation.isError && ( diff --git a/coral/src/app/pages/topics/schema-request.test.tsx b/coral/src/app/pages/topics/schema-request.test.tsx index 2ff6464158..dd4786a380 100644 --- a/coral/src/app/pages/topics/schema-request.test.tsx +++ b/coral/src/app/pages/topics/schema-request.test.tsx @@ -1,3 +1,4 @@ +import { Context as AquariumContext } from "@aivenio/aquarium"; import { cleanup, screen } from "@testing-library/react/pure"; import SchemaRequest from "src/app/pages/topics/schema-request"; import { getQueryClientForTests } from "src/services/test-utils/query-client-tests"; @@ -40,7 +41,11 @@ describe("SchemaRequest", () => { } + element={ + + + + } />