Calendar/test/utils/DateService.midnight.test.ts
Janus C. H. Knudsen 9bc082eed4 Improves date handling and event stacking
Enhances date validation and timezone handling using DateService, ensuring data integrity and consistency.

Refactors event rendering and dragging to correctly handle date transformations.

Adds a test plan for event stacking and z-index management.

Fixes edge cases in navigation and date calculations for week/year boundaries and DST transitions.
2025-10-04 00:32:26 +02:00

246 lines
9.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { DateService } from '../../src/utils/DateService';
describe('DateService - Midnight Crossing & Multi-Day Events', () => {
const dateService = new DateService('Europe/Copenhagen');
describe('Midnight Crossing Events', () => {
it('should handle event starting before midnight and ending after', () => {
const start = new Date(2024, 0, 15, 23, 30); // Jan 15, 23:30
const end = new Date(2024, 0, 16, 1, 30); // Jan 16, 01:30
expect(dateService.isMultiDay(start, end)).toBe(true);
expect(dateService.isSameDay(start, end)).toBe(false);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(120); // 2 hours
});
it('should calculate duration correctly across midnight', () => {
const start = new Date(2024, 0, 15, 22, 0); // 22:00
const end = new Date(2024, 0, 16, 2, 0); // 02:00 next day
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(240); // 4 hours
});
it('should handle event ending exactly at midnight', () => {
const start = new Date(2024, 0, 15, 20, 0); // 20:00
const end = new Date(2024, 0, 16, 0, 0); // 00:00 (midnight)
expect(dateService.isMultiDay(start, end)).toBe(true);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(240); // 4 hours
});
it('should handle event starting exactly at midnight', () => {
const start = new Date(2024, 0, 15, 0, 0); // 00:00 (midnight)
const end = new Date(2024, 0, 15, 3, 0); // 03:00 same day
expect(dateService.isMultiDay(start, end)).toBe(false);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(180); // 3 hours
});
it('should create date at specific time correctly across midnight', () => {
const baseDate = new Date(2024, 0, 15);
// 1440 minutes = 24:00 = midnight next day
const midnightNextDay = dateService.createDateAtTime(baseDate, 1440);
expect(midnightNextDay.getDate()).toBe(16);
expect(midnightNextDay.getHours()).toBe(0);
expect(midnightNextDay.getMinutes()).toBe(0);
// 1500 minutes = 25:00 = 01:00 next day
const oneAmNextDay = dateService.createDateAtTime(baseDate, 1500);
expect(oneAmNextDay.getDate()).toBe(16);
expect(oneAmNextDay.getHours()).toBe(1);
expect(oneAmNextDay.getMinutes()).toBe(0);
});
});
describe('Multi-Day Events', () => {
it('should detect 2-day event', () => {
const start = new Date(2024, 0, 15, 10, 0);
const end = new Date(2024, 0, 16, 14, 0);
expect(dateService.isMultiDay(start, end)).toBe(true);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(28 * 60); // 28 hours
});
it('should detect 3-day event', () => {
const start = new Date(2024, 0, 15, 9, 0);
const end = new Date(2024, 0, 17, 17, 0);
expect(dateService.isMultiDay(start, end)).toBe(true);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(56 * 60); // 56 hours
});
it('should detect week-long event', () => {
const start = new Date(2024, 0, 15, 0, 0);
const end = new Date(2024, 0, 22, 0, 0);
expect(dateService.isMultiDay(start, end)).toBe(true);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(7 * 24 * 60); // 7 days
});
it('should handle month-spanning multi-day event', () => {
const start = new Date(2024, 0, 30, 12, 0); // Jan 30
const end = new Date(2024, 1, 2, 12, 0); // Feb 2
expect(dateService.isMultiDay(start, end)).toBe(true);
expect(start.getMonth()).toBe(0); // January
expect(end.getMonth()).toBe(1); // February
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(3 * 24 * 60); // 3 days
});
it('should handle year-spanning multi-day event', () => {
const start = new Date(2024, 11, 30, 10, 0); // Dec 30, 2024
const end = new Date(2025, 0, 2, 10, 0); // Jan 2, 2025
expect(dateService.isMultiDay(start, end)).toBe(true);
expect(start.getFullYear()).toBe(2024);
expect(end.getFullYear()).toBe(2025);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(3 * 24 * 60); // 3 days
});
});
describe('Timezone Boundary Events', () => {
it('should handle UTC to local timezone conversion across midnight', () => {
// Event in UTC that crosses date boundary in local timezone
const utcStart = '2024-01-15T23:00:00Z'; // 23:00 UTC
const utcEnd = '2024-01-16T01:00:00Z'; // 01:00 UTC next day
const localStart = dateService.fromUTC(utcStart);
const localEnd = dateService.fromUTC(utcEnd);
// Copenhagen is UTC+1 (or UTC+2 in summer)
// So 23:00 UTC = 00:00 or 01:00 local (midnight crossing)
expect(localStart.getDate()).toBeGreaterThanOrEqual(15);
expect(localEnd.getDate()).toBeGreaterThanOrEqual(16);
const duration = dateService.getDurationMinutes(localStart, localEnd);
expect(duration).toBe(120); // 2 hours
});
it('should preserve duration when converting UTC to local', () => {
const utcStart = '2024-06-15T10:00:00Z';
const utcEnd = '2024-06-15T18:00:00Z';
const localStart = dateService.fromUTC(utcStart);
const localEnd = dateService.fromUTC(utcEnd);
const utcDuration = 8 * 60; // 8 hours
const localDuration = dateService.getDurationMinutes(localStart, localEnd);
expect(localDuration).toBe(utcDuration);
});
it('should handle all-day events (00:00 to 00:00 next day)', () => {
const start = new Date(2024, 0, 15, 0, 0, 0);
const end = new Date(2024, 0, 16, 0, 0, 0);
expect(dateService.isMultiDay(start, end)).toBe(true);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(24 * 60); // 24 hours
});
it('should handle multi-day all-day events', () => {
const start = new Date(2024, 0, 15, 0, 0, 0);
const end = new Date(2024, 0, 18, 0, 0, 0); // 3-day event
expect(dateService.isMultiDay(start, end)).toBe(true);
const duration = dateService.getDurationMinutes(start, end);
expect(duration).toBe(3 * 24 * 60); // 72 hours
});
});
describe('Edge Cases with Minutes Since Midnight', () => {
it('should calculate minutes since midnight correctly at day boundary', () => {
const midnight = new Date(2024, 0, 15, 0, 0);
const beforeMidnight = new Date(2024, 0, 14, 23, 59);
const afterMidnight = new Date(2024, 0, 15, 0, 1);
expect(dateService.getMinutesSinceMidnight(midnight)).toBe(0);
expect(dateService.getMinutesSinceMidnight(beforeMidnight)).toBe(23 * 60 + 59);
expect(dateService.getMinutesSinceMidnight(afterMidnight)).toBe(1);
});
it('should handle createDateAtTime with overflow minutes (>1440)', () => {
const baseDate = new Date(2024, 0, 15);
// 1500 minutes = 25 hours = next day at 01:00
const result = dateService.createDateAtTime(baseDate, 1500);
expect(result.getDate()).toBe(16); // Next day
expect(result.getHours()).toBe(1);
expect(result.getMinutes()).toBe(0);
});
it('should handle createDateAtTime with large overflow (48+ hours)', () => {
const baseDate = new Date(2024, 0, 15);
// 2880 minutes = 48 hours = 2 days later
const result = dateService.createDateAtTime(baseDate, 2880);
expect(result.getDate()).toBe(17); // 2 days later
expect(result.getHours()).toBe(0);
expect(result.getMinutes()).toBe(0);
});
});
describe('Same Day vs Multi-Day Detection', () => {
it('should correctly identify same-day events', () => {
const start = new Date(2024, 0, 15, 8, 0);
const end = new Date(2024, 0, 15, 17, 0);
expect(dateService.isSameDay(start, end)).toBe(true);
expect(dateService.isMultiDay(start, end)).toBe(false);
});
it('should correctly identify multi-day events', () => {
const start = new Date(2024, 0, 15, 23, 0);
const end = new Date(2024, 0, 16, 1, 0);
expect(dateService.isSameDay(start, end)).toBe(false);
expect(dateService.isMultiDay(start, end)).toBe(true);
});
it('should handle ISO string inputs for multi-day detection', () => {
const startISO = '2024-01-15T23:00:00Z';
const endISO = '2024-01-16T01:00:00Z';
// Convert UTC strings to local timezone first
const startLocal = dateService.fromUTC(startISO);
const endLocal = dateService.fromUTC(endISO);
const result = dateService.isMultiDay(startLocal, endLocal);
// 23:00 UTC = 00:00 CET (next day) in Copenhagen
// So this IS a multi-day event in local time
expect(result).toBe(true);
});
it('should handle mixed Date and string inputs', () => {
const startDate = new Date(2024, 0, 15, 10, 0);
const endISO = '2024-01-16T10:00:00Z';
const result = dateService.isMultiDay(startDate, endISO);
expect(typeof result).toBe('boolean');
});
});
});