import React from "react"; import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MemoryRouter } from "react-router"; import { emailService } from "#/api/email-service/email-service.api"; import { EmailVerificationModal } from "#/components/features/waitlist/email-verification-modal"; import * as ToastHandlers from "#/utils/custom-toast-handlers"; import { renderWithProviders, createAxiosError } from "../../../../test-utils"; describe("EmailVerificationModal", () => { const mockOnClose = vi.fn(); const resendEmailVerificationSpy = vi.spyOn( emailService, "resendEmailVerification", ); const displaySuccessToastSpy = vi.spyOn(ToastHandlers, "displaySuccessToast"); const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast"); const renderWithRouter = (ui: React.ReactElement) => { return renderWithProviders({ui}); }; beforeEach(() => { vi.clearAllMocks(); }); it("should render the email verification message", () => { // Arrange & Act renderWithRouter(); // Assert expect( screen.getByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"), ).toBeInTheDocument(); }); it("should render the rate limited message when wasRateLimited is true", () => { // Arrange & Act renderWithRouter( , ); // Assert - should show the rate limited message instead of the default one expect( screen.getByText("AUTH$CHECK_INBOX_FOR_VERIFICATION_EMAIL"), ).toBeInTheDocument(); expect( screen.queryByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"), ).not.toBeInTheDocument(); }); it("should render the TermsAndPrivacyNotice component", () => { // Arrange & Act renderWithRouter(); // Assert const termsSection = screen.getByTestId("terms-and-privacy-notice"); expect(termsSection).toBeInTheDocument(); }); it("should render resend verification button", () => { // Arrange & Act renderWithRouter(); // Assert expect( screen.getByText("SETTINGS$RESEND_VERIFICATION"), ).toBeInTheDocument(); }); it("should call resendEmailVerification when the button is clicked", async () => { // Arrange const userId = "test_user_id"; resendEmailVerificationSpy.mockResolvedValue({ message: "Email verification message sent", }); renderWithRouter( , ); // Act const resendButton = screen.getByText("SETTINGS$RESEND_VERIFICATION"); await userEvent.click(resendButton); // Assert await waitFor(() => { expect(resendEmailVerificationSpy).toHaveBeenCalledWith({ userId, isAuthFlow: true, }); }); }); it("should display success toast when resend succeeds", async () => { // Arrange resendEmailVerificationSpy.mockResolvedValue({ message: "Email verification message sent", }); renderWithRouter(); // Act const resendButton = screen.getByText("SETTINGS$RESEND_VERIFICATION"); await userEvent.click(resendButton); // Assert await waitFor(() => { expect(displaySuccessToastSpy).toHaveBeenCalledWith( "SETTINGS$VERIFICATION_EMAIL_SENT", ); }); }); it("should display rate limit error message when receiving 429 status", async () => { // Arrange const rateLimitError = createAxiosError(429, "Too Many Requests", { detail: "Too many requests. Please wait 2 minutes before trying again.", }); resendEmailVerificationSpy.mockRejectedValue(rateLimitError); renderWithRouter(); // Act const resendButton = screen.getByText("SETTINGS$RESEND_VERIFICATION"); await userEvent.click(resendButton); // Assert await waitFor(() => { expect(displayErrorToastSpy).toHaveBeenCalledWith( "Too many requests. Please wait 2 minutes before trying again.", ); }); }); it("should display generic error message when receiving non-429 error", async () => { // Arrange const genericError = createAxiosError(500, "Internal Server Error", { error: "Internal server error", }); resendEmailVerificationSpy.mockRejectedValue(genericError); renderWithRouter(); // Act const resendButton = screen.getByText("SETTINGS$RESEND_VERIFICATION"); await userEvent.click(resendButton); // Assert await waitFor(() => { expect(displayErrorToastSpy).toHaveBeenCalledWith( "SETTINGS$FAILED_TO_RESEND_VERIFICATION", ); }); }); it("should disable button and show sending text while request is pending", async () => { // Arrange let resolvePromise: (value: { message: string }) => void; const pendingPromise = new Promise<{ message: string }>((resolve) => { resolvePromise = resolve; }); resendEmailVerificationSpy.mockReturnValue(pendingPromise); renderWithRouter(); // Act const resendButton = screen.getByText("SETTINGS$RESEND_VERIFICATION"); await userEvent.click(resendButton); // Assert await waitFor(() => { const sendingButton = screen.getByText("SETTINGS$SENDING"); expect(sendingButton).toBeInTheDocument(); expect(sendingButton).toBeDisabled(); }); // Cleanup resolvePromise!({ message: "Email verification message sent" }); }); it("should re-enable button after request completes", async () => { // Arrange resendEmailVerificationSpy.mockResolvedValue({ message: "Email verification message sent", }); renderWithRouter(); // Act const resendButton = screen.getByText("SETTINGS$RESEND_VERIFICATION"); await userEvent.click(resendButton); // Assert await waitFor(() => { expect(resendEmailVerificationSpy).toHaveBeenCalled(); }); // After successful send, the button will be disabled due to cooldown // So we just verify the mutation was called successfully await waitFor(() => { const button = screen.getByRole("button"); expect(button).toBeDisabled(); // Button is disabled during cooldown expect(button).toHaveTextContent(/SETTINGS\$RESEND_VERIFICATION/); }); }); });