Files
kapteins-daagbok/client/src/utils/logEntryPayload.test.ts
T

214 lines
6.0 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildLogEntryPayload,
hasUnsavedEventDraft,
isLogEventDraftEmpty,
localDateString,
normalizeLogEvent,
splitTimeHHMM,
readLogEntryTidesMap,
type LogEventPayload
} from './logEntryPayload.js'
const emptyDraft = (): LogEventPayload =>
normalizeLogEvent({ time: '12:34' })
const filledDraft = (): LogEventPayload =>
normalizeLogEvent({ time: '12:34', remarks: 'Wind dreht' })
describe('localDateString', () => {
it('uses local calendar date, not UTC', () => {
const date = new Date(2026, 5, 4, 1, 30, 0)
expect(localDateString(date)).toBe('2026-06-04')
expect(date.toISOString().substring(0, 10)).toBe('2026-06-03')
})
})
describe('logEntryPayload event drafts', () => {
it('treats time-only draft as empty', () => {
expect(isLogEventDraftEmpty(emptyDraft())).toBe(true)
})
it('detects draft with content', () => {
expect(isLogEventDraftEmpty(filledDraft())).toBe(false)
})
it('does not flag empty open form as unsaved', () => {
expect(hasUnsavedEventDraft(emptyDraft(), null, [])).toBe(false)
})
it('flags new event draft with content as unsaved', () => {
expect(hasUnsavedEventDraft(filledDraft(), null, [])).toBe(true)
})
it('flags edited event when values differ', () => {
const events = [emptyDraft()]
const edited = filledDraft()
expect(hasUnsavedEventDraft(edited, 0, events)).toBe(true)
})
it('ignores edit mode when values match', () => {
const events = [filledDraft()]
expect(hasUnsavedEventDraft(filledDraft(), 0, events)).toBe(false)
})
})
describe('buildLogEntryPayload greywater', () => {
const base = {
date: '2026-05-31',
dayOfTravel: '1',
departure: 'Kiel',
destination: 'Laboe',
freshwater: { morning: 0, refilled: 0, evening: 0, consumption: 0 },
fuel: { morning: 0, refilled: 0, evening: 0, consumption: 0 },
events: [] as LogEventPayload[]
}
it('includes greywater when level > 0', () => {
const payload = buildLogEntryPayload({ ...base, greywater: { level: 45 } })
expect(payload.greywater).toEqual({ level: 45 })
})
it('omits greywater when level is 0', () => {
const payload = buildLogEntryPayload({ ...base, greywater: { level: 0 } })
expect(payload.greywater).toBeUndefined()
})
})
describe('buildLogEntryPayload tides map', () => {
const base = {
date: '2026-06-11',
dayOfTravel: '1',
departure: 'Norddeich',
destination: 'Juist',
freshwater: { morning: 0, refilled: 0, evening: 0, consumption: 0 },
fuel: { morning: 0, refilled: 0, evening: 0, consumption: 0 },
events: [] as LogEventPayload[]
}
it('persists multiple tide roles (departure and destination)', () => {
const payload = buildLogEntryPayload({
...base,
tides: {
departure: { highWater: '18:34', lowWater: '12:05' },
destination: { highWater: '19:00', lowWater: '12:30' }
}
})
expect(payload.tides).toEqual({
departure: { highWater: '18:34', lowWater: '12:05' },
destination: { highWater: '19:00', lowWater: '12:30' }
})
})
it('persists tide location metadata', () => {
const payload = buildLogEntryPayload({
...base,
tides: {
gps: {
highWater: '06:00',
lowWater: '00:04',
locationSource: 'gps',
lat: '53.624526',
lng: '7.155263'
}
}
})
expect(payload.tides).toEqual({
gps: {
highWater: '06:00',
lowWater: '00:04',
locationSource: 'gps',
lat: '53.624526',
lng: '7.155263'
}
})
})
})
describe('readLogEntryTidesMap backward compatibility', () => {
it('reads old flat schema as departure role', () => {
const oldData = {
tides: {
highWater: '12:30',
lowWater: '06:15',
locationSource: 'departure',
placeName: 'Kiel'
}
}
const map = readLogEntryTidesMap(oldData)
expect(map.departure).toEqual({
highWater: '12:30',
lowWater: '06:15',
locationSource: 'departure',
placeName: 'Kiel'
})
expect(map.gps).toBeUndefined()
expect(map.destination).toBeUndefined()
})
it('reads old flat schema with gps locationSource as gps role', () => {
const oldData = {
tides: {
highWater: '12:30',
lowWater: '06:15',
locationSource: 'gps',
lat: '54.3',
lng: '10.1'
}
}
const map = readLogEntryTidesMap(oldData)
expect(map.gps).toEqual({
highWater: '12:30',
lowWater: '06:15',
locationSource: 'gps',
lat: '54.3',
lng: '10.1'
})
expect(map.departure).toBeUndefined()
expect(map.destination).toBeUndefined()
})
it('reads new nested schema correctly', () => {
const newData = {
tides: {
departure: { highWater: '12:00', lowWater: '06:00', placeName: 'Kiel' },
gps: { highWater: '13:00', lowWater: '07:00', lat: '54.3' }
}
}
const map = readLogEntryTidesMap(newData)
expect(map.departure).toEqual({
highWater: '12:00',
lowWater: '06:00',
placeName: 'Kiel'
})
expect(map.gps).toEqual({
highWater: '13:00',
lowWater: '07:00',
lat: '54.3'
})
expect(map.destination).toBeUndefined()
})
})
describe('splitTimeHHMM', () => {
it('splits valid time HH:MM correctly', () => {
const result = splitTimeHHMM('15:45')
expect(result).toEqual({ hours: '15', minutes: '45' })
})
it('uses fallback value when time is empty', () => {
const result = splitTimeHHMM('', '00:00')
expect(result).toEqual({ hours: '00', minutes: '00' })
})
it('falls back to current local time when empty and no fallback is specified', () => {
const result = splitTimeHHMM('')
const hours = parseInt(result.hours, 10)
const minutes = parseInt(result.minutes, 10)
expect(hours).toBeGreaterThanOrEqual(0)
expect(hours).toBeLessThanOrEqual(23)
expect(minutes).toBeGreaterThanOrEqual(0)
expect(minutes).toBeLessThanOrEqual(59)
})
})