From 312d8f19003b5682e5ec1cb7d5eaa28accfc62c9 Mon Sep 17 00:00:00 2001 From: Shiva Shankara Vara Prasad Date: Fri, 25 Sep 2026 15:54:49 +0530 Subject: [PATCH 1/4] Handle disabled college TPO directory state --- src/pages/CollegeTpoDirectoryPage.jsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pages/CollegeTpoDirectoryPage.jsx b/src/pages/CollegeTpoDirectoryPage.jsx index f03eb674..d9b34676 100644 --- a/src/pages/CollegeTpoDirectoryPage.jsx +++ b/src/pages/CollegeTpoDirectoryPage.jsx @@ -88,12 +88,26 @@ export default function CollegeTpoDirectoryPage() { + ) : data?.enabled === false ? ( +
+
+
+
+

+ College TPO directory is not live yet +

+

+ {data.message || "The college support directory is currently being prepared."} +

+
+
) : error ? (

TPO directory unavailable

{error}

- ) : ( + ) : data?.college && Array.isArray(data.tpos) ? ( <>
@@ -150,6 +164,13 @@ export default function CollegeTpoDirectoryPage() {
)} + ) : ( +
+

TPO directory unavailable

+

+ We received an unexpected response. Please try again later. +

+
)}
From 325859e8ea949ca08cbe52fcffb9a344bd45f933 Mon Sep 17 00:00:00 2001 From: Shiva Shankara Vara Prasad Date: Fri, 25 Sep 2026 15:58:07 +0530 Subject: [PATCH 2/4] Add college TPO directory backend regression tests --- backend/routes/tpo.test.js | 91 +++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/backend/routes/tpo.test.js b/backend/routes/tpo.test.js index 06f65961..eedc54f8 100644 --- a/backend/routes/tpo.test.js +++ b/backend/routes/tpo.test.js @@ -733,4 +733,93 @@ describe("GET /college-directory", () => { expect(res.status).toHaveBeenCalledWith(403); expect(User.find).not.toHaveBeenCalled(); }); -}); + + it("returns a stable error code when the student's college email is unverified", async () => { + const res = await runRoute("get", "/college-directory", { + userDoc: { role: "student", emailDomain: "report.edu", education: { emailVerified: false } }, + }); + + expect(res.status).toHaveBeenCalledWith(403); + expect(res.json).toHaveBeenCalledWith({ + error: "Verify your college email to view your college TPO directory.", + code: "COLLEGE_EMAIL_UNVERIFIED", + }); + expect(College.findById).not.toHaveBeenCalled(); + expect(College.findOne).not.toHaveBeenCalled(); + expect(User.find).not.toHaveBeenCalled(); + }); + + it("resolves a verified college from the student's email domain when collegeId is missing", async () => { + College.findOne.mockReturnValue({ + lean: vi.fn().mockResolvedValue({ + _id: "college-domain", + name: "Domain University", + status: "verified", + domains: ["report.edu"], + primaryTpo: null, + }), + }); + + const res = await runRoute("get", "/college-directory", { + userDoc: { + role: "student", + emailDomain: "REPORT.EDU", + education: { emailVerified: true }, + }, + }); + + expect(res.json).toHaveBeenCalledWith({ + college: { id: "college-domain", name: "Domain University" }, + tpos: expect.any(Array), + }); + expect(College.findOne).toHaveBeenCalledWith({ + domains: "report.edu", + status: "verified", + }); + }); + + it("returns 404 when the verified student's college cannot be resolved to a verified institution", async () => { + College.findById.mockReturnValue({ + lean: vi.fn().mockResolvedValue(null), + }); + College.findOne.mockReturnValue({ + lean: vi.fn().mockResolvedValue(null), + }); + + const res = await runRoute("get", "/college-directory", { + userDoc: { + role: "student", + emailDomain: "unknown.edu", + education: { emailVerified: true, collegeId: "missing-college" }, + }, + }); + + expect(res.status).toHaveBeenCalledWith(404); + expect(res.json).toHaveBeenCalledWith({ + error: "Your college is not linked to a verified institution yet.", + }); + expect(User.find).not.toHaveBeenCalled(); + }); + + it("does not expose TPOs from another college when the student's college has multiple domains", async () => { + College.findById.mockReturnValue({ + lean: vi.fn().mockResolvedValue({ + _id: "college-1", + name: "Report University", + status: "verified", + domains: ["report.edu", "legacy.report.edu"], + primaryTpo: null, + }), + }); + + const res = await runRoute("get", "/college-directory", { userDoc: verifiedStudent }); + + expect(res.status).not.toHaveBeenCalledWith(403); + expect(User.find).toHaveBeenCalledWith( + expect.objectContaining({ + "tpoProfile.collegeDomain": { $in: ["report.edu", "legacy.report.edu"] }, + }) + ); + }); + +}); \ No newline at end of file From c2b967b06f386259bb13f4151973cb7e54455d10 Mon Sep 17 00:00:00 2001 From: Shiva Shankara Vara Prasad Date: Fri, 25 Sep 2026 16:06:22 +0530 Subject: [PATCH 3/4] Add College TPO directory frontend state tests --- src/pages/CollegeTpoDirectoryPage.test.jsx | 69 +++++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/pages/CollegeTpoDirectoryPage.test.jsx b/src/pages/CollegeTpoDirectoryPage.test.jsx index 5e938beb..90ddbe96 100644 --- a/src/pages/CollegeTpoDirectoryPage.test.jsx +++ b/src/pages/CollegeTpoDirectoryPage.test.jsx @@ -3,11 +3,16 @@ import { render, screen } from "@testing-library/react"; import CollegeTpoDirectoryPage from "./CollegeTpoDirectoryPage"; const apiFetch = vi.fn(); +const share = vi.fn().mockResolvedValue(undefined); vi.mock("../services/api", () => ({ apiFetch: (...args) => apiFetch(...args), })); +vi.mock("../utils/share", () => ({ + share: (...args) => share(...args), +})); + vi.mock("../layouts/DashboardLayout", () => ({ default: ({ children }) =>
{children}
, })); @@ -60,14 +65,72 @@ describe("CollegeTpoDirectoryPage", () => { expect(await screen.findByText("No verified TPOs yet")).toBeInTheDocument(); expect(screen.getByText(/does not currently have a verified TPO/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /invite your college faculty/i })).toBeInTheDocument(); }); - it("shows the API error state", async () => { - apiFetch.mockRejectedValueOnce(new Error("Verify your college email first.")); + it("shows the college-email verification state and profile CTA", async () => { + apiFetch.mockRejectedValueOnce({ + status: 403, + body: { code: "COLLEGE_EMAIL_UNVERIFIED" }, + message: "Verify your college email to view your college TPO directory.", + }); + + render(); + + expect(await screen.findByText("Verify your college email")).toBeInTheDocument(); + expect( + screen.getByText(/verify your college email from your Profile page/i) + ).toBeInTheDocument(); + + const profileLink = screen.getByRole("link", { name: /go to profile/i }); + expect(profileLink).toHaveAttribute("href", "/profile"); + expect(screen.queryByText("TPO directory unavailable")).not.toBeInTheDocument(); + }); + + it("shows the generic API error state for failures other than college-email verification", async () => { + apiFetch.mockRejectedValueOnce({ + status: 500, + body: { error: "Service unavailable." }, + message: "Service unavailable.", + }); render(); expect(await screen.findByText("TPO directory unavailable")).toBeInTheDocument(); - expect(screen.getByText("Verify your college email first.")).toBeInTheDocument(); + expect(screen.getByText("Service unavailable.")).toBeInTheDocument(); + expect(screen.queryByText("Verify your college email")).not.toBeInTheDocument(); + }); + + it("shows the disabled-directory state when the backend reports the feature is not live", async () => { + apiFetch.mockResolvedValueOnce({ + enabled: false, + message: "College dashboard is not live yet.", + }); + + render(); + + expect(await screen.findByText("College TPO directory is not live yet")).toBeInTheDocument(); + expect(screen.getByText("College dashboard is not live yet.")).toBeInTheDocument(); + expect(screen.queryByText("No verified TPOs yet")).not.toBeInTheDocument(); + }); + + it("uses the shared invite flow without changing directory state", async () => { + apiFetch.mockResolvedValueOnce({ + college: { id: "college-1", name: "Report University" }, + tpos: [], + }); + + render(); + + const inviteButton = await screen.findByRole("button", { + name: /invite your college faculty/i, + }); + await inviteButton.click(); + + expect(share).toHaveBeenCalledWith({ + title: "Join Code Club as a TPO", + text: expect.stringContaining("Report University"), + url: expect.stringContaining("/tpo/signup"), + }); }); }); From 592d7b4b75beea05eced2b7aa0bbcd9abb1c88b2 Mon Sep 17 00:00:00 2001 From: Shiva Shankara Vara Prasad Date: Fri, 25 Sep 2026 16:18:07 +0530 Subject: [PATCH 4/4] Polish College TPO directory accessibility and responsive states --- src/pages/CollegeTpoDirectoryPage.jsx | 201 ++++++++++++++++++-------- 1 file changed, 137 insertions(+), 64 deletions(-) diff --git a/src/pages/CollegeTpoDirectoryPage.jsx b/src/pages/CollegeTpoDirectoryPage.jsx index d9b34676..24d30f74 100644 --- a/src/pages/CollegeTpoDirectoryPage.jsx +++ b/src/pages/CollegeTpoDirectoryPage.jsx @@ -51,26 +51,44 @@ export default function CollegeTpoDirectoryPage() { return ( -
-
-

College support

-

Find your College TPOs

-

+

+
+

+ College support +

+

+ Find your College TPOs +

+

Connect with the verified Training & Placement Officers responsible for your institution.

-
+ {loading ? ( -
-
+
+ ) : verificationRequired ? ( -
+
-
-
-
+
) : data?.enabled === false ? ( -
+
-
-
-
+
) : error ? ( -
-

TPO directory unavailable

-

{error}

-
+
+

+ TPO directory unavailable +

+

{error}

+
) : data?.college && Array.isArray(data.tpos) ? ( <> -
-
-
+ {data.tpos.length === 0 ? ( -
-
+ ) : ( -
- {data.tpos.map((tpo) => ( -
-
-
-

{tpo.name}

-

{tpo.collegeName}

+
+

+ Verified College TPOs +

+
+ {data.tpos.map((tpo) => ( +
+
+
+

+ {tpo.name} +

+

+ {tpo.collegeName} +

+
+ +
- - -
- {tpo.isPrimary ?

Primary TPO

: null} - - -
- ))} -
+ {tpo.isPrimary ? ( +

Primary TPO

+ ) : null} + + + + ))} +
+ )} ) : ( -
-

TPO directory unavailable

-

+

+

+ TPO directory unavailable +

+

We received an unexpected response. Please try again later.

-
+ )} -
+ ); }