mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 00:14:23 +00:00
fix: keep out-of-office dates right when the time zone setting arrives or changes after the page opened
This commit is contained in:
@@ -100,6 +100,21 @@ describe('out-of-office templates', () => {
|
||||
expect(sent.subject).toMatch(/^Away until Monday,? 4 January 2027$/);
|
||||
});
|
||||
|
||||
it('follows a time zone that arrives after the page opened, without saving or shifting the dates', async () => {
|
||||
const server = await openCard([], 'UTC');
|
||||
const inputs = () => document.querySelectorAll('input[type="datetime-local"]') as NodeListOf<HTMLInputElement>;
|
||||
expect(inputs()[1].value).toBe('2027-01-03T23:00');
|
||||
// Settings sync brings the user's zone a moment later.
|
||||
act(() => useSettingsStore.setState({ timeZone: 'Asia/Tokyo' }));
|
||||
expect(inputs()[1].value).toBe('2027-01-04T08:00');
|
||||
await settle();
|
||||
expect(server.client.setVacationResponse).not.toHaveBeenCalled();
|
||||
// An edit afterwards is read on the new clock.
|
||||
fireEvent.change(inputs()[0], { target: { value: '2026-12-22T09:00' } });
|
||||
await waitFor(() => expect(server.client.setVacationResponse).toHaveBeenCalled(), { timeout: 3000 });
|
||||
expect(server.client.setVacationResponse.mock.calls[0][0]).toMatchObject({ fromDate: '2026-12-22T00:00:00.000Z', toDate: '2027-01-03T23:00:00Z' });
|
||||
});
|
||||
|
||||
it('shows and fills the dates in the time zone set in Bulwark, not the browser’s', async () => {
|
||||
await openCard([{ ...TEMPLATE, text: 'Back {end}', subject: '' }], 'Asia/Tokyo');
|
||||
const inputs = document.querySelectorAll('input[type="datetime-local"]');
|
||||
|
||||
@@ -132,14 +132,26 @@ export function VacationSettings() {
|
||||
// The date fields as typed ("yyyy-mm-ddThh:mm" on the browser's clock).
|
||||
const [fromInput, setFromInput] = useState(() => (fromDate ? utcToLocalDatetime(fromDate, timeZone) : ''));
|
||||
const [toInput, setToInput] = useState(() => (toDate ? utcToLocalDatetime(toDate, timeZone) : ''));
|
||||
// The zone the fields' wall-clock text is in. The time zone setting can
|
||||
// arrive or change after the fields were filled (settings sync): the text
|
||||
// is read in the zone it was written in, then rewritten for the new zone.
|
||||
const [inputZone, setInputZone] = useState(timeZone);
|
||||
// A field the browser holds half filled in (a date without its time) reads
|
||||
// as empty, without an event when it was empty before: told apart here, so
|
||||
// it does not pass for "no date", which leaves that end of the period open.
|
||||
const [halfFilled, setHalfFilled] = useState({ from: false, to: false });
|
||||
const fromRef = useRef<HTMLInputElement>(null);
|
||||
const toRef = useRef<HTMLInputElement>(null);
|
||||
const localFromDate = fieldMoment(fromInput, fromDate, timeZone);
|
||||
const localToDate = fieldMoment(toInput, toDate, timeZone);
|
||||
const localFromDate = fieldMoment(fromInput, fromDate, inputZone);
|
||||
const localToDate = fieldMoment(toInput, toDate, inputZone);
|
||||
useEffect(() => {
|
||||
if (timeZone === inputZone) return;
|
||||
// The same moments, shown on the new zone's clock; nothing is saved for it.
|
||||
if (localFromDate) setFromInput(utcToLocalDatetime(localFromDate, timeZone));
|
||||
if (localToDate) setToInput(utcToLocalDatetime(localToDate, timeZone));
|
||||
setInputZone(timeZone);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [timeZone]);
|
||||
const datesUnusable = localFromDate === null || localToDate === null || halfFilled.from || halfFilled.to;
|
||||
const [localSubject, setLocalSubject] = useState(subject);
|
||||
const [localTextBody, setLocalTextBody] = useState(textBody);
|
||||
@@ -195,6 +207,7 @@ export function VacationSettings() {
|
||||
setLocalEnabled(stored.isEnabled);
|
||||
setFromInput(stored.fromDate ? utcToLocalDatetime(stored.fromDate, timeZone) : '');
|
||||
setToInput(stored.toDate ? utcToLocalDatetime(stored.toDate, timeZone) : '');
|
||||
setInputZone(timeZone);
|
||||
setHalfFilled({ from: false, to: false });
|
||||
setLocalSubject(stored.subject);
|
||||
setLocalTextBody(stored.textBody);
|
||||
|
||||
Reference in New Issue
Block a user