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", () => {