File size: 7,480 Bytes
cd99321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import { describe, it, expect } from 'vitest'
import { parseTimeToMinutes, getSpanPhase, getTransportRouteEndpoints, getDisplayTimeForDay, getTransportForDay, getMergedItems } from './dayMerge'

describe('parseTimeToMinutes', () => {
  it('parses HH:MM string', () => {
    expect(parseTimeToMinutes('09:30')).toBe(570)
  })

  it('parses ISO datetime string', () => {
    expect(parseTimeToMinutes('2025-03-30T14:00:00')).toBe(840)
  })

  it('returns null for null/empty', () => {
    expect(parseTimeToMinutes(null)).toBeNull()
    expect(parseTimeToMinutes(undefined)).toBeNull()
  })
})

describe('getSpanPhase', () => {
  it('returns single when start === end', () => {
    expect(getSpanPhase({ day_id: 1, end_day_id: 1 }, 1)).toBe('single')
  })

  it('returns start for the departure day', () => {
    expect(getSpanPhase({ day_id: 1, end_day_id: 3 }, 1)).toBe('start')
  })

  it('returns end for the arrival day', () => {
    expect(getSpanPhase({ day_id: 1, end_day_id: 3 }, 3)).toBe('end')
  })

  it('returns middle for days in between', () => {
    expect(getSpanPhase({ day_id: 1, end_day_id: 3 }, 2)).toBe('middle')
  })
})

describe('getTransportRouteEndpoints', () => {
  const pickup = { role: 'from', lat: 48.1, lng: 11.5 }
  const dropoff = { role: 'to', lat: 52.5, lng: 13.4 }
  // A car rental spanning day 1 (pickup) through day 3 (drop-off).
  const rental = { day_id: 1, end_day_id: 3, endpoints: [pickup, dropoff] }

  it('routes to the pickup only on the start day of a multi-day rental', () => {
    expect(getTransportRouteEndpoints(rental, 1)).toEqual({ from: { lat: 48.1, lng: 11.5 }, to: null })
  })

  it('routes from the drop-off only on the end day', () => {
    expect(getTransportRouteEndpoints(rental, 3)).toEqual({ from: null, to: { lat: 52.5, lng: 13.4 } })
  })

  it('adds no waypoints on the days in between (regression for #1210)', () => {
    expect(getTransportRouteEndpoints(rental, 2)).toEqual({ from: null, to: null })
  })

  it('uses both endpoints for a single-day transport', () => {
    const sameDay = { day_id: 1, end_day_id: 1, endpoints: [pickup, dropoff] }
    expect(getTransportRouteEndpoints(sameDay, 1)).toEqual({
      from: { lat: 48.1, lng: 11.5 },
      to: { lat: 52.5, lng: 13.4 },
    })
  })

  it('returns nulls when the endpoints carry no coordinates', () => {
    const noCoords = { day_id: 1, end_day_id: 1, endpoints: [{ role: 'from' }, { role: 'to' }] }
    expect(getTransportRouteEndpoints(noCoords, 1)).toEqual({ from: null, to: null })
  })
})

describe('getDisplayTimeForDay', () => {
  const r = { day_id: 1, end_day_id: 3, reservation_time: '2025-01-01T09:00:00', reservation_end_time: '2025-01-03T14:00:00' }

  it('returns reservation_time on start day', () => {
    expect(getDisplayTimeForDay(r, 1)).toBe(r.reservation_time)
  })

  it('returns reservation_end_time on end day', () => {
    expect(getDisplayTimeForDay(r, 3)).toBe(r.reservation_end_time)
  })

  it('returns null for middle day', () => {
    expect(getDisplayTimeForDay(r, 2)).toBeNull()
  })
})

describe('getTransportForDay', () => {
  const days = [
    { id: 1, day_number: 1 },
    { id: 2, day_number: 2 },
    { id: 3, day_number: 3 },
  ]

  it('excludes hotel (rendered via accommodation path)', () => {
    const reservations = [{ id: 10, type: 'hotel', day_id: 1 }]
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [], days })).toHaveLength(0)
  })

  it('includes tour booking on the correct day', () => {
    const reservations = [{ id: 20, type: 'tour', day_id: 1 }]
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [], days })).toHaveLength(1)
    expect(getTransportForDay({ reservations, dayId: 2, dayAssignmentIds: [], days })).toHaveLength(0)
  })

  it('includes restaurant, event, and other bookings by day_id', () => {
    const reservations = [
      { id: 30, type: 'restaurant', day_id: 2 },
      { id: 31, type: 'event', day_id: 2 },
      { id: 32, type: 'other', day_id: 2 },
    ]
    expect(getTransportForDay({ reservations, dayId: 2, dayAssignmentIds: [], days })).toHaveLength(3)
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [], days })).toHaveLength(0)
  })

  it('includes single-day transport on the correct day', () => {
    const reservations = [{ id: 10, type: 'flight', day_id: 1, end_day_id: 1 }]
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [], days })).toHaveLength(1)
    expect(getTransportForDay({ reservations, dayId: 2, dayAssignmentIds: [], days })).toHaveLength(0)
  })

  it('includes multi-day transport on all spanned days', () => {
    const reservations = [{ id: 10, type: 'train', day_id: 1, end_day_id: 3 }]
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [], days })).toHaveLength(1)
    expect(getTransportForDay({ reservations, dayId: 2, dayAssignmentIds: [], days })).toHaveLength(1)
    expect(getTransportForDay({ reservations, dayId: 3, dayAssignmentIds: [], days })).toHaveLength(1)
  })

  it('excludes transport linked to an assignment on that day', () => {
    const reservations = [{ id: 10, type: 'bus', day_id: 1, end_day_id: 1, assignment_id: 42 }]
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [42], days })).toHaveLength(0)
    expect(getTransportForDay({ reservations, dayId: 1, dayAssignmentIds: [99], days })).toHaveLength(1)
  })
})

describe('getMergedItems', () => {
  it('merges places and notes sorted by sortKey', () => {
    const dayAssignments = [
      { id: 1, order_index: 0, place: { place_time: null } },
      { id: 2, order_index: 2, place: { place_time: null } },
    ]
    const dayNotes = [{ id: 10, sort_order: 1 }]
    const result = getMergedItems({ dayAssignments, dayNotes, dayTransports: [], dayId: 5 })
    expect(result.map(i => i.type)).toEqual(['place', 'note', 'place'])
    expect(result[0].data.id).toBe(1)
    expect(result[1].data.id).toBe(10)
    expect(result[2].data.id).toBe(2)
  })

  it('inserts transport by time when no per-day position is set', () => {
    const dayAssignments = [
      { id: 1, order_index: 0, place: { place_time: '08:00' } },
      { id: 2, order_index: 1, place: { place_time: '13:00' } },
    ]
    const dayTransports = [
      { id: 20, type: 'flight', day_id: 5, end_day_id: 5, reservation_time: '10:30', day_positions: null },
    ]
    const result = getMergedItems({ dayAssignments, dayNotes: [], dayTransports, dayId: 5 })
    const types = result.map(i => i.type)
    // transport (10:30) should be between place at 08:00 (idx 0) and place at 13:00 (idx 1)
    expect(types).toEqual(['place', 'transport', 'place'])
  })

  it('orders a timed transport chronologically regardless of a stale per-day position', () => {
    const dayAssignments = [
      { id: 1, order_index: 0, place: { place_time: '08:00' } },
      { id: 2, order_index: 1, place: { place_time: '13:00' } },
    ]
    // The train is at 10:30, so it sorts between the 08:00 and 13:00 places by time —
    // timed items are arranged chronologically even if an old manual position exists.
    const dayTransports = [
      { id: 20, type: 'train', day_id: 5, end_day_id: 5, reservation_time: '10:30', day_positions: { 5: 1.5 } },
    ]
    const result = getMergedItems({ dayAssignments, dayNotes: [], dayTransports, dayId: 5 })
    const types = result.map(i => i.type)
    expect(types).toEqual(['place', 'transport', 'place'])
  })
})