From 7095927ebd9009accd0c1b4580182c6871761344 Mon Sep 17 00:00:00 2001 From: Harold Torres Date: Fri, 3 Apr 2026 17:22:05 +1100 Subject: [PATCH] fix: enhance RecommendationsClient integration tests and improve state management - Update integration tests for RecommendationsClient to include strict mode scenarios and error handling. - Refactor the RecommendationsClient component to manage submission state more effectively using refs. - Ensure proper cleanup of effects to prevent unintended submissions when the component unmounts. --- ...RecommendationsClient.integration.test.tsx | 58 ++++++++++++++++++- .../recommendations/RecommendationsClient.tsx | 12 ++-- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/app/(routes)/recommendations/RecommendationsClient.integration.test.tsx b/app/(routes)/recommendations/RecommendationsClient.integration.test.tsx index 2112505..267bbff 100644 --- a/app/(routes)/recommendations/RecommendationsClient.integration.test.tsx +++ b/app/(routes)/recommendations/RecommendationsClient.integration.test.tsx @@ -1,6 +1,6 @@ import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { StrictMode, useEffect, useState } from "react"; import RecommendationsClient from "./RecommendationsClient"; import { MovieProvider, useMovieContext } from "@/contexts/MovieContext"; import { @@ -53,8 +53,8 @@ function MovieSessionSnapshot() { ); } -function renderRecommendations() { - return render( +function renderRecommendations({ strictMode = false }: { strictMode?: boolean } = {}) { + const content = ( @@ -62,6 +62,8 @@ function renderRecommendations() { ); + + return render(strictMode ? {content} : content); } function getRequestUrl(input: RequestInfo | URL): string { @@ -121,6 +123,38 @@ describe("RecommendationsClient integration", () => { expect(screen.getByAltText("Mad Max: Fury Road")).toBeInTheDocument(); }); + it("still submits recommendations in strict mode after effect replay", async () => { + let recommendationRequestCount = 0; + + global.fetch = jest.fn((input: RequestInfo | URL) => { + const url = getRequestUrl(input); + + if (url === "/api/recommendations") { + recommendationRequestCount += 1; + return Promise.resolve(createRecommendationStreamResponse()); + } + + if (url.startsWith("https://api.themoviedb.org/")) { + return Promise.resolve( + createJsonResponse(tmdbFixtures.postersByQuery["Mad Max: Fury Road"]) + ); + } + + return Promise.reject(new Error(`Unhandled fetch: ${url}`)); + }) as typeof fetch; + + renderRecommendations({ strictMode: true }); + + await waitFor(() => { + expect(recommendationRequestCount).toBeGreaterThan(0); + expect( + screen.getByRole("heading", { name: "Mad Max: Fury Road (2015)" }) + ).toBeInTheDocument(); + }); + + expect(screen.getByText("Movie 1 of 2")).toBeInTheDocument(); + }); + it("renders a no-results state when the streamed response is empty", async () => { global.fetch = jest.fn((input: RequestInfo | URL) => { const url = getRequestUrl(input); @@ -166,6 +200,24 @@ describe("RecommendationsClient integration", () => { expect(screen.getByText(/Pipeline failed/)).toBeInTheDocument(); }); + it("renders an error message when the recommendations request cannot be reached at the network level", async () => { + global.fetch = jest.fn((input: RequestInfo | URL) => { + const url = getRequestUrl(input); + + if (url === "/api/recommendations") { + return Promise.reject(new Error("Failed to fetch")); + } + + return Promise.reject(new Error(`Unhandled fetch: ${url}`)); + }) as typeof fetch; + + renderRecommendations(); + + await waitFor(() => { + expect(screen.getByText("Oops! Something went wrong")).toBeInTheDocument(); + }); + }); + it("clears the movie session and routes home when Start Over is pressed", async () => { global.fetch = jest.fn((input: RequestInfo | URL) => { const url = getRequestUrl(input); diff --git a/app/(routes)/recommendations/RecommendationsClient.tsx b/app/(routes)/recommendations/RecommendationsClient.tsx index 3de6eaa..21e3d20 100644 --- a/app/(routes)/recommendations/RecommendationsClient.tsx +++ b/app/(routes)/recommendations/RecommendationsClient.tsx @@ -62,6 +62,7 @@ export default function RecommendationsClient() { const [state, dispatch] = useReducer(recommendationsViewReducer, initialRecommendationsState); const { currentIndex, posterUrls, isLoadingPoster } = state; const hasSubmittedRef = useRef(false); + const wasStoppedRef = useRef(false); useEffect(() => { if (participantsData.length === 0) { @@ -75,14 +76,17 @@ export default function RecommendationsClient() { }); useEffect(() => { - if (participantsData.length > 0 && !hasSubmittedRef.current) { - hasSubmittedRef.current = true; - submit({ participantsData, timeAvailable }); - } + if (participantsData.length === 0) return; + if (hasSubmittedRef.current && !wasStoppedRef.current) return; + + hasSubmittedRef.current = true; + wasStoppedRef.current = false; + void submit({ participantsData, timeAvailable }); }, [participantsData, timeAvailable, submit]); useEffect(() => { return () => { + wasStoppedRef.current = true; stop(); }; }, [stop]);