diff --git a/client/src/components/Map/markerCluster.ts b/client/src/components/Map/markerCluster.ts index b831b8b39..a80a69fd2 100644 --- a/client/src/components/Map/markerCluster.ts +++ b/client/src/components/Map/markerCluster.ts @@ -1,12 +1,8 @@ import L from 'leaflet' /** - * The cluster group every place map is drawn with. - * - * The planner and the public share page draw the same trip, so they have to - * cluster it the same way: a shared link that opens on three dozen overlapping - * pins while the planner shows four tidy bubbles is one trip told two different - * ways. Both maps build their group from this block and this factory (#2343). + * The cluster group the planner and the public share page both draw places with, + * so a shared link clusters a trip the way the planner does (#2343). */ export const CLUSTER_OPTIONS = { chunkedLoading: true, @@ -26,13 +22,7 @@ export interface ClusterLike { getChildCount: () => number } -/** - * A count bubble, sized by what it stands for. - * - * Markup rather than a component because Leaflet owns this element: - * `L.divIcon`'s html lands in the marker pane, outside React's tree — which is - * also why its styling lives in index.css (`.marker-cluster-custom`). - */ +/** A count bubble sized by its count, styled by `.marker-cluster-custom` in index.css. */ export function createClusterIcon(cluster: ClusterLike) { const count = cluster.getChildCount() const size = count < 10 ? 36 : count < 50 ? 42 : 48 diff --git a/client/src/pages/SharedTripPage.test.tsx b/client/src/pages/SharedTripPage.test.tsx index 30bf9db74..54a44c493 100644 --- a/client/src/pages/SharedTripPage.test.tsx +++ b/client/src/pages/SharedTripPage.test.tsx @@ -24,10 +24,7 @@ vi.mock('react-leaflet', () => ({ }), })); -// The markers now reach the map through a cluster group (#2343). The real wrapper -// asks React Leaflet's context for the map it belongs to, and the stubbed -// MapContainer above provides none — so it is stubbed for the same reason -// everything else here is. +// The real cluster group needs the map context the stubbed MapContainer lacks (#2343). vi.mock('react-leaflet-cluster', () => ({ default: ({ children }: { children?: React.ReactNode }) => (
{children}
diff --git a/client/src/pages/SharedTripPage.tsx b/client/src/pages/SharedTripPage.tsx index ba00b68ad..bacb99da0 100644 --- a/client/src/pages/SharedTripPage.tsx +++ b/client/src/pages/SharedTripPage.tsx @@ -19,9 +19,7 @@ import { createElement, useEffect, useRef } from 'react'; import { renderIconMarkup } from '../utils/iconMarkup'; import { MapContainer, Marker, Polyline, TileLayer, Tooltip, useMap } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-cluster'; -// leaflet.markercluster's own sheets travel with the planner's MapView, which this -// page never mounts: without them the bubbles keep the library's default styling -// and spiderfy's legs are invisible. +// MapView brings these sheets for the planner, and this page never mounts it. import 'leaflet.markercluster/dist/MarkerCluster.css'; import 'leaflet.markercluster/dist/MarkerCluster.Default.css'; import { CLUSTER_OPTIONS, createClusterIcon } from '../components/Map/markerCluster'; @@ -461,10 +459,7 @@ export default function SharedTripPage() { interactive={false} /> )} - {/* Clustered like the planner's map (#2343). A shared link draws the - whole trip at once on a 300px-tall map, where a stop a few blocks - from the next one is the same few pixels: unclustered, the pins - cover each other and the ones underneath cannot be tapped. */} + {/* Clustered like the planner's map, so nearby stops stay tappable (#2343). */} {mapPlaces.map((p: any) => ( diff --git a/client/tests/unit/pages/SharedTripPage.map.test.tsx b/client/tests/unit/pages/SharedTripPage.map.test.tsx index 83a213d14..9b7f9f238 100644 --- a/client/tests/unit/pages/SharedTripPage.map.test.tsx +++ b/client/tests/unit/pages/SharedTripPage.map.test.tsx @@ -1,17 +1,9 @@ /** - * The share page's map is the planner's map (#2343). - * - * A shared link is the only view a guest ever gets — no account, no zoom habits, - * often a phone — and it draws the whole trip at once on a 300px-tall canvas. - * Unclustered, three dozen pins pile onto each other there and the ones - * underneath can be neither read nor tapped, while the planner shows the same - * trip as a handful of tidy bubbles. - * - * The data hooks and the Leaflet components are stubbed: what is under test is - * the page's own wiring — that every stop reaches a cluster group built from the - * shared option block — not Leaflet's rendering, which jsdom cannot do anyway. + * The share page clusters its markers like the planner (#2343). The data hook and + * Leaflet are stubbed: under test is the page's wiring, not Leaflet's rendering. */ import React from 'react'; +import L from 'leaflet'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { render, screen, within } from '../../helpers/render'; import { CLUSTER_OPTIONS, createClusterIcon } from '../../../src/components/Map/markerCluster'; @@ -92,7 +84,7 @@ beforeEach(() => { }); describe('the map on a public share page', () => { - it('SHAREMAP-001: every stop is handed to a cluster group, not the raw map', () => { + it('FE-PAGE-SHARED-MAP-001: every stop is handed to a cluster group, not the raw map', () => { mocks.hook = hookState( payload([place(11, 'The Bund', 31.24, 121.49), place(12, 'Peace Hotel', 31.2397, 121.4903)]), ); @@ -104,7 +96,7 @@ describe('the map on a public share page', () => { expect(within(screen.getByTestId('map')).getAllByTestId('marker')).toHaveLength(2); }); - it('SHAREMAP-002: the group is built from the shared options, so both maps cluster alike', () => { + it('FE-PAGE-SHARED-MAP-002: the group is built from the shared options, so both maps cluster alike', () => { mocks.hook = hookState(payload([place(11, 'The Bund', 31.24, 121.49)])); render(); @@ -116,18 +108,18 @@ describe('the map on a public share page', () => { expect(typeof props.iconCreateFunction).toBe('function'); }); - it('SHAREMAP-004: the bubble shows the count, in the size bucket that count earns', () => { + it('FE-PAGE-SHARED-MAP-003: the bubble shows the count, in the size bucket that count earns', () => { const small = createClusterIcon({ getChildCount: () => 7 }); const medium = createClusterIcon({ getChildCount: () => 30 }); const large = createClusterIcon({ getChildCount: () => 60 }); expect(small.options.className).toBe('marker-cluster-wrapper'); expect(small.options.html).toContain('7'); - expect([small, medium, large].map((icon) => icon.options.iconSize?.x)).toEqual([36, 42, 48]); + expect([small, medium, large].map((icon) => icon.options.iconSize)).toEqual([L.point(36, 36), L.point(42, 42), L.point(48, 48)]); expect(small.options.html).toContain('width:36px;height:36px'); }); - it('SHAREMAP-003: a place without coordinates is left out of the map, as before', () => { + it('FE-PAGE-SHARED-MAP-004: a place without coordinates is left out of the map, as before', () => { mocks.hook = hookState( payload([place(11, 'The Bund', 31.24, 121.49), { id: 12, name: 'Geocoding failed' } as never]), );