mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: write an event's length in the app's language in its popover, as 1 Std. in German
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { useSettingsStore } from '@/stores/settings-store';
|
||||
import { AUTO_TIME_ZONE } from '@/lib/timezone';
|
||||
import type { Calendar, CalendarEvent } from '@/lib/jmap/types';
|
||||
|
||||
// pim-p2-19: the German popover read "16:00 – 17:00 (1h)"; German writes "1 Std.".
|
||||
|
||||
const lang = vi.hoisted(() => ({ locale: 'de' }));
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string, values?: Record<string, unknown>) => (values ? `${key}:${JSON.stringify(values)}` : key),
|
||||
useLocale: () => lang.locale,
|
||||
useFormatter: () => ({ dateTime: (d: Date | string) => String(d), relativeTime: (d: Date | string) => String(d), number: (n: number) => String(n) }),
|
||||
}));
|
||||
|
||||
import { EventDetailPopover } from '../event-detail-popover';
|
||||
|
||||
const calendar = { id: 'cal', name: 'Work', myRights: { mayWriteAll: true, mayWriteOwn: true } } as unknown as Calendar;
|
||||
|
||||
function popover(duration: string) {
|
||||
const event = {
|
||||
id: 'e1', calendarIds: { cal: true }, title: 'Team sync', start: '2026-11-05T16:00:00', duration,
|
||||
timeZone: 'Europe/Berlin', showWithoutTime: false, participants: null, alerts: null, useDefaultAlerts: false,
|
||||
} as unknown as CalendarEvent;
|
||||
render(
|
||||
<EventDetailPopover event={event} calendar={calendar} anchorRect={new DOMRect(100, 100, 50, 20)}
|
||||
onEdit={vi.fn()} onDelete={vi.fn()} onDuplicate={vi.fn()} onClose={vi.fn()} onSaveNote={vi.fn()}
|
||||
currentUserEmails={['me@example.com']} />,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => useSettingsStore.setState({ timeZone: 'Europe/Berlin' }));
|
||||
afterEach(() => {
|
||||
useSettingsStore.setState({ timeZone: AUTO_TIME_ZONE });
|
||||
lang.locale = 'de';
|
||||
});
|
||||
|
||||
describe('the popover writes the duration in the app language', () => {
|
||||
it('in German', () => {
|
||||
popover('PT1H');
|
||||
expect(screen.getByText('(1 Std.)')).toBeInTheDocument();
|
||||
expect(screen.queryByText('(1h)')).toBeNull();
|
||||
});
|
||||
|
||||
it('with hours and minutes', () => {
|
||||
popover('PT1H30M');
|
||||
expect(screen.getByText('(1 Std. 30 Min.)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('in English', () => {
|
||||
lang.locale = 'en';
|
||||
popover('PT45M');
|
||||
expect(screen.getByText('(45 min)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -114,12 +114,20 @@ function computePosition(
|
||||
};
|
||||
}
|
||||
|
||||
function formatDurationDisplay(minutes: number): string {
|
||||
if (minutes < 60) return `${minutes}min`;
|
||||
/** "1 hr 30 min", "1 Std. 30 Min.": the language's own short units (pim-p2-19). */
|
||||
export function formatDurationDisplay(minutes: number, locale: string): string {
|
||||
const unit = (value: number, name: "hour" | "minute") => {
|
||||
try {
|
||||
return new Intl.NumberFormat(locale, { style: "unit", unit: name, unitDisplay: "short" }).format(value);
|
||||
} catch {
|
||||
return `${value}${name === "hour" ? "h" : "min"}`;
|
||||
}
|
||||
};
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
if (m === 0) return `${h}h`;
|
||||
return `${h}h${m}min`;
|
||||
if (h === 0) return unit(m, "minute");
|
||||
if (m === 0) return unit(h, "hour");
|
||||
return `${unit(h, "hour")} ${unit(m, "minute")}`;
|
||||
}
|
||||
|
||||
function getRecurrenceLabel(event: CalendarEvent, t: ReturnType<typeof useTranslations>, locale: string): string | null {
|
||||
@@ -506,7 +514,7 @@ export function EventDetailPopover({
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-muted-foreground text-xs">
|
||||
({formatDurationDisplay(durationMinutes)})
|
||||
({formatDurationDisplay(durationMinutes, locale)})
|
||||
</div>
|
||||
{zoned && zonedText && (
|
||||
<div className="text-muted-foreground text-xs" data-testid="event-zone-times">
|
||||
@@ -529,7 +537,7 @@ export function EventDetailPopover({
|
||||
) : (
|
||||
<div className="text-muted-foreground">
|
||||
<bdi dir="ltr">{formatTime(startDate)} – {formatTime(endDate)}</bdi>
|
||||
<span className="ms-1.5 text-xs">({formatDurationDisplay(durationMinutes)})</span>
|
||||
<span className="ms-1.5 text-xs">({formatDurationDisplay(durationMinutes, locale)})</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user