Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed Enhanced-Suspension-Test-Guide.md
Empty file.
Empty file removed SUSPENSION-POPUP-SUMMARY.md
Empty file.
255 changes: 245 additions & 10 deletions __tests__/pages/AdminReports.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
// __tests__/pages/ReportingContent.test.tsx
import React from "react";
import { render, screen, waitFor, act } from "@testing-library/react";
import { render, screen, waitFor, act, fireEvent, within } from "@testing-library/react";
import "@testing-library/jest-dom";
import ReportingContent from "@/components/Admin/dashboardContent/ReportingContent";
import toast from "react-hot-toast";

const mockFetch = (response: any, ok = true, status = 500) => {
(global as any).fetch = jest.fn().mockResolvedValue({
ok,
status,
json: () => Promise.resolve(response),
jest.mock("react-hot-toast", () => ({
loading: jest.fn(),
success: jest.fn(),
error: jest.fn(),
dismiss: jest.fn(),
}));

beforeAll(() => {
(global as any).URL.createObjectURL = jest.fn(() => "blob:url");
(global as any).URL.revokeObjectURL = jest.fn();
});

const mockFetch = (...responses: any[]) => {
(global as any).fetch = jest.fn();
responses.forEach((res) => {
(global as any).fetch.mockResolvedValueOnce({
ok: res.ok ?? true,
status: res.status ?? 200,
json: () => Promise.resolve(res.body),
headers: { get: (n: string) => res.headers?.[n] || null },
blob: () =>
Promise.resolve(
new Blob(["file"], {
type: res.headers?.["content-type"] || "application/pdf",
})
),
});
});
};

Expand All @@ -18,15 +41,227 @@ describe("ReportingContent", () => {
});

it("displays an error when the fetch fails", async () => {
mockFetch({ message: "Oops" }, false, 500);
mockFetch({ body: { message: "Oops" }, ok: false, status: 500 });
await act(async () => {
render(<ReportingContent />);
});
await waitFor(() =>
expect(screen.getByText("Error Loading Reports")).toBeInTheDocument()
);
expect(
screen.getByText("HTTP error! status: 500")
).toBeInTheDocument();
// the component renders the raw error text it got from `.text()`
expect(screen.getByText("res.text is not a function")).toBeInTheDocument();
});

it("renders reports table on successful fetch", async () => {
const reports = [
{
_id: "1",
reportedBy: { _id: "a", firstName: "Alice", lastName: "Reporter", email: "a@example.com" },
reportedUser: { _id: "b", firstName: "Bob", lastName: "Reported", email: "b@example.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "pending",
createdAt: new Date().toISOString(),
},
];
mockFetch({ body: reports });
await act(async () => {
render(<ReportingContent />);
});
await screen.findByText("Alice Reporter");
expect(screen.getByText("Bob Reported")).toBeInTheDocument();
const rows = await screen.findAllByRole("row");
expect(rows).toHaveLength(2);
});

it("filters by search and status", async () => {
const reports = [
{
_id: "1",
reportedBy: { _id: "a", firstName: "Alice", lastName: "Reporter", email: "a@e.com" },
reportedUser: { _id: "b", firstName: "Bob", lastName: "Reported", email: "b@e.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "pending",
createdAt: new Date().toISOString(),
},
{
_id: "2",
reportedBy: { _id: "c", firstName: "Carol", lastName: "Reporter", email: "c@e.com" },
reportedUser: { _id: "d", firstName: "Dave", lastName: "Reported", email: "d@e.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "resolved",
createdAt: new Date().toISOString(),
},
];
mockFetch({ body: reports });
await act(async () => {
render(<ReportingContent />);
});
await screen.findByText("Alice Reporter");

const search = screen.getByPlaceholderText(
"Search by name, email, reason , or report ID…"
);
fireEvent.change(search, { target: { value: "Bob" } });
expect(screen.getByText("Bob Reported")).toBeInTheDocument();
expect(screen.queryByText("Dave Reported")).toBeNull();

fireEvent.change(search, { target: { value: "" } });
const select = screen.getByDisplayValue(/All/);
fireEvent.change(select, { target: { value: "resolved" } });
expect(screen.getByText("Dave Reported")).toBeInTheDocument();
expect(screen.queryByText("Bob Reported")).toBeNull();
});

it("toggles sort direction", async () => {
const now = Date.now();
const reports = [
{
_id: "1",
reportedBy: { _id: "o", firstName: "Old", lastName: "Reporter", email: "o@e.com" },
reportedUser: { _id: "1b", firstName: "Old", lastName: "Reported", email: "o2@e.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "pending",
createdAt: new Date(now - 1000).toISOString(),
},
{
_id: "2",
reportedBy: { _id: "n", firstName: "New", lastName: "Reporter", email: "n@e.com" },
reportedUser: { _id: "2b", firstName: "New", lastName: "Reported", email: "n2@e.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "pending",
createdAt: new Date(now).toISOString(),
},
];
mockFetch({ body: reports });
await act(async () => {
render(<ReportingContent />);
});
await screen.findByText("Old Reporter");

let rows = screen.getAllByRole("row");
expect(within(rows[1]).getByText("New Reporter")).toBeInTheDocument();

// there are two sort buttons: one for desc, one for asc
const [descBtn, ascBtn] = screen.getAllByTitle(/Sort by date/);
// click the ascending icon to flip to oldest first
fireEvent.click(ascBtn);

rows = screen.getAllByRole("row");
expect(within(rows[1]).getByText("Old Reporter")).toBeInTheDocument();
});

it("downloads evidence file", async () => {
const reports = [
{
_id: "1",
reportedBy: { _id: "a", firstName: "Alice", lastName: "Reporter", email: "a@e.com" },
reportedUser: { _id: "b", firstName: "Bob", lastName: "Reported", email: "b@e.com" },
reason: "other",
description: "",
evidenceFiles: ["/proof.pdf"],
status: "pending",
createdAt: new Date().toISOString(),
},
];
mockFetch(
{ body: reports },
{ body: {}, headers: { "content-type": "application/pdf" } }
);
await act(async () => {
render(<ReportingContent />);
});
await screen.findByText("Alice Reporter");

fireEvent.click(screen.getByTitle(/Download evidence/));
await waitFor(() => {
expect(global.fetch).toHaveBeenLastCalledWith(
`/api/file/retrieve?fileUrl=${encodeURIComponent("/proof.pdf")}`
);
expect(toast.success).toHaveBeenCalledWith(
"Evidence file downloaded successfully"
);
});
});

it("marks under review", async () => {
const report = {
_id: "1",
reportedBy: { _id: "a", firstName: "Alice", lastName: "Reporter", email: "a@e.com" },
reportedUser: { _id: "b", firstName: "Bob", lastName: "Reported", email: "b@e.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "pending",
createdAt: new Date().toISOString(),
};
mockFetch({ body: [report] }, { body: {} });
window.confirm = jest.fn(() => true);

await act(async () => {
render(<ReportingContent />);
});
await screen.findByText("Alice Reporter");

fireEvent.click(screen.getByTitle("Mark as Under Review"));
await waitFor(() => {
expect(global.fetch).toHaveBeenLastCalledWith(
"/api/admin/reports/1/notify",
expect.objectContaining({ method: "POST" })
);
expect(toast.success).toHaveBeenCalledWith(
"Status updated to Under Review"
);
const row = screen.getByText("Alice Reporter").closest("tr")!;
expect(within(row).getByText("Under Review")).toBeInTheDocument();
});
});

it("resolve and dismiss actions", async () => {
const base = {
_id: "1",
reportedBy: { _id: "a", firstName: "Alice", lastName: "Reporter", email: "a@e.com" },
reportedUser: { _id: "b", firstName: "Bob", lastName: "Reported", email: "b@e.com" },
reason: "other",
description: "",
evidenceFiles: [],
status: "under_review",
createdAt: new Date().toISOString(),
};
mockFetch({ body: [base] }, { body: {} }, { body: {} });
window.confirm = jest.fn(() => true);
window.alert = jest.fn();

await act(async () => {
render(<ReportingContent />);
});
await screen.findByText("Alice Reporter");

fireEvent.click(screen.getByTitle("Mark resolved"));
await waitFor(() => {
expect(global.fetch).toHaveBeenNthCalledWith(
2,
"/api/admin/reports/1/resolve",
expect.objectContaining({
method: "POST",
body: JSON.stringify({ resolution: "mark_resolved" }),
})
);
expect(window.alert).toHaveBeenCalledWith(
"Report marked as resolved successfully!"
);
});

const row = screen.getByText("Alice Reporter").closest("tr")!;
expect(within(row).getByText("Resolved")).toBeInTheDocument();
});
});
Loading