214 lines
6.0 KiB
TypeScript
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)
|
|
})
|
|
})
|