diff --git a/src/components/pins/exam-centre-countdown.test.tsx b/src/components/pins/exam-centre-countdown.test.tsx
new file mode 100644
index 0000000..9ad96e5
--- /dev/null
+++ b/src/components/pins/exam-centre-countdown.test.tsx
@@ -0,0 +1,88 @@
+import { cleanup, render, screen } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+import { ExamCentreCountdown } from "@/components/pins/exam-centre-countdown";
+import { PinPopup } from "@/components/pins/pin-popup";
+import type { Place } from "@/lib/types";
+
+vi.mock("sonner", () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
+
+afterEach(cleanup);
+
+const centre: Place = {
+ id: "12345",
+ name: "Example International School",
+ type: "sat_centre",
+ city: "mumbai",
+ lat: 19.0176,
+ lng: 72.8562,
+ gmaps_link: "https://maps.google.com/?q=19.0176,72.8562",
+ added_by: "someone",
+ exam: "SAT",
+ valid_till: "2027-03-15",
+};
+
+const now = new Date("2026-10-21T12:00:00");
+
+describe("ExamCentreCountdown", () => {
+ it("shows a calm pill while the date is far away", () => {
+ render();
+
+ const pill = screen.getByText(/^Valid until/);
+ expect(pill.textContent).toContain("2027");
+ expect(pill.className).toContain("text-success");
+ });
+
+ it("switches to a warning inside the 60 day window", () => {
+ render(
+ ,
+ );
+
+ const pill = screen.getByText(/^Expires soon/);
+ expect(pill.className).toContain("text-warning");
+ });
+
+ it("still counts as valid on the last day", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText(/^Expires soon/)).toBeTruthy();
+ });
+
+ it("shows Expired in the destructive colour once the date has passed", () => {
+ render(
+ ,
+ );
+
+ const pill = screen.getByText(/^Expired/);
+ expect(pill.className).toContain("text-destructive");
+ });
+
+ it("renders nothing when the place has no valid_till", () => {
+ const { valid_till, ...withoutDate } = centre;
+ void valid_till;
+ const { container } = render();
+
+ expect(container.innerHTML).toBe("");
+ });
+});
+
+describe("PinPopup exam centre validity", () => {
+ it("shows the validity pill and no duplicate reconfirm text for an exam centre", () => {
+ render();
+
+ expect(screen.getByText(/^(Valid until|Expires soon|Expired)/)).toBeTruthy();
+ expect(screen.getByText("SAT")).toBeTruthy();
+ expect(screen.queryByText(/reconfirm by/)).toBeNull();
+ });
+
+ it("shows no pill for a place that is not an exam centre", () => {
+ const library: Place = { ...centre, type: "library" };
+ delete library.exam;
+ delete library.valid_till;
+ render();
+
+ expect(screen.queryByText(/^(Valid until|Expires soon|Expired)/)).toBeNull();
+ });
+});
diff --git a/src/components/pins/exam-centre-countdown.tsx b/src/components/pins/exam-centre-countdown.tsx
index 46bd4ff..d24d43c 100644
--- a/src/components/pins/exam-centre-countdown.tsx
+++ b/src/components/pins/exam-centre-countdown.tsx
@@ -26,6 +26,8 @@ function daysUntil(iso: string, now: Date): number | null {
export interface ExamCentreCountdownProps {
place: Place;
className?: string;
+ /** The clock to compare against; tests pass a fixed date. Defaults to the current time. */
+ now?: Date;
}
/**
@@ -35,10 +37,14 @@ export interface ExamCentreCountdownProps {
* pattern as verified-badge.tsx: green while comfortably valid, amber inside
* the 60-day window, red once the date has passed.
*/
-export function ExamCentreCountdown({ place, className }: ExamCentreCountdownProps) {
+export function ExamCentreCountdown({
+ place,
+ className,
+ now = new Date(),
+}: ExamCentreCountdownProps) {
if (!place.valid_till) return null;
- const days = daysUntil(place.valid_till, new Date());
+ const days = daysUntil(place.valid_till, now);
const expired = days !== null && days < 0;
const expiringSoon = days !== null && days >= 0 && days <= EXPIRY_WARNING_DAYS;
diff --git a/src/components/pins/pin-popup.tsx b/src/components/pins/pin-popup.tsx
index ccfa36e..486ebf2 100644
--- a/src/components/pins/pin-popup.tsx
+++ b/src/components/pins/pin-popup.tsx
@@ -7,21 +7,13 @@ import type { Place } from "@/lib/types";
import { humanizeCity, PLACE_TYPE_LABELS } from "@/lib/types";
import { directionsUrl, PLACE_TYPE_COLORS } from "@/lib/map";
import { buildShareUrl } from "@/lib/share";
+import { ExamCentreCountdown } from "@/components/pins/exam-centre-countdown";
import { VerifiedBadge } from "@/components/pins/verified-badge";
interface PinPopupProps {
place: Place;
}
-function formatValidTill(iso: string): string {
- if (!/^\d{4}-\d{2}-\d{2}$/.test(iso)) return iso;
- return new Date(iso + "T00:00:00").toLocaleDateString("en-IN", {
- day: "numeric",
- month: "short",
- year: "numeric",
- });
-}
-
export function PinPopup({ place }: PinPopupProps) {
function copyLink() {
const url = buildShareUrl({
@@ -61,6 +53,7 @@ export function PinPopup({ place }: PinPopupProps) {
{humanizeCity(place.city)}
+
@@ -69,13 +62,10 @@ export function PinPopup({ place }: PinPopupProps) {
{place.address}
)}
- {/* Exam centre validity */}
+ {/* Exam centre: the validity pill in the header carries the reconfirm date */}
{place.exam && (
{place.exam} centre
- {place.valid_till && (
- <> · reconfirm by {formatValidTill(place.valid_till)}>
- )}
)}