From 1beaa2a2a816112744abef525324eabb97debb1f Mon Sep 17 00:00:00 2001 From: Mikhail Chandran Date: Thu, 29 Oct 2026 23:33:58 +0530 Subject: [PATCH] Show per-type place counts with map links on city pages --- src/app/city/[slug]/page.tsx | 4 ++ src/components/home/city-stats-card.test.tsx | 56 ++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/components/home/city-stats-card.test.tsx diff --git a/src/app/city/[slug]/page.tsx b/src/app/city/[slug]/page.tsx index 7f1a14b..ed4fab5 100644 --- a/src/app/city/[slug]/page.tsx +++ b/src/app/city/[slug]/page.tsx @@ -14,6 +14,7 @@ import { humanizeCity, PLACE_TYPE_LABELS } from "@/lib/types"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { PageContainer } from "@/components/layout/page-container"; +import { CityStatsCard } from "@/components/home/city-stats-card"; // Every city page is decided at build time from the dataset; a slug that is // not in it is a 404, never a server-rendered miss. @@ -68,6 +69,9 @@ export default async function CityPage({ params }: CityPageProps) { the crowdsourced StudyMap dataset. Open one on the map to see it in context.

+ {/* Per-type counts, each linking to /map filtered to this city and type. */} + + {page.places.length <= THIN_CITY_MAX_PLACES && ( diff --git a/src/components/home/city-stats-card.test.tsx b/src/components/home/city-stats-card.test.tsx new file mode 100644 index 0000000..45967c1 --- /dev/null +++ b/src/components/home/city-stats-card.test.tsx @@ -0,0 +1,56 @@ +import { cleanup, render, screen, within } from "@testing-library/react"; +import { afterEach, describe, expect, it } from "vitest"; + +import { CityStatsCard } from "@/components/home/city-stats-card"; +import type { Place } from "@/lib/types"; + +afterEach(cleanup); + +function place(overrides: Partial & Pick): Place { + return { + name: overrides.id, + city: "navi_mumbai", + lat: 19.03, + lng: 73.03, + gmaps_link: "https://maps.google.com/?q=19.03,73.03", + added_by: "test", + ...overrides, + }; +} + +const PLACES: Place[] = [ + place({ id: "navi-library-01", type: "library" }), + place({ id: "navi-library-02", type: "library" }), + place({ id: "navi-sat-01", type: "sat_centre" }), + place({ id: "mumbai-airport-01", type: "airport", city: "mumbai" }), +]; + +describe("CityStatsCard", () => { + it("titles the card with the city and counts only that city's places by type", () => { + render(); + + expect(screen.getByText("Navi Mumbai")).toBeTruthy(); + const library = screen.getByRole("link", { name: /Library/ }); + expect(within(library).getByText("2")).toBeTruthy(); + const sat = screen.getByRole("link", { name: /SAT centre/ }); + expect(within(sat).getByText("1")).toBeTruthy(); + expect(screen.queryByRole("link", { name: /Airport/ })).toBeNull(); + }); + + it("links each count to the map filtered to that city and type", () => { + render(); + + const href = screen.getByRole("link", { name: /Library/ }).getAttribute("href") ?? ""; + expect(href.startsWith("/map?")).toBe(true); + const params = new URLSearchParams(href.slice("/map?".length)); + expect(params.get("city")).toBe("navi_mumbai"); + expect(params.get("types")).toBe("library"); + }); + + it("says so when the city has no places", () => { + render(); + + expect(screen.getByText("No places recorded yet.")).toBeTruthy(); + expect(screen.queryAllByRole("link")).toHaveLength(0); + }); +});