fix(client): compute "today" from the local calendar, not UTC

This commit is contained in:
jubnl
2026-08-25 00:44:42 +02:00
parent 67b71a52d1
commit efcdbd94fb
20 changed files with 171 additions and 115 deletions
@@ -1,4 +1,5 @@
import { useState } from 'react'
import { localIsoDate } from '../../utils/localDate'
import { ArrowLeft, ChevronRight, Calendar } from 'lucide-react'
import { useTranslation } from '../../i18n'
@@ -81,7 +82,7 @@ export function DatePicker({ value, onChange, tripDates }: {
const dateStr = `${viewMonth.year}-${pad(viewMonth.month + 1)}-${pad(day)}`
const isSelected = dateStr === value
const isTrip = tripDates?.has(dateStr)
const isToday = dateStr === new Date().toISOString().split('T')[0]
const isToday = dateStr === localIsoDate()
return (
<button
@@ -2,6 +2,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { http, HttpResponse, delay } from 'msw'
import { localIsoDate } from '../../utils/localDate'
import userEvent from '@testing-library/user-event'
import { render, screen, waitFor, fireEvent } from '../../../tests/helpers/render'
import { server } from '../../../tests/helpers/msw/server'
@@ -516,7 +517,7 @@ describe('EntryEditor', () => {
entry_date: '', photos: undefined as unknown as JourneyPhoto[],
}))
const today = new Date().toISOString().split('T')[0]
const today = localIsoDate() // local, matching the editor's default — not the UTC date
const label = new Date(today + 'T00:00:00').toLocaleDateString(undefined, {
month: 'short', day: 'numeric', year: 'numeric',
})
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState, useRef } from 'react'
import { localIsoDate } from '../../utils/localDate'
import { X, Plus, Image, Minus, Check, MapPin, Locate, Camera } from 'lucide-react'
import { normalizeImageFiles } from '../../utils/convertHeic'
import { type ResilientResult, type UploadProgress } from '../../utils/uploadQueue'
@@ -33,7 +34,7 @@ export function EntryEditor({ entry, journeyId, tripDates, galleryPhotos, trips,
const toast = useToast()
const [title, setTitle] = useState(entry.title || '')
const [story, setStory] = useState(entry.story || '')
const [entryDate, setEntryDate] = useState(entry.entry_date || new Date().toISOString().split('T')[0])
const [entryDate, setEntryDate] = useState(entry.entry_date || localIsoDate())
const [entryTime, setEntryTime] = useState(entry.entry_time?.slice(0, 5) || '')
const [locationName, setLocationName] = useState(entry.location_name || '')
const [locationLat, setLocationLat] = useState<number | null>(entry.location_lat ?? null)
@@ -78,7 +79,7 @@ export function EntryEditor({ entry, journeyId, tripDates, galleryPhotos, trips,
const isDirty = (
title !== (entry.title || '') ||
story !== (entry.story || '') ||
entryDate !== (entry.entry_date || new Date().toISOString().split('T')[0]) ||
entryDate !== (entry.entry_date || localIsoDate()) ||
entryTime !== (entry.entry_time?.slice(0, 5) || '') ||
locationName !== (entry.location_name || '') ||
(locationLat ?? null) !== (entry.location_lat ?? null) ||
@@ -1,4 +1,5 @@
import { useState, useEffect, useRef, useMemo } from 'react'
import { localIsoDate } from '../../utils/localDate'
import { useParams } from 'react-router'
import apiClient from '../../api/client'
import { useTripStore } from '../../store/tripStore'
@@ -480,7 +481,7 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
onChange={tm => {
const [d] = (form.reservation_time || '').split('T')
const selectedDay = days.find(dy => dy.id === selectedDayId)
const date = d || selectedDay?.date || new Date().toISOString().split('T')[0]
const date = d || selectedDay?.date || localIsoDate()
set('reservation_time', tm ? `${date}T${tm}` : date)
}}
/>
@@ -1,4 +1,5 @@
import userEvent from '@testing-library/user-event';
import { localIsoDate } from '../../utils/localDate';
import { act, fireEvent, render, screen } from '../../../tests/helpers/render';
import { useSettingsStore } from '../../store/settingsStore';
import { CustomDatePicker, CustomDateTimePicker } from './CustomDateTimePicker';
@@ -722,7 +723,7 @@ describe('CustomDateTimePicker branches', () => {
const steppers = screen.getAllByRole('button').filter((b) => b.textContent?.trim() === '');
fireEvent.click(steppers[steppers.length - 4]);
const today = new Date().toISOString().split('T')[0];
const today = localIsoDate(); // the picker falls back to the LOCAL date, not the UTC one
expect(onChange).toHaveBeenCalledWith(`${today}T01:00`);
});
@@ -1,4 +1,5 @@
import { Calendar, ChevronLeft, ChevronRight, Keyboard } from 'lucide-react';
import { localIsoDate } from '../../utils/localDate';
import React, { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from '../../i18n';
@@ -688,7 +689,7 @@ export function CustomDateTimePicker({ value, onChange, placeholder, style = {}
onChange(d ? `${d}T${timePart || '12:00'}` : '');
};
const handleTimeChange = (t: string) => {
const d = datePart || new Date().toISOString().split('T')[0];
const d = datePart || localIsoDate();
onChange(t ? `${d}T${t}` : d);
};
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { localIsoDate } from '../../../utils/localDate'
import { Camera, Plus, Image, Images, X, MapPin, Locate, Trash2, CheckCircle2, MinusCircle } from 'lucide-react'
import MSheet from '../../components/MSheet'
import MIconBtn from '../../components/MIconBtn'
@@ -57,7 +58,7 @@ export default function MJourneyEntrySheet({
const [title, setTitle] = useState(entry.title || '')
const [story, setStory] = useState(entry.story || '')
const [entryDate, setEntryDate] = useState(entry.entry_date || new Date().toISOString().split('T')[0])
const [entryDate, setEntryDate] = useState(entry.entry_date || localIsoDate())
const [entryTime, setEntryTime] = useState(entry.entry_time?.slice(0, 5) || '')
const [locationName, setLocationName] = useState(entry.location_name || '')
const [locationLat, setLocationLat] = useState<number | null>(entry.location_lat ?? null)
@@ -177,7 +178,7 @@ export default function MJourneyEntrySheet({
const isDirty =
title !== (entry.title || '') ||
story !== (entry.story || '') ||
entryDate !== (entry.entry_date || new Date().toISOString().split('T')[0]) ||
entryDate !== (entry.entry_date || localIsoDate()) ||
entryTime !== (entry.entry_time?.slice(0, 5) || '') ||
locationName !== (entry.location_name || '') ||
mood !== (entry.mood || '') ||
+4 -3
View File
@@ -1,4 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { localIsoDate } from '../utils/localDate';
import { render, screen, waitFor, within } from '../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -535,9 +536,9 @@ describe('DashboardPage', () => {
describe('FE-PAGE-DASH-023: SpotlightCard shows progress bar for ongoing trip', () => {
it('renders progress bar and live badge when trip is currently ongoing', async () => {
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
const nextWeek = new Date(Date.now() + 7 * 86400000).toISOString().split('T')[0];
const today = localIsoDate();
const yesterday = localIsoDate(new Date(Date.now() - 86400000));
const nextWeek = localIsoDate(new Date(Date.now() + 7 * 86400000));
const ongoingTrip = buildTrip({
title: 'Current Voyage',
+2 -1
View File
@@ -1,4 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { localIsoDate } from '../utils/localDate';
import { render, screen, waitFor, cleanup } from '../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
@@ -1317,7 +1318,7 @@ describe('JourneyDetailPage', () => {
http.post('/api/journeys/1/entries', () => {
return HttpResponse.json({
id: 99, journey_id: 1, author_id: 1, type: 'entry',
entry_date: new Date().toISOString().split('T')[0],
entry_date: localIsoDate(),
title: 'Test Entry', story: null, location_name: null,
location_lat: null, location_lng: null, mood: null, weather: null,
tags: [], pros_cons: null, visibility: 'private', sort_order: 0,
+3 -2
View File
@@ -1,4 +1,5 @@
import PageShell from '../components/Layout/PageShell'
import { localIsoDate } from '../utils/localDate'
import { useTranslation, TransHtml } from '../i18n'
import {
Plus, Search, Sparkles, Calendar, MapPin,
@@ -271,9 +272,9 @@ function JourneyPageDesktop() {
<div className="flex flex-col gap-2 max-h-[320px] overflow-y-auto">
{availableTrips.map(trip => {
const selected = selectedTripIds.has(trip.id)
const status = trip.end_date && trip.end_date < new Date().toISOString().split('T')[0]
const status = trip.end_date && trip.end_date < localIsoDate()
? 'completed'
: trip.start_date && trip.start_date <= new Date().toISOString().split('T')[0]
: trip.start_date && trip.start_date <= localIsoDate()
? 'active'
: 'upcoming'
const statusColors: Record<string, string> = {
+2 -1
View File
@@ -1,5 +1,6 @@
// FE-JRN-LISTWIRE-001 to FE-JRN-LISTWIRE-014
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { localIsoDate } from '../utils/localDate';
import { render, screen, fireEvent } from '../../tests/helpers/render';
import type { Journey } from '../store/journeyStore';
import JourneyPage from './JourneyPage';
@@ -182,7 +183,7 @@ describe('JourneyPage wiring', () => {
});
it('FE-JRN-LISTWIRE-014: modal trips render their status and toggle their selection', () => {
const today = new Date().toISOString().split('T')[0];
const today = localIsoDate(); // local — the status chips classify against the wall clock
const availableTrips = [
{ id: 1, title: 'Past trip', start_date: '2020-01-01', end_date: '2020-01-05', place_count: 3, cover_image: '/uploads/t.jpg' },
{ id: 2, title: 'Running trip', start_date: today, end_date: '2099-01-01', place_count: 1 },
@@ -1,97 +1,51 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { daysUntil, getTripStatus, sortTrips, MS_PER_DAY, type DashboardTrip } from './dashboardModel';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { daysUntil, getTripStatus, sortTrips, localIsoToday } from './dashboardModel'
// FE-HOOK-DASHMODEL-001 onwards
/**
* The dashboard classifies trips against the user's WALL CLOCK — `daysUntil`
* always did (it parses dates as local midnight). But the "ongoing" check and
* the sort ranking derived "today" from `toISOString()`, which is UTC — so in
* any non-UTC timezone, between local midnight and the UTC rollover, a trip
* that ended yesterday still ranked as "running" and a trip starting today
* wasn't "ongoing". These tests pin the clock inside that window (00:30 local).
* On a UTC machine local == UTC and the old bug is unobservable, so the
* regression cases below are vacuous there — they bite on any offset TZ.
*/
describe('dashboardModel — "today" is the user\'s local date', () => {
beforeEach(() => {
vi.useFakeTimers()
// Local 2026-08-25 00:30 — for any TZ ahead of UTC, the UTC date is still 08-24.
vi.setSystemTime(new Date(2026, 7, 25, 0, 30))
})
afterEach(() => {
vi.useRealTimers()
})
const trip = (over: Partial<DashboardTrip>): DashboardTrip =>
({ id: 1, title: 't', ...over }) as unknown as DashboardTrip;
it('localIsoToday() is the local calendar date, not the UTC one', () => {
expect(localIsoToday()).toBe('2026-08-25')
})
const offset = (days: number): string => {
const d = new Date('2026-07-15T12:00:00Z');
d.setUTCDate(d.getUTCDate() + days);
return d.toISOString().split('T')[0];
};
it('daysUntil counts from local midnight', () => {
expect(daysUntil('2026-08-25')).toBe(0)
expect(daysUntil('2026-08-26')).toBe(1)
expect(daysUntil('2026-08-24')).toBe(-1)
expect(daysUntil(null)).toBeNull()
})
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-07-15T12:00:00Z'));
});
it('a trip spanning the local today is ongoing', () => {
expect(getTripStatus({ start_date: '2026-08-25', end_date: '2026-08-26' } as never)).toBe('ongoing')
})
afterEach(() => {
vi.useRealTimers();
});
it('a trip starting on the local today (no end) is "today"', () => {
expect(getTripStatus({ start_date: '2026-08-25', end_date: null } as never)).toBe('today')
})
describe('daysUntil', () => {
it('FE-HOOK-DASHMODEL-001: has no answer without a date', () => {
expect(daysUntil(null)).toBeNull();
expect(daysUntil(undefined)).toBeNull();
});
it('a trip that ended yesterday (local) is past — and sorts after one starting today', () => {
expect(getTripStatus({ start_date: '2026-08-20', end_date: '2026-08-24' } as never)).toBe('past')
it('FE-HOOK-DASHMODEL-002: counts forwards and backwards from today', () => {
expect(daysUntil(offset(0))).toBe(0);
expect(daysUntil(offset(5))).toBe(5);
expect(daysUntil(offset(-3))).toBe(-3);
});
});
describe('getTripStatus', () => {
it('FE-HOOK-DASHMODEL-003: a date range around today is ongoing', () => {
expect(getTripStatus(trip({ start_date: offset(-2), end_date: offset(2) }))).toBe('ongoing');
});
it('FE-HOOK-DASHMODEL-004: a trip without a start date has no status', () => {
expect(getTripStatus(trip({ start_date: null, end_date: null }))).toBeNull();
});
it.each([
[0, 'today'],
[1, 'tomorrow'],
[9, 'future'],
[-9, 'past'],
] as Array<[number, string]>)('FE-HOOK-DASHMODEL-005: a start in %s days is %s', (days, status) => {
// No end date, so the "ongoing" rule cannot swallow the start-relative states.
expect(getTripStatus(trip({ start_date: offset(days), end_date: null }))).toBe(status);
});
it('FE-HOOK-DASHMODEL-010: a finished date range is past', () => {
expect(getTripStatus(trip({ start_date: offset(-9), end_date: offset(-8) }))).toBe('past');
});
});
describe('sortTrips', () => {
it('FE-HOOK-DASHMODEL-006: running first, then upcoming ascending, then finished descending', () => {
const sorted = sortTrips([
trip({ id: 1, start_date: offset(-40), end_date: offset(-30) }),
trip({ id: 2, start_date: offset(20), end_date: offset(25) }),
trip({ id: 3, start_date: offset(-1), end_date: offset(3) }),
trip({ id: 4, start_date: offset(5), end_date: offset(8) }),
trip({ id: 5, start_date: offset(-10), end_date: offset(-8) }),
]);
expect(sorted.map(t => t.id)).toEqual([3, 4, 2, 5, 1]);
});
it('FE-HOOK-DASHMODEL-007: leaves the input array untouched', () => {
const input = [trip({ id: 1, start_date: offset(10) }), trip({ id: 2, start_date: offset(1) })];
const sorted = sortTrips(input);
expect(input.map(t => t.id)).toEqual([1, 2]);
expect(sorted.map(t => t.id)).toEqual([2, 1]);
});
it('FE-HOOK-DASHMODEL-008: dateless trips rank last', () => {
const sorted = sortTrips([
trip({ id: 1, start_date: null, end_date: null }),
trip({ id: 2, start_date: offset(4), end_date: offset(6) }),
]);
expect(sorted.map(t => t.id)).toEqual([2, 1]);
});
});
describe('MS_PER_DAY', () => {
it('FE-HOOK-DASHMODEL-009: is one day in milliseconds', () => {
expect(MS_PER_DAY).toBe(24 * 60 * 60 * 1000);
});
});
const endedYesterday = { id: 1, start_date: '2026-08-20', end_date: '2026-08-24' }
const startsToday = { id: 2, start_date: '2026-08-25', end_date: '2026-08-26' }
const sorted = sortTrips([endedYesterday, startsToday] as never[])
expect(sorted.map((t: { id: number }) => t.id)).toEqual([2, 1])
})
})
+12 -2
View File
@@ -6,6 +6,7 @@
*/
import type { Trip } from '../../types'
import { localIsoDate } from '../../utils/localDate'
// The dashboard works with the canonical Trip shape returned by the list/get
// endpoints (it already carries the computed day_count/place_count/is_owner/
@@ -29,6 +30,15 @@ export interface UpcomingReservation {
export const MS_PER_DAY = 86400000
/**
* Today as a local-calendar 'YYYY-MM-DD' — see utils/localDate for why this
* must never come from toISOString(). Trip dates are wall-clock dates
* (daysUntil below parses them as local midnight).
*/
export function localIsoToday(): string {
return localIsoDate()
}
export function daysUntil(dateStr: string | null | undefined): number | null {
if (!dateStr) return null
const today = new Date(); today.setHours(0, 0, 0, 0)
@@ -37,7 +47,7 @@ export function daysUntil(dateStr: string | null | undefined): number | null {
}
export function getTripStatus(trip: DashboardTrip): 'ongoing' | 'today' | 'tomorrow' | 'future' | 'past' | null {
const today = new Date().toISOString().split('T')[0]
const today = localIsoToday()
if (trip.start_date && trip.end_date && trip.start_date <= today && trip.end_date >= today) return 'ongoing'
const until = daysUntil(trip.start_date)
if (until === null) return null
@@ -48,7 +58,7 @@ export function getTripStatus(trip: DashboardTrip): 'ongoing' | 'today' | 'tomor
}
export function sortTrips(trips: DashboardTrip[]): DashboardTrip[] {
const today = new Date().toISOString().split('T')[0]
const today = localIsoToday()
const rank = (t: DashboardTrip) => {
if (t.start_date && t.end_date && t.start_date <= today && t.end_date >= today) return 0
if (t.start_date && t.start_date >= today) return 1
+2 -1
View File
@@ -6,6 +6,7 @@ import { useAuthStore } from '../../store/authStore'
import { useTranslation } from '../../i18n'
import { useToast } from '../../components/shared/Toast'
import { getApiErrorMessage } from '../../types'
import { localIsoToday } from './dashboardModel'
import type { TripCreateRequest } from '@trek/shared'
import {
type DashboardTrip,
@@ -92,7 +93,7 @@ export function useDashboard() {
loadTrips()
}
const today = new Date().toISOString().split('T')[0]
const today = localIsoToday()
// A trip the hero features on its own merit: one that is running, else the next
// one coming up. Only that one is taken out of the grid below, so the same trip
// isn't shown twice.
+30
View File
@@ -59,3 +59,33 @@ describe('computeJourneyLifecycle', () => {
expect(computeJourneyLifecycle('published', null, TODAY)).toBe('live')
})
})
/**
* The midnight-window regression: "today" must be the LOCAL calendar date.
* The old code derived it from toISOString() (the UTC date), so between local
* midnight and the UTC rollover a journey running through yesterday still
* showed 'live' and one starting today showed 'upcoming'. Pinned at 00:30
* local — vacuous on a UTC machine, biting on any TZ ahead of UTC.
*/
describe('computeJourneyLifecycle — local today at the midnight window', () => {
beforeAll(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date(2026, 7, 25, 0, 30)) // local 2026-08-25 00:30
})
afterAll(() => {
vi.useRealTimers()
})
it('a journey spanning the local today is live', () => {
expect(computeJourneyLifecycle('active', '2026-08-25', '2026-08-26')).toBe('live')
})
it('a journey that ended yesterday (local) is completed', () => {
expect(computeJourneyLifecycle('active', '2026-08-20', '2026-08-24')).toBe('completed')
})
it('single boundaries follow the local date too', () => {
expect(computeJourneyLifecycle('active', '2026-08-25', null)).toBe('live') // starts today → live
expect(computeJourneyLifecycle('active', null, '2026-08-24')).toBe('completed') // ended yesterday
})
})
+5 -3
View File
@@ -1,3 +1,5 @@
import { localIsoDate } from './localDate'
export type JourneyLifecycle = 'archived' | 'live' | 'upcoming' | 'completed' | 'draft'
export function computeJourneyLifecycle(
@@ -8,7 +10,7 @@ export function computeJourneyLifecycle(
if (status === 'archived') return 'archived'
if (tripDateMin && tripDateMax) {
const today = new Date().toISOString().split('T')[0]
const today = localIsoDate()
if (tripDateMin <= today && today <= tripDateMax) return 'live'
if (tripDateMin > today) return 'upcoming'
return 'completed'
@@ -20,11 +22,11 @@ export function computeJourneyLifecycle(
// Single boundary: only start or only end
if (tripDateMin && !tripDateMax) {
const today = new Date().toISOString().split('T')[0]
const today = localIsoDate()
return tripDateMin > today ? 'upcoming' : 'live'
}
if (!tripDateMin && tripDateMax) {
const today = new Date().toISOString().split('T')[0]
const today = localIsoDate()
return tripDateMax < today ? 'completed' : 'live'
}
+28
View File
@@ -0,0 +1,28 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { localIsoDate } from './localDate'
/**
* "Today" must be the user's local calendar date. `toISOString()` is the UTC
* date — between local midnight and the UTC rollover it is YESTERDAY for any
* TZ ahead of UTC, which misclassified trips/journeys in that window. On a UTC
* machine local == UTC, so the distinction is vacuous there; these tests pin
* the clock at 00:30 local, where any positive offset separates the two.
*/
describe('localIsoDate', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date(2026, 7, 25, 0, 30)) // local 2026-08-25 00:30
})
afterEach(() => {
vi.useRealTimers()
})
it('formats the local calendar date, zero-padded', () => {
expect(localIsoDate()).toBe('2026-08-25')
expect(localIsoDate(new Date(2026, 0, 3, 12))).toBe('2026-01-03')
})
it('defaults to now', () => {
expect(localIsoDate()).toBe(localIsoDate(new Date()))
})
})
+16
View File
@@ -0,0 +1,16 @@
/**
* The user's LOCAL calendar date as 'YYYY-MM-DD'.
*
* Never derive "today" from `toISOString().split('T')[0]` — that is the UTC
* date, which disagrees with the wall clock between local midnight and the UTC
* rollover (for a UTC+2 user, from 00:00 to 02:00 every night). Trip and
* journey dates are wall-clock dates, so every "is this today/past/upcoming"
* comparison must use this instead.
*
* (Converting a Date you deliberately built in UTC — `new Date(s + 'T00:00:00Z')`
* round-tripped through date arithmetic — is a different, self-consistent
* pattern and fine as it is.)
*/
export function localIsoDate(d: Date = new Date()): string {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
@@ -25,10 +25,13 @@ vi.mock('../../../../src/components/Plugins/TripCardBadges', () => ({
useTripCardBadges: () => (tripId: number) => mocks.badges[tripId] ?? [],
}));
// Local-calendar date string — NOT toISOString(), which is the UTC date and
// disagrees with the badge logic's wall-clock classification between local
// midnight and the UTC rollover (these tests flaked in exactly that window).
const iso = (offsetDays: number): string => {
const d = new Date();
d.setDate(d.getDate() + offsetDays);
return d.toISOString().split('T')[0];
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
function buildTrip(over: Partial<DashboardTrip> = {}): DashboardTrip {
@@ -1,6 +1,7 @@
import { StrictMode } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { http, HttpResponse } from 'msw';
import { localIsoDate } from '../../../../src/utils/localDate';
import userEvent from '@testing-library/user-event';
import { mapsApi, weatherApi } from '../../../../src/api/client';
import { useAddonStore } from '../../../../src/store/addonStore';
@@ -326,8 +327,8 @@ describe('MJourneyEntrySheet full editor', () => {
expect(screen.getByText('Mood')).toBeInTheDocument();
expect(screen.getByText('Weather')).toBeInTheDocument();
expect(screen.getByText('Tags')).toBeInTheDocument();
// An empty entry_date falls back to today, an empty gallery locks the picker.
expect(dateField().value).toBe(new Date().toISOString().split('T')[0]);
// An empty entry_date falls back to today (the LOCAL date), an empty gallery locks the picker.
expect(dateField().value).toBe(localIsoDate());
expect(timeField()).toHaveValue('');
expect(screen.getByRole('button', { name: 'From Gallery' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Upload photos' })).toBeEnabled();