Files
TREK/client/tests/unit/studio/mapScope.test.ts
Maurice 6cbefd23e8 feat(studio): filter photos by entry, upload from Studio, and switch stops off
The content browser gets a filter over the journey's pictures: all of them,
the ones no entry holds, or one entry at a time, with the day and the place
under each name. An entry card in the Entries tab reaches its own pictures
the same way. Pictures can now come in without leaving Studio, into the
entry the filter names or into the gallery, from the panel or dropped
straight onto the page, and a photo element can fill its page or the whole
spread in one press.

Stops can be taken out of the arithmetic. A journal entry carries a
stats_excluded flag; an entry that has it is off the route, out of the
distance, out of the countries and not a step, while staying in the
journal. Studio lists every stop in the travel panel with a switch, the
entry editor carries the same switch on desktop and phone, and the entry
card says when a day is off the route.
2026-09-03 10:47:08 +02:00

154 lines
5.7 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import type { BookElement, JourneyStats } from '@trek/shared'
import { isStale, refreshPatch, routeFor } from '../../../src/components/Studio/travelRefresh'
/**
* A map of one trip, out of a journey made of several (#1973).
*
* A journey is a collection of trips, and printing two of them as one route
* draws a line from the last stop of the first to the first stop of the second.
* On a journey that went to Iceland in June and Portugal in September that leg
* is the longest line on the page and nobody travelled it.
*
* The scope is stored on the element rather than worked out at draw time,
* because the stops are frozen into it when it is placed. These cases pin the
* part that would otherwise rot quietly: bringing a map up to date has to keep
* the scope, or a map of Iceland turns back into a map of everywhere the first
* time somebody clicks refresh.
*/
const ICELAND = 11
const PORTUGAL = 22
const stats: JourneyStats = {
journeyId: 5,
distance: 4_000_000,
days: 21,
steps: 6,
photos: 40,
places: 12,
furthest: 3_000_000,
countries: [
{ code: 'IS', name: 'Iceland', places: 2, firstVisit: '2026-06-02' },
{ code: 'PT', name: 'Portugal', places: 2, firstVisit: '2026-09-04' },
],
points: [
{ lat: 64.14, lng: -21.94, label: 'Reykjavík', date: '2026-06-02', country: 'IS', tripId: ICELAND, photoId: null, entryId: null },
{ lat: 65.68, lng: -18.12, label: 'Akureyri', date: '2026-06-06', country: 'IS', tripId: ICELAND, photoId: null, entryId: null },
{ lat: 38.72, lng: -9.14, label: 'Lisboa', date: '2026-09-04', country: 'PT', tripId: PORTUGAL, photoId: null, entryId: null },
{ lat: 41.15, lng: -8.61, label: 'Porto', date: '2026-09-08', country: 'PT', tripId: PORTUGAL, photoId: null, entryId: null },
],
trips: [
{ id: ICELAND, title: 'Iceland', start: '2026-06-02', end: '2026-06-14', points: 2 },
{ id: PORTUGAL, title: 'Portugal', start: '2026-09-04', end: '2026-09-12', points: 2 },
],
excluded: [],
start: '2026-06-02',
end: '2026-09-12',
}
const map = (over: Record<string, unknown> = {}): BookElement => ({
id: 'mp1', kind: 'map', frame: { x: 10, y: 10, w: 180, h: 140 },
rotation: 0, opacity: 1, locked: false,
font: 'sans', color: '#1a1a1a', accent: '#c2410c', textScale: 1, weight: 400, stale: false,
style: 'minimal', source: 'vector', tileUrl: '', attribution: '', zoom: null, clip: 'rect',
showLand: true, showRoute: true, showPins: true, showLabels: false,
countries: ['IS', 'PT'],
points: stats.points.map(p => ({ lat: p.lat, lng: p.lng, label: p.label })),
path: [],
fitPadding: 0.18,
fitToCountries: true,
tripId: null,
...over,
} as unknown as BookElement)
describe('which stops a map is of', () => {
it('is the whole journey when no trip is named', () => {
expect(routeFor(stats, null)).toHaveLength(4)
})
it('is one trip when one is', () => {
const route = routeFor(stats, ICELAND)
expect(route.map(p => p.label)).toEqual(['Reykjavík', 'Akureyri'])
})
it('is empty for a trip with nothing on the route, rather than everything', () => {
expect(routeFor(stats, 999)).toEqual([])
})
})
describe('bringing a map up to date', () => {
it('keeps a journey map showing the journey', () => {
const patch = refreshPatch(map(), stats) as { points: unknown[]; countries: string[] }
expect(patch.points).toHaveLength(4)
expect(patch.countries).toEqual(['IS', 'PT'])
})
/*
* The case this test exists for: refresh used to read `stats.points` flat,
* so one click turned a map of Iceland back into a map of both trips.
*/
it('keeps a trip map showing that trip', () => {
const el = map({
tripId: ICELAND,
countries: ['IS'],
points: [{ lat: 64.14, lng: -21.94, label: 'Reykjavík' }],
})
const patch = refreshPatch(el, stats) as { points: { label: string }[]; countries: string[] }
expect(patch.points.map(p => p.label)).toEqual(['Reykjavík', 'Akureyri'])
expect(patch.countries).toEqual(['IS'])
})
it('notices a trip map is behind, against its own trip rather than the journey', () => {
const behind = map({
tripId: ICELAND,
points: [{ lat: 64.14, lng: -21.94, label: 'Reykjavík' }],
})
expect(isStale(behind, stats)).toBe(true)
const current = map({
tripId: ICELAND,
points: [
{ lat: 64.14, lng: -21.94, label: 'Reykjavík' },
{ lat: 65.68, lng: -18.12, label: 'Akureyri' },
],
})
expect(isStale(current, stats)).toBe(false)
})
it('does not call a journey map stale merely because a trip map would be', () => {
expect(isStale(map(), stats)).toBe(false)
})
/*
* A marker draws the entry's own first photograph, so adding one to an entry
* changes the map without moving a single stop. The shape test — same number
* of stops, same two ends — cannot see that, and until it did the only way to
* get the picture onto the page was to delete the map and place it again.
*/
it('notices a photograph added to a stop, though nothing moved', () => {
const el = map({
points: stats.points.map(p => ({ lat: p.lat, lng: p.lng, label: p.label, photoId: null })),
})
expect(isStale(el, stats)).toBe(false)
const withPhoto: JourneyStats = {
...stats,
points: stats.points.map((p, i) => (i === 1 ? { ...p, photoId: 42 } : p)),
}
expect(isStale(el, withPhoto)).toBe(true)
const patch = refreshPatch(el, withPhoto) as { points: { photoId: number | null }[] }
expect(patch.points[1].photoId).toBe(42)
})
it('notices one taken away again', () => {
const el = map({
points: stats.points.map((p, i) => ({
lat: p.lat, lng: p.lng, label: p.label, photoId: i === 0 ? 7 : null,
})),
})
expect(isStale(el, stats)).toBe(true)
})
})