import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { renderHook, waitFor, act } from "@testing-library/react"; import { server } from "../../mocks/server"; import { errorHandlers } from "../../mocks/handlers"; import { useSegmentation } from "../useSegmentation"; describe("useSegmentation", () => { beforeEach(() => { vi.useFakeTimers({ shouldAdvanceTime: true }); }); afterEach(() => { vi.useRealTimers(); }); it("starts with null result and not loading", () => { const { result } = renderHook(() => useSegmentation()); expect(result.current.result).toBeNull(); expect(result.current.isLoading).toBe(false); expect(result.current.error).toBeNull(); expect(result.current.jobStatus).toBeNull(); }); it("sets loading state and job status during segmentation", async () => { const { result } = renderHook(() => useSegmentation()); act(() => { result.current.runSegmentation("sub-stroke0001"); }); expect(result.current.isLoading).toBe(true); // Wait for job to be created await waitFor(() => { expect(result.current.jobId).toBeDefined(); }); expect(result.current.jobStatus).toBeDefined(); }); it("returns result on job completion", async () => { const { result } = renderHook(() => useSegmentation()); act(() => { result.current.runSegmentation("sub-stroke0001"); }); // Wait for job creation await waitFor(() => { expect(result.current.jobId).toBeDefined(); }); // Advance time to allow job to complete (mock jobs complete in ~3s) await act(async () => { await vi.advanceTimersByTimeAsync(5000); }); await waitFor(() => { expect(result.current.isLoading).toBe(false); expect(result.current.result).not.toBeNull(); }); expect(result.current.result?.metrics.caseId).toBe("sub-stroke0001"); expect(result.current.result?.metrics.diceScore).toBe(0.847); expect(result.current.result?.dwiUrl).toContain("dwi.nii.gz"); }); it("shows progress updates during job execution", async () => { const { result } = renderHook(() => useSegmentation()); act(() => { result.current.runSegmentation("sub-stroke0001"); }); // Wait for job to start await waitFor(() => { expect(result.current.jobId).toBeDefined(); }); // Progress should be tracked expect(result.current.progress).toBeGreaterThanOrEqual(0); expect(result.current.progressMessage).toBeDefined(); }); it("sets error on job creation failure", async () => { server.use(errorHandlers.segmentCreateError); const { result } = renderHook(() => useSegmentation()); act(() => { result.current.runSegmentation("sub-stroke0001"); }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); expect(result.current.error).toMatch(/failed to create job/i); expect(result.current.result).toBeNull(); }); it("clears previous error on new request", async () => { server.use(errorHandlers.segmentCreateError); const { result } = renderHook(() => useSegmentation()); // First request fails act(() => { result.current.runSegmentation("sub-stroke0001"); }); await waitFor(() => { expect(result.current.error).not.toBeNull(); }); // Reset to success handler server.resetHandlers(); // Second request should clear error act(() => { result.current.runSegmentation("sub-stroke0001"); }); expect(result.current.error).toBeNull(); expect(result.current.isLoading).toBe(true); }); it("can cancel a running job", async () => { const { result } = renderHook(() => useSegmentation()); act(() => { result.current.runSegmentation("sub-stroke0001"); }); await waitFor(() => { expect(result.current.isLoading).toBe(true); }); // Cancel the job act(() => { result.current.cancelJob(); }); expect(result.current.isLoading).toBe(false); expect(result.current.jobStatus).toBeNull(); }); it("cleans up polling on unmount", async () => { const { result, unmount } = renderHook(() => useSegmentation()); act(() => { result.current.runSegmentation("sub-stroke0001"); }); await waitFor(() => { expect(result.current.isLoading).toBe(true); }); // Unmount should not throw unmount(); }); });