mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
test(shared): type the cluster icon size check and tighten the comments
iconSize is a PointExpression, which has no x on its tuple form, so the client typecheck refused the size assertion; it now compares against L.point. The test ids follow the FE-PAGE-SHARED-MAP scheme in the order they run, and the comments around the cluster group say in a line what they said in a paragraph.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 }) => (
|
||||
<div data-testid="marker-cluster-group">{children}</div>
|
||||
|
||||
@@ -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). */}
|
||||
<MarkerClusterGroup {...CLUSTER_OPTIONS} iconCreateFunction={createClusterIcon}>
|
||||
{mapPlaces.map((p: any) => (
|
||||
<Marker key={p.id} position={[p.lat, p.lng]} icon={createMarkerIcon(p, dayOrderMap[p.id] ?? null)}>
|
||||
|
||||
@@ -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(<SharedTripPage />);
|
||||
|
||||
@@ -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('<span>7</span>');
|
||||
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]),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user