import { QueryClientProvider } from "@tanstack/react-query"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { useLanguageCheckQuery } from "@/core/queries"; import { Step3Description } from "@/components/Exercises/screens/Add/Step3Description"; import { setDescriptionEn, setNotesEn, } from "@/components/Exercises/screens/Add/state/exerciseSubmissionReducer"; import React from "react"; import { testQueryClient } from "@/tests/queryClient"; import type { Mock } from "vitest"; vi.mock("@/core/queries"); vi.mock("@/components/Exercises/screens/Add/state/exerciseSubmissionReducer", async () => { const originalModule = await vi.importActual( "@/components/Exercises/screens/Add/state/exerciseSubmissionReducer" ); return { __esModule: true, ...originalModule, setDescriptionEn: vi.fn(), setNotesEn: vi.fn(), }; }); const mockedUseLanguageCheckQuery = useLanguageCheckQuery as Mock; const mockOnContinue = vi.fn(); const mockOnBack = vi.fn(); let mutateAsync: Mock; function renderStep() { render( ); } describe("Test the add exercise step 3 component", () => { beforeEach(() => { mutateAsync = vi.fn().mockResolvedValue({ success: true, data: {} }); mockedUseLanguageCheckQuery.mockImplementation(() => ({ isPending: false, mutateAsync, })); }); afterEach(() => { vi.clearAllMocks(); }); test("Renders without crashing", () => { renderStep(); expect(screen.getByText("continue")).toBeInTheDocument(); expect(screen.getByText("goBack")).toBeInTheDocument(); }); test("clicking 'goBack' calls onBack", async () => { const user = userEvent.setup(); renderStep(); await user.click(screen.getByText("goBack")); expect(mockOnBack).toHaveBeenCalled(); }); test("the continue button is disabled while the language check is pending", () => { mockedUseLanguageCheckQuery.mockImplementation(() => ({ isPending: true, mutateAsync, })); renderStep(); const continueBtn = screen.getByRole("button", { name: /continue/i }); expect(continueBtn).toBeDisabled(); }); // The yup validator requires at least 40 characters, so any happy-path test // has to fill the markdown editor before submitting. const VALID_DESCRIPTION = "This is a long enough description for the exercise that satisfies the minimum length validator."; async function fillDescription(user: ReturnType, value: string) { // The MarkdownEditor TextField uses t('useMarkdownHint') as placeholder; // in the test setup translations resolve to the key. const textarea = screen.getByPlaceholderText("useMarkdownHint"); await user.clear(textarea); await user.type(textarea, value); } test("on success: dispatches description/notes and calls onContinue", async () => { const user = userEvent.setup(); renderStep(); await fillDescription(user, VALID_DESCRIPTION); await user.click(screen.getByText("continue")); // language check was triggered with the typed value await waitFor(() => { expect(mutateAsync).toHaveBeenCalled(); }); const [callArg] = (mutateAsync as Mock).mock.calls[0]; expect(callArg.input).toBe(VALID_DESCRIPTION); // both pieces of state are persisted expect(setDescriptionEn).toHaveBeenCalledWith(VALID_DESCRIPTION); expect(setNotesEn).toHaveBeenCalled(); // continue was called await waitFor(() => { expect(mockOnContinue).toHaveBeenCalled(); }); }); test("on language-check failure: still dispatches but does not call onContinue", async () => { // The mutation itself doesn't reject - it returns the error payload directly. // The component branches on whether 'success' is in the result. mutateAsync = vi.fn().mockResolvedValue({ check: { message: "this does not look like English" }, }); mockedUseLanguageCheckQuery.mockImplementation(() => ({ isPending: false, mutateAsync, })); const user = userEvent.setup(); renderStep(); await fillDescription(user, VALID_DESCRIPTION); await user.click(screen.getByText("continue")); await waitFor(() => { expect(mutateAsync).toHaveBeenCalled(); }); // State is still saved before the early return expect(setDescriptionEn).toHaveBeenCalledWith(VALID_DESCRIPTION); expect(setNotesEn).toHaveBeenCalled(); // But the user does NOT advance to the next step expect(mockOnContinue).not.toHaveBeenCalled(); }); });