From 95b8e69bb41542befc39da2da0afe14013b854d9 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 27 Jul 2026 12:46:20 -0300 Subject: [PATCH] feat: enhance tests for useJobsData and auth service with new scenarios --- .../unit/hooks/useJobsData.extended.test.tsx | 51 +++++++++++++++++++ .../new_dashboard/branch-coverage.test.tsx | 22 ++++++++ .../unit/new_dashboard/theme.toast.test.tsx | 6 +++ .../tests/unit/services/auth.service.test.tsx | 44 ++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/frontend/tests/unit/hooks/useJobsData.extended.test.tsx b/frontend/tests/unit/hooks/useJobsData.extended.test.tsx index 44c5ba8..8ec2036 100644 --- a/frontend/tests/unit/hooks/useJobsData.extended.test.tsx +++ b/frontend/tests/unit/hooks/useJobsData.extended.test.tsx @@ -67,6 +67,38 @@ describe("useJobsData extended", () => { expect(result.current.error).toBe("falha jobs"); }); + it("aplica os metadados padrão quando a API retorna payload parcial", async () => { + mocks.fetchJobsByAPIMock.mockResolvedValueOnce({}); + + const { result } = renderHook(() => useJobsData(3, 10)); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + expect(result.current.meta).toEqual({ + total: 0, + hasNext: false, + hasPrev: false, + page: 3, + limit: 10, + totalPages: 0, + }); + }); + + expect(result.current.jobs).toEqual([]); + }); + + it("usa mensagem padrão quando carregar vagas rejeita sem Error", async () => { + mocks.fetchJobsByAPIMock.mockRejectedValueOnce("offline"); + + const { result } = renderHook(() => useJobsData()); + + await waitFor(() => { + expect(result.current.error).toBe( + "Erro inesperado ao carregar vagas.", + ); + }); + }); + it("triggerScraper carrega jobs após executar request", async () => { const { result } = renderHook(() => useJobsData()); @@ -78,4 +110,23 @@ describe("useJobsData extended", () => { expect(mocks.fetchJobsByAPIMock).toHaveBeenCalled(); expect(result.current.scraping).toBe(false); }); + + it("usa mensagem padrão quando o scraper rejeita sem Error", async () => { + mocks.runScraperRequestMock.mockRejectedValueOnce("indisponível"); + + const { result } = renderHook(() => useJobsData()); + + await waitFor(() => { + expect(result.current.loading).toBe(false); + }); + + await act(async () => { + await result.current.triggerScraper(); + }); + + expect(result.current.error).toBe( + "Erro inesperado ao executar o scraper.", + ); + expect(result.current.scraping).toBe(false); + }); }); diff --git a/frontend/tests/unit/new_dashboard/branch-coverage.test.tsx b/frontend/tests/unit/new_dashboard/branch-coverage.test.tsx index 8af7f13..765865e 100644 --- a/frontend/tests/unit/new_dashboard/branch-coverage.test.tsx +++ b/frontend/tests/unit/new_dashboard/branch-coverage.test.tsx @@ -6,6 +6,7 @@ import { Header } from "@/domains/new_dashboard/components/layout/Header"; import { MessageDetailModal } from "@/domains/new_dashboard/components/layout/MessageDetailModal"; import { JobDetailModal } from "@/domains/new_dashboard/components/jobs/JobDetailModal"; import { JobRow } from "@/domains/new_dashboard/components/jobs/JobRow"; +import { MentoringTab } from "@/domains/new_dashboard/components/mentoring/MentoringTab"; import { ProfileForm } from "@/domains/new_dashboard/components/profile/ProfileForm"; import { Modal } from "@/domains/new_dashboard/components/shared/Modal"; import { @@ -236,6 +237,27 @@ describe("new_dashboard branch coverage", () => { expect(onClose).toHaveBeenCalledOnce(); }); + it("abre, confirma e fecha os detalhes de uma mentoria", () => { + render(); + + fireEvent.click(screen.getAllByRole("button", { name: "Ver Mais" })[0]); + + expect( + screen.getByRole("heading", { name: "Julio Silva", level: 2 }), + ).toBeInTheDocument(); + expect(screen.queryByText(/participação confirmada/i)).not.toBeInTheDocument(); + + fireEvent.click( + screen.getByRole("button", { name: /confirmar participação/i }), + ); + expect(screen.getByText(/participação confirmada/i)).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: "Fechar" })); + expect( + screen.queryByRole("heading", { name: "Julio Silva", level: 2 }), + ).not.toBeInTheDocument(); + }); + it("usa origem sistema como fallback no detalhe da mensagem", () => { render( { expect(container).toBeEmptyDOMElement(); }); + it("renderiza toast quando recebe uma mensagem", () => { + render(); + + expect(screen.getByText("Perfil atualizado")).toBeInTheDocument(); + }); + it("lança erro quando o contexto de toast é usado fora do provider", () => { expect(() => render()).toThrow( /useToastContext must be used within ToastProvider/i, diff --git a/frontend/tests/unit/services/auth.service.test.tsx b/frontend/tests/unit/services/auth.service.test.tsx index 96c888d..ae0773a 100644 --- a/frontend/tests/unit/services/auth.service.test.tsx +++ b/frontend/tests/unit/services/auth.service.test.tsx @@ -33,6 +33,50 @@ describe("authService", () => { beforeEach(() => { fetchMock.mockReset(); vi.restoreAllMocks(); + vi.unstubAllEnvs(); + }); + + describe("response parsing and URL normalization", () => { + it("uses the configured API URL without trailing slashes", async () => { + vi.stubEnv("VITE_API_BASE_URL", "https://api.example.com///"); + fetchMock.mockResolvedValueOnce(mockResponse({ jsonData: { ok: true } })); + + await expect(auth.logout()).resolves.toEqual({ ok: true }); + + expect(fetchMock).toHaveBeenCalledWith( + "https://api.example.com/auth/logout", + expect.objectContaining({ method: "POST" }), + ); + }); + + it("parses non-JSON response bodies as messages", async () => { + fetchMock.mockResolvedValueOnce( + mockResponse({ + contentType: "text/plain", + textData: "Logout concluído", + }), + ); + + await expect(auth.logout()).resolves.toEqual({ + message: "Logout concluído", + }); + }); + + it("returns an empty payload for an empty response body", async () => { + fetchMock.mockResolvedValueOnce( + mockResponse({ contentType: "", textData: "" }), + ); + + await expect(auth.logout()).resolves.toEqual({}); + }); + + it("returns an empty payload when response parsing fails", async () => { + const response = mockResponse({ contentType: "application/json" }); + response.json.mockRejectedValueOnce(new SyntaxError("invalid JSON")); + fetchMock.mockResolvedValueOnce(response); + + await expect(auth.logout()).resolves.toEqual({}); + }); }); describe("login", () => {