diff --git a/src/components/map/filter-presets.test.tsx b/src/components/map/filter-presets.test.tsx new file mode 100644 index 0000000..1d52bd1 --- /dev/null +++ b/src/components/map/filter-presets.test.tsx @@ -0,0 +1,42 @@ +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { FilterPresets } from "@/components/map/filter-presets"; + +afterEach(cleanup); + +describe("FilterPresets", () => { + it("lists the preset buttons in a labelled group", () => { + render(); + + const group = screen.getByRole("group", { name: "Filter presets" }); + expect(group).toBeTruthy(); + for (const label of [ + "Libraries", + "SAT centres", + "Exam centres", + "Govt offices", + "Airports", + ]) { + expect(screen.getByRole("button", { name: label })).toBeTruthy(); + } + }); + + it("applies one type for a single-type preset", () => { + const onApply = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: "Airports" })); + + expect(onApply).toHaveBeenCalledWith(["airport"]); + }); + + it("applies both exam types for the Exam centres preset", () => { + const onApply = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: "Exam centres" })); + + expect(onApply).toHaveBeenCalledWith(["sat_centre", "foreign_lang_exam_centre"]); + }); +}); diff --git a/src/components/map/map-panel.tsx b/src/components/map/map-panel.tsx index 1ca91e2..f9d5f50 100644 --- a/src/components/map/map-panel.tsx +++ b/src/components/map/map-panel.tsx @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import { CategoryChips } from "@/components/map/category-chips"; +import { FilterPresets } from "@/components/map/filter-presets"; import { CityFilter } from "@/components/map/city-filter"; import { NearMeButton } from "@/components/map/near-me-button"; import { MyPlacesPanel } from "@/components/map/my-places-panel"; @@ -124,6 +125,8 @@ export function MapPanel({ scroll={scrollChips} /> + onFiltersChange({ ...filters, types })} /> + { expect(screen.queryByText("City Library")).toBeNull(); }); }); + +describe("PlacesMap filter presets", () => { + it("narrows the results to the preset's type and sets it in the URL", () => { + render(); + expect(screen.getByText("City Library")).toBeTruthy(); + + fireEvent.click(screen.getByRole("button", { name: "Airports" })); + + expect(screen.getByText("City Airport")).toBeTruthy(); + expect(screen.queryByText("City Library")).toBeNull(); + expect(screen.queryByText("Thane Library")).toBeNull(); + advance(500); + expect(params().get("types")).toBe("airport"); + }); +}); diff --git a/src/components/pins/pin-popup.test.tsx b/src/components/pins/pin-popup.test.tsx new file mode 100644 index 0000000..ea29787 --- /dev/null +++ b/src/components/pins/pin-popup.test.tsx @@ -0,0 +1,56 @@ +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +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 place: Place = { + id: "mum-library-07", + name: "City Library", + type: "library", + city: "mumbai", + lat: 19.0176, + lng: 72.8562, + gmaps_link: "https://maps.google.com/?q=19.0176,72.8562", + added_by: "someone", +}; + +describe("PinPopup directions", () => { + it("opens the maps-app picker from the directions button", () => { + render(); + expect(screen.queryByText("Get directions")).toBeNull(); + + fireEvent.click(screen.getByRole("button", { name: "Directions to City Library" })); + + expect(screen.getByText("Get directions")).toBeTruthy(); + const links = screen.getAllByRole("link").map((a) => a.getAttribute("href") ?? ""); + expect(links.some((href) => href.startsWith("https://maps.apple.com/"))).toBe(true); + expect(links.some((href) => href.startsWith("geo:19.0176,72.8562"))).toBe(true); + }); + + it("keeps the direct Google Maps link in the popup", () => { + render(); + + const google = screen.getByRole("link", { name: /Google Maps/ }); + expect(google.getAttribute("href")).toBe(place.gmaps_link); + }); +}); + +describe("PinPopup report a problem", () => { + it("opens the report dialog for this place", () => { + render(); + expect(screen.queryByText("Report an issue")).toBeNull(); + + fireEvent.click(screen.getByRole("button", { name: "Report a problem" })); + + expect(screen.getByText("Report an issue")).toBeTruthy(); + expect( + screen.getByText(/City Library - opens a pre-filled GitHub issue/), + ).toBeTruthy(); + expect(screen.getByRole("button", { name: "Open GitHub issue" })).toBeTruthy(); + }); +}); diff --git a/src/components/pins/pin-popup.tsx b/src/components/pins/pin-popup.tsx index 486ebf2..382078f 100644 --- a/src/components/pins/pin-popup.tsx +++ b/src/components/pins/pin-popup.tsx @@ -1,5 +1,6 @@ "use client"; +import * as React from "react"; import { ExternalLink, Link2, Navigation } from "lucide-react"; import { toast } from "sonner"; @@ -7,6 +8,8 @@ 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 { DirectionsModal } from "@/components/map/directions-modal"; +import { ReportIssueDialog } from "@/components/map/report-issue-dialog"; import { ExamCentreCountdown } from "@/components/pins/exam-centre-countdown"; import { VerifiedBadge } from "@/components/pins/verified-badge"; @@ -15,6 +18,9 @@ interface PinPopupProps { } export function PinPopup({ place }: PinPopupProps) { + const [directionsOpen, setDirectionsOpen] = React.useState(false); + const [reportOpen, setReportOpen] = React.useState(false); + function copyLink() { const url = buildShareUrl({ types: [], @@ -80,15 +86,15 @@ export function PinPopup({ place }: PinPopupProps) { Google Maps - setDirectionsOpen(true)} aria-label={`Directions to ${place.name}`} + aria-haspopup="dialog" className="inline-flex items-center justify-center rounded-md border border-input bg-background px-2.5 py-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground active:bg-muted/80" > - + {/* Footer badge */} - + Added by{" "} {place.added_by} + setReportOpen(true)} + aria-haspopup="dialog" + className="text-[11px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" + > + Report a problem + + + + ); }
Added by{" "} {place.added_by}