mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
fix(tours): offer the map comparison only where the plan map can draw it (ACT-02)
The compare switch in a tour's detail showed whenever the detail sat beside the plan map, but the map draws no comparison in road trip mode or with the trip overview open. Pressing it there turned the switch on while nothing appeared on the map. The plan map now tells the compare store whether it can draw a comparison. The detail offers the switch only then, and turning to the road trip or the overview, or leaving the map, ends a running comparison.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
// FE-ACT-SECTION-001 to FE-ACT-SECTION-007
|
||||
// FE-ACT-SECTION-001 to FE-ACT-SECTION-008
|
||||
// ACT-01 in the tour's detail, rendered without the translation provider so
|
||||
// every text reads as its key: the actual tracks with their own values marked
|
||||
// as such beside the plan's (ACT-02), a time only where the track measured
|
||||
// one, the actions only for someone who may change the tour, and the map
|
||||
// comparison only where a plan map is beside the detail.
|
||||
// comparison only where a plan map is beside the detail and can draw it.
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import type { TourActivity, TourListItem } from '@trek/shared';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
@@ -81,7 +81,7 @@ const show = (canEdit = true, item: TourListItem = tour, mapCompare = false) =>
|
||||
|
||||
beforeEach(() => {
|
||||
hook.state = state();
|
||||
useTourActivityCompareStore.getState().clear();
|
||||
useTourActivityCompareStore.setState({ comparison: null, mapAvailable: true });
|
||||
});
|
||||
|
||||
describe('TourActivitySection (ACT-01)', () => {
|
||||
@@ -178,4 +178,17 @@ describe('TourActivitySection (ACT-01)', () => {
|
||||
show(false);
|
||||
expect(screen.queryByRole('button', { name: /tours.activity.compare.show/ })).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-ACT-SECTION-008: in road trip mode or with the overview open the map draws other lines, so no switch is offered and a running comparison ends', () => {
|
||||
show(false, tour, true);
|
||||
fireEvent.click(screen.getByRole('button', { name: /tours.activity.compare.show/ }));
|
||||
expect(useTourActivityCompareStore.getState().comparison).not.toBeNull();
|
||||
|
||||
act(() => useTourActivityCompareStore.getState().setMapAvailable(false));
|
||||
expect(useTourActivityCompareStore.getState().comparison).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /tours.activity.compare.(show|hide)/ })).toBeNull();
|
||||
|
||||
act(() => useTourActivityCompareStore.getState().setMapAvailable(true));
|
||||
expect(screen.getByRole('button', { name: /tours.activity.compare.show/ })).toHaveAttribute('aria-pressed', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CalendarDays, Clock, CloudOff, FileDown, Footprints, Plus, RefreshCw, U
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import { useSettingsStore } from '../../../store/settingsStore';
|
||||
import { useTourActivityCompareStore } from '../../../store/tourActivityCompareStore';
|
||||
import type { AssignmentsMap, Day } from '../../../types';
|
||||
import { BOX } from '../../Planner/bookings/bookingParts';
|
||||
import { SoftPill } from '../../Planner/planParts';
|
||||
@@ -215,7 +216,7 @@ interface TourActivitySectionProps {
|
||||
plan: PlanValues;
|
||||
/** The plan's track colour, as the map and the table draw it. */
|
||||
planColor: string;
|
||||
/** Offers to compare a track on the plan map; only where one is beside the detail. */
|
||||
/** Offers to compare a track on the plan map; only where one is beside the detail and the map can draw it. */
|
||||
mapCompare?: boolean;
|
||||
onToursChanged?: (placeIds: number[]) => Promise<void> | void;
|
||||
}
|
||||
@@ -245,7 +246,9 @@ export default function TourActivitySection({
|
||||
const state = useTourActivities({ tripId, tour, executions, canEdit, onToursChanged });
|
||||
const { activities, unavailable } = state;
|
||||
const comparison = useActivityComparison(tour.place_id, activities);
|
||||
const side: PlanSide = { plan, planColor, comparison: mapCompare ? comparison : null };
|
||||
// The plan map says whether it can draw a comparison; where it cannot, no switch is offered.
|
||||
const mapAvailable = useTourActivityCompareStore((store) => store.mapAvailable);
|
||||
const side: PlanSide = { plan, planColor, comparison: mapCompare && mapAvailable ? comparison : null };
|
||||
if (activities === null || tour.activity_of != null || (activities.length === 0 && !canEdit && !unavailable)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// FE-ACT-COMPARE-MAP-001 to FE-ACT-COMPARE-MAP-005
|
||||
// FE-ACT-COMPARE-MAP-001 to FE-ACT-COMPARE-MAP-006
|
||||
// ACT-02 on the plan map: while a tour is compared with its actual track, the
|
||||
// track leaves the map as a tour of its own and comes back as one dotted line
|
||||
// of the route, the plan stays on the map, and the legend names both; off,
|
||||
// the map gets exactly what it would have had.
|
||||
// the map gets exactly what it would have had. The map also says whether it
|
||||
// can compare at all, and ends a comparison where it no longer can.
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { buildPlace } from '../../../../tests/helpers/factories';
|
||||
@@ -41,7 +42,7 @@ const input = {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
useTourActivityCompareStore.getState().clear();
|
||||
useTourActivityCompareStore.setState({ comparison: null, mapAvailable: false });
|
||||
});
|
||||
|
||||
describe('compareMapLines (ACT-02)', () => {
|
||||
@@ -114,4 +115,19 @@ describe('useActivityCompareMap (ACT-02)', () => {
|
||||
document.documentElement.style.removeProperty('--success');
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-ACT-COMPARE-MAP-006: tells the store whether it can compare, and ends a comparison where it no longer can or is gone', () => {
|
||||
const { rerender, unmount } = renderHook((props: typeof input) => useActivityCompareMap(props), { initialProps: input });
|
||||
expect(useTourActivityCompareStore.getState().mapAvailable).toBe(true);
|
||||
act(() => useTourActivityCompareStore.getState().show({ tourPlaceId: 7, trackPlaceId: 20 }));
|
||||
|
||||
rerender({ ...input, enabled: false });
|
||||
expect(useTourActivityCompareStore.getState()).toMatchObject({ mapAvailable: false, comparison: null });
|
||||
|
||||
rerender(input);
|
||||
expect(useTourActivityCompareStore.getState().mapAvailable).toBe(true);
|
||||
act(() => useTourActivityCompareStore.getState().show({ tourPlaceId: 7, trackPlaceId: 20 }));
|
||||
unmount();
|
||||
expect(useTourActivityCompareStore.getState()).toMatchObject({ mapAvailable: false, comparison: null });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTourActivityCompareStore } from '../../../store/tourActivityCompareStore';
|
||||
import type { Place } from '../../../types';
|
||||
import { parseRenderableRouteGeometry } from '../../../utils/routeGeometry';
|
||||
@@ -80,11 +80,22 @@ export function compareMapLines<P extends Place>(
|
||||
};
|
||||
}
|
||||
|
||||
/** ACT-02 on the plan map: `compareMapLines` with the comparison of the store and the theme's colours. */
|
||||
/**
|
||||
* ACT-02 on the plan map: `compareMapLines` with the comparison of the store
|
||||
* and the theme's colours. The map also tells the store whether it can
|
||||
* compare at all, so the detail offers the switch only where pressing it
|
||||
* draws something; turning to the road trip or the overview, or leaving the
|
||||
* map, ends a running comparison.
|
||||
*/
|
||||
export function useActivityCompareMap<P extends Place>(input: CompareMapInput<P>) {
|
||||
const comparison = useTourActivityCompareStore((state) => state.comparison);
|
||||
const paint = useMapPaint(ACTUAL_PAINT);
|
||||
const { enabled, places, mapPlaces, route, routeWalking, selectedPlaceId } = input;
|
||||
useEffect(() => {
|
||||
const { setMapAvailable } = useTourActivityCompareStore.getState();
|
||||
setMapAvailable(enabled);
|
||||
return () => setMapAvailable(false);
|
||||
}, [enabled]);
|
||||
return useMemo(
|
||||
() => compareMapLines({ enabled, places, mapPlaces, route, routeWalking, selectedPlaceId }, comparison, paint),
|
||||
[comparison, enabled, mapPlaces, paint, places, route, routeWalking, selectedPlaceId]
|
||||
|
||||
@@ -69,7 +69,7 @@ function renderStage(overrides: Partial<Record<keyof PlanMapStageProps, unknown>
|
||||
|
||||
beforeEach(() => {
|
||||
for (const key of Object.keys(captured)) delete captured[key]
|
||||
useTourActivityCompareStore.getState().clear()
|
||||
useTourActivityCompareStore.setState({ comparison: null, mapAvailable: false })
|
||||
})
|
||||
|
||||
describe('PlanMapStage', () => {
|
||||
@@ -222,10 +222,18 @@ describe('PlanMapStage', () => {
|
||||
|
||||
expect(recorded('map')).toMatchObject({ places: [plan], route: [[[47, 11], [47.01, 11]]], routeWalking: [true] })
|
||||
expect(screen.getByTestId('activity-compare-legend')).toHaveTextContent('Watch export')
|
||||
expect(useTourActivityCompareStore.getState().mapAvailable).toBe(true)
|
||||
|
||||
// The road trip and the trip overview draw other lines; nothing is compared there.
|
||||
// The road trip and the trip overview draw other lines; nothing is compared
|
||||
// there, the detail is told so and the comparison ends.
|
||||
rerender(<PlanMapStage {...stageProps({ ...compared, overviewActive: true })} />)
|
||||
expect(recorded('map')).toMatchObject({ route: ['overview'] })
|
||||
expect(screen.queryByTestId('activity-compare-legend')).toBeNull()
|
||||
expect(useTourActivityCompareStore.getState()).toMatchObject({ mapAvailable: false, comparison: null })
|
||||
|
||||
rerender(<PlanMapStage {...stageProps(compared)} />)
|
||||
expect(useTourActivityCompareStore.getState().mapAvailable).toBe(true)
|
||||
rerender(<PlanMapStage {...stageProps({ ...compared, roadtripActive: true })} />)
|
||||
expect(useTourActivityCompareStore.getState().mapAvailable).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,12 +15,23 @@ interface ActivityComparison {
|
||||
|
||||
interface TourActivityCompareState {
|
||||
comparison: ActivityComparison | null;
|
||||
/**
|
||||
* Whether the plan map can draw a comparison right now. The map says so
|
||||
* itself: in road trip mode, with the trip overview open or on the phone it
|
||||
* draws other lines, and the detail then offers no switch that would do
|
||||
* nothing.
|
||||
*/
|
||||
mapAvailable: boolean;
|
||||
show: (comparison: ActivityComparison | null) => void;
|
||||
clear: () => void;
|
||||
/** Set by the plan map; a map that can no longer compare ends the comparison too. */
|
||||
setMapAvailable: (available: boolean) => void;
|
||||
}
|
||||
|
||||
export const useTourActivityCompareStore = create<TourActivityCompareState>((set) => ({
|
||||
comparison: null,
|
||||
mapAvailable: false,
|
||||
show: (comparison) => set({ comparison }),
|
||||
clear: () => set({ comparison: null }),
|
||||
setMapAvailable: (available) => set(available ? { mapAvailable: true } : { mapAvailable: false, comparison: null }),
|
||||
}));
|
||||
|
||||
@@ -60,7 +60,7 @@ export const UNRESET_STORES: Record<string, string> = {
|
||||
useStudioStore: NOT_YET,
|
||||
useSystemNoticeStore: NOT_YET,
|
||||
useTourActivityCompareStore:
|
||||
'the actual track compared on the map; the tests that read it clear it in their own beforeEach',
|
||||
'the actual track compared on the map and whether the map can draw it; the tests that read it reset both in their own beforeEach',
|
||||
useTourFerrataCrossingStore: 'set by the tour map stage; the tests that read it reset it in their own beforeEach',
|
||||
useTourPlaceToolStore: 'reset in the tour tests themselves to initialTourPlaceToolState()',
|
||||
useTourPoiStore: 'the places found along a tour; the tests that read it clear it in their own beforeEach',
|
||||
|
||||
Reference in New Issue
Block a user