mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 08:15:38 +00:00
122 lines
5.8 KiB
TypeScript
122 lines
5.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
formatPrintTimeZone,
|
|
groupEventsByDay,
|
|
layoutMonthWeekForPrint,
|
|
monthCellCapacity,
|
|
printHourRange,
|
|
} from '../calendar-print';
|
|
import { buildWeekSegments, type CalendarWeekSegment } from '../calendar-utils';
|
|
import type { CalendarEvent, CalendarTask } from '@/lib/jmap/types';
|
|
|
|
function event(id: string, start: string, duration: string, allDay = false): CalendarEvent {
|
|
return {
|
|
id, uid: id, title: id, start, duration, showWithoutTime: allDay,
|
|
calendarIds: { cal: true }, status: 'confirmed',
|
|
} as unknown as CalendarEvent;
|
|
}
|
|
|
|
const segment = (id: string, startIndex: number, span: number, row: number): CalendarWeekSegment => ({
|
|
event: event(id, '2026-10-26T00:00:00', 'P1D', true), startIndex, span, row, continuesBefore: false, continuesAfter: false,
|
|
});
|
|
const noTasks = (): CalendarTask[][] => Array.from({ length: 7 }, () => []);
|
|
|
|
describe('monthCellCapacity', () => {
|
|
it('fits more lines in a month of four weeks than of six, and more on a portrait page', () => {
|
|
expect(monthCellCapacity(6, 'landscape')).toBe(5);
|
|
expect(monthCellCapacity(4, 'landscape')).toBeGreaterThan(monthCellCapacity(6, 'landscape'));
|
|
expect(monthCellCapacity(6, 'portrait')).toBeGreaterThan(monthCellCapacity(6, 'landscape'));
|
|
});
|
|
});
|
|
|
|
describe('layoutMonthWeekForPrint', () => {
|
|
it('shows every entry of a day that fits, with no "+N more"', () => {
|
|
const segments = [0, 1, 2].map((row) => segment(`e${row}`, 0, 1, row));
|
|
const layout = layoutMonthWeekForPrint(segments, noTasks(), 3);
|
|
expect(layout.chips).toHaveLength(3);
|
|
expect(layout.more).toEqual([]);
|
|
});
|
|
|
|
it('gives a day that overflows one line less and a "+N more" in its last line', () => {
|
|
const segments = [0, 1, 2, 3].map((row) => segment(`e${row}`, 2, 1, row));
|
|
const layout = layoutMonthWeekForPrint(segments, noTasks(), 3);
|
|
expect(layout.chips.map((c) => c.key)).toEqual(['e0-2-0', 'e1-2-1']);
|
|
expect(layout.more).toEqual([{ dayIndex: 2, lane: 2, count: 2 }]);
|
|
});
|
|
|
|
it('keeps a span on the days it fits and cuts it on the one that overflows', () => {
|
|
// A three-day bar on line 2; Tuesday (index 1) has two more events below it.
|
|
const segments = [
|
|
segment('bar', 0, 3, 2),
|
|
segment('a', 1, 1, 0), segment('b', 1, 1, 1), segment('c', 1, 1, 3),
|
|
];
|
|
const layout = layoutMonthWeekForPrint(segments, noTasks(), 3);
|
|
const bars = layout.chips.filter((c) => c.item.kind === 'event' && c.item.event.id === 'bar');
|
|
expect(bars.map((c) => [c.startIndex, c.span, c.continuesBefore, c.continuesAfter])).toEqual([
|
|
[0, 1, false, true],
|
|
[2, 1, true, false],
|
|
]);
|
|
expect(layout.more).toEqual([{ dayIndex: 1, lane: 2, count: 2 }]);
|
|
});
|
|
|
|
it('stacks tasks under their day\'s events and counts the ones that do not fit', () => {
|
|
const tasks = noTasks();
|
|
tasks[4] = [1, 2, 3].map((n) => ({ id: `t${n}`, title: `t${n}`, calendarIds: {} } as unknown as CalendarTask));
|
|
const layout = layoutMonthWeekForPrint([segment('e', 4, 1, 0)], tasks, 3);
|
|
expect(layout.chips.filter((c) => c.item.kind === 'task').map((c) => c.lane)).toEqual([1]);
|
|
expect(layout.more).toEqual([{ dayIndex: 4, lane: 2, count: 2 }]);
|
|
});
|
|
});
|
|
|
|
describe('printHourRange', () => {
|
|
const working = { startMinutes: 8 * 60, endMinutes: 18 * 60, restricted: true };
|
|
const day = new Date(2026, 9, 28);
|
|
|
|
it('widens the configured hours to whole hours around an event outside them', () => {
|
|
const early = event('early', '2026-10-28T06:30:00', 'PT45M');
|
|
const late = event('late', '2026-10-28T19:10:00', 'PT20M');
|
|
expect(printHourRange(working, [early, late], [day])).toEqual({ startMinutes: 6 * 60, endMinutes: 20 * 60, restricted: true });
|
|
});
|
|
|
|
it('keeps the configured hours when every event is inside them', () => {
|
|
expect(printHourRange(working, [event('lunch', '2026-10-28T12:00:00', 'PT1H')], [day])).toEqual(working);
|
|
});
|
|
|
|
it('does not widen for an event that fills the whole day (drawn in the all-day row)', () => {
|
|
expect(printHourRange(working, [event('trip', '2026-10-27T12:00:00', 'P2D')], [day])).toEqual(working);
|
|
});
|
|
});
|
|
|
|
describe('groupEventsByDay', () => {
|
|
it('lists a multi-day event on each of its days, in time order, within the range', () => {
|
|
const conference = event('conference', '2026-10-27T00:00:00', 'P3D', true);
|
|
const standup = event('standup', '2026-10-28T09:00:00', 'PT15M');
|
|
const breakfast = event('breakfast', '2026-10-28T07:30:00', 'PT30M');
|
|
const days = groupEventsByDay([standup, conference, breakfast], { start: new Date(2026, 9, 28), end: new Date(2026, 9, 31) });
|
|
expect(days.map((d) => [d.dateKey, d.events.map((e) => e.id)])).toEqual([
|
|
['2026-10-28', ['conference', 'breakfast', 'standup']],
|
|
['2026-10-29', ['conference']],
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('formatPrintTimeZone', () => {
|
|
it('names the zone in the user\'s language with its id', () => {
|
|
expect(formatPrintTimeZone('Europe/Berlin', 'en', new Date(2026, 9, 28))).toBe('Central European Time (Europe/Berlin)');
|
|
expect(formatPrintTimeZone('Europe/Berlin', 'de', new Date(2026, 9, 28))).toBe('Mitteleuropäische Zeit (Europe/Berlin)');
|
|
});
|
|
|
|
it('falls back to the id for a zone Intl does not know', () => {
|
|
expect(formatPrintTimeZone('Mars/Olympus', 'en')).toBe('Mars/Olympus');
|
|
});
|
|
});
|
|
|
|
// Keep the packed segments the month print gets in line with the screen's.
|
|
describe('month print input', () => {
|
|
it('uses the same lanes as the month view', () => {
|
|
const week = Array.from({ length: 7 }, (_, i) => new Date(2026, 9, 26 + i));
|
|
const segments = buildWeekSegments([event('a', '2026-10-27T00:00:00', 'P2D', true), event('b', '2026-10-27T10:00:00', 'PT1H')], week);
|
|
expect(segments.map((s) => [s.event.id, s.startIndex, s.span, s.row])).toEqual([['a', 1, 2, 0], ['b', 1, 1, 1]]);
|
|
});
|
|
});
|