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.
218 lines
8.2 KiB
TypeScript
218 lines
8.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { DateService } from '../../src/utils/DateService';
|
|
|
|
describe('DateService - Edge Cases', () => {
|
|
const dateService = new DateService('Europe/Copenhagen');
|
|
|
|
describe('Leap Year Handling', () => {
|
|
it('should handle February 29 in leap year (2024)', () => {
|
|
const leapDate = new Date(2024, 1, 29); // Feb 29, 2024
|
|
expect(dateService.isValid(leapDate)).toBe(true);
|
|
expect(leapDate.getMonth()).toBe(1); // February
|
|
expect(leapDate.getDate()).toBe(29);
|
|
});
|
|
|
|
it('should reject February 29 in non-leap year (2023)', () => {
|
|
const invalidDate = new Date(2023, 1, 29); // Tries Feb 29, 2023
|
|
// JavaScript auto-corrects to March 1
|
|
expect(invalidDate.getMonth()).toBe(2); // March
|
|
expect(invalidDate.getDate()).toBe(1);
|
|
});
|
|
|
|
it('should handle February 28 in non-leap year', () => {
|
|
const validDate = new Date(2023, 1, 28); // Feb 28, 2023
|
|
expect(dateService.isValid(validDate)).toBe(true);
|
|
expect(validDate.getMonth()).toBe(1); // February
|
|
expect(validDate.getDate()).toBe(28);
|
|
});
|
|
|
|
it('should correctly add 1 year to Feb 29 (leap year)', () => {
|
|
const leapDate = new Date(2024, 1, 29);
|
|
const nextYear = dateService.addDays(leapDate, 365); // 2025 is not leap year
|
|
|
|
// Should be Feb 28, 2025 (or March 1 depending on implementation)
|
|
expect(nextYear.getFullYear()).toBe(2025);
|
|
expect(nextYear.getMonth()).toBeGreaterThanOrEqual(1); // Feb or March
|
|
});
|
|
|
|
it('should validate leap year dates with isWithinBounds', () => {
|
|
const leapDate2024 = new Date(2024, 1, 29);
|
|
const leapDate2000 = new Date(2000, 1, 29);
|
|
|
|
expect(dateService.isWithinBounds(leapDate2024)).toBe(true);
|
|
expect(dateService.isWithinBounds(leapDate2000)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('ISO Week 53 Handling', () => {
|
|
it('should correctly identify week 53 in 2020 (has week 53)', () => {
|
|
const dec31_2020 = new Date(2020, 11, 31); // Dec 31, 2020
|
|
const weekNum = dateService.getWeekNumber(dec31_2020);
|
|
expect(weekNum).toBe(53);
|
|
});
|
|
|
|
it('should correctly identify week 53 in 2026 (has week 53)', () => {
|
|
const dec31_2026 = new Date(2026, 11, 31); // Dec 31, 2026
|
|
const weekNum = dateService.getWeekNumber(dec31_2026);
|
|
expect(weekNum).toBe(53);
|
|
});
|
|
|
|
it('should NOT have week 53 in 2021 (goes to week 52)', () => {
|
|
const dec31_2021 = new Date(2021, 11, 31); // Dec 31, 2021
|
|
const weekNum = dateService.getWeekNumber(dec31_2021);
|
|
expect(weekNum).toBe(52);
|
|
});
|
|
|
|
it('should handle transition from week 53 to week 1', () => {
|
|
const lastDayOf2020 = new Date(2020, 11, 31); // Week 53
|
|
const firstDayOf2021 = dateService.addDays(lastDayOf2020, 1);
|
|
|
|
expect(dateService.getWeekNumber(lastDayOf2020)).toBe(53);
|
|
expect(dateService.getWeekNumber(firstDayOf2021)).toBe(53); // Still week 53!
|
|
|
|
// Monday after should be week 1
|
|
const firstMonday2021 = new Date(2021, 0, 4);
|
|
expect(dateService.getWeekNumber(firstMonday2021)).toBe(1);
|
|
});
|
|
|
|
it('should get correct week bounds for week 53', () => {
|
|
const dec31_2020 = new Date(2020, 11, 31);
|
|
const weekBounds = dateService.getWeekBounds(dec31_2020);
|
|
|
|
// Week 53 of 2020 starts on Monday Dec 28, 2020
|
|
expect(weekBounds.start.getDate()).toBe(28);
|
|
expect(weekBounds.start.getMonth()).toBe(11); // December
|
|
|
|
// Ends on Sunday Jan 3, 2021
|
|
expect(weekBounds.end.getDate()).toBe(3);
|
|
expect(weekBounds.end.getMonth()).toBe(0); // January
|
|
expect(weekBounds.end.getFullYear()).toBe(2021);
|
|
});
|
|
});
|
|
|
|
describe('Month Boundary Edge Cases', () => {
|
|
it('should correctly add months across year boundary', () => {
|
|
const nov2024 = new Date(2024, 10, 15); // Nov 15, 2024
|
|
const feb2025 = dateService.addMonths(nov2024, 3);
|
|
|
|
expect(feb2025.getFullYear()).toBe(2025);
|
|
expect(feb2025.getMonth()).toBe(1); // February
|
|
expect(feb2025.getDate()).toBe(15);
|
|
});
|
|
|
|
it('should handle month-end overflow (Jan 31 + 1 month)', () => {
|
|
const jan31 = new Date(2024, 0, 31);
|
|
const result = dateService.addMonths(jan31, 1);
|
|
|
|
// date-fns addMonths handles this gracefully
|
|
expect(result.getMonth()).toBe(1); // February
|
|
expect(result.getFullYear()).toBe(2024);
|
|
// Will be Feb 29 (leap year) or last day of Feb
|
|
});
|
|
|
|
it('should handle adding negative months', () => {
|
|
const mar2024 = new Date(2024, 2, 15); // March 15, 2024
|
|
const dec2023 = dateService.addMonths(mar2024, -3);
|
|
|
|
expect(dec2023.getFullYear()).toBe(2023);
|
|
expect(dec2023.getMonth()).toBe(11); // December
|
|
expect(dec2023.getDate()).toBe(15);
|
|
});
|
|
});
|
|
|
|
describe('Year Boundary Edge Cases', () => {
|
|
it('should handle year transition (Dec 31 -> Jan 1)', () => {
|
|
const dec31 = new Date(2024, 11, 31);
|
|
const jan1 = dateService.addDays(dec31, 1);
|
|
|
|
expect(jan1.getFullYear()).toBe(2025);
|
|
expect(jan1.getMonth()).toBe(0); // January
|
|
expect(jan1.getDate()).toBe(1);
|
|
});
|
|
|
|
it('should handle reverse year transition (Jan 1 -> Dec 31)', () => {
|
|
const jan1 = new Date(2024, 0, 1);
|
|
const dec31 = dateService.addDays(jan1, -1);
|
|
|
|
expect(dec31.getFullYear()).toBe(2023);
|
|
expect(dec31.getMonth()).toBe(11); // December
|
|
expect(dec31.getDate()).toBe(31);
|
|
});
|
|
|
|
it('should correctly calculate week bounds at year boundary', () => {
|
|
const jan1_2024 = new Date(2024, 0, 1);
|
|
const weekBounds = dateService.getWeekBounds(jan1_2024);
|
|
|
|
// Jan 1, 2024 is a Monday (week 1)
|
|
expect(weekBounds.start.getDate()).toBe(1);
|
|
expect(weekBounds.start.getMonth()).toBe(0);
|
|
expect(weekBounds.start.getFullYear()).toBe(2024);
|
|
});
|
|
});
|
|
|
|
describe('DST Transition Edge Cases', () => {
|
|
it('should handle spring DST transition (CET -> CEST)', () => {
|
|
// Last Sunday of March 2024: March 31, 02:00 -> 03:00
|
|
const beforeDST = new Date(2024, 2, 31, 1, 30); // 01:30 CET
|
|
const afterDST = new Date(2024, 2, 31, 3, 30); // 03:30 CEST
|
|
|
|
expect(dateService.isValid(beforeDST)).toBe(true);
|
|
expect(dateService.isValid(afterDST)).toBe(true);
|
|
|
|
// The hour 02:00-03:00 doesn't exist!
|
|
const nonExistentTime = new Date(2024, 2, 31, 2, 30);
|
|
// JavaScript auto-adjusts this
|
|
expect(nonExistentTime.getHours()).not.toBe(2);
|
|
});
|
|
|
|
it('should handle fall DST transition (CEST -> CET)', () => {
|
|
// Last Sunday of October 2024: October 27, 03:00 -> 02:00
|
|
const beforeDST = new Date(2024, 9, 27, 2, 30, 0, 0);
|
|
const afterDST = new Date(2024, 9, 27, 3, 30, 0, 0);
|
|
|
|
expect(dateService.isValid(beforeDST)).toBe(true);
|
|
expect(dateService.isValid(afterDST)).toBe(true);
|
|
|
|
// 02:00-03:00 exists TWICE (ambiguous hour)
|
|
// This is handled by timezone-aware libraries
|
|
});
|
|
|
|
it('should calculate duration correctly across DST', () => {
|
|
// Event spanning DST transition
|
|
const start = new Date(2024, 2, 31, 1, 0); // Before DST
|
|
const end = new Date(2024, 2, 31, 4, 0); // After DST
|
|
|
|
const duration = dateService.getDurationMinutes(start, end);
|
|
|
|
// Clock time: 3 hours, but actual duration: 2 hours (due to DST)
|
|
// date-fns should handle this correctly
|
|
expect(duration).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Extreme Date Values', () => {
|
|
it('should reject dates before 1900', () => {
|
|
const oldDate = new Date(1899, 11, 31);
|
|
expect(dateService.isWithinBounds(oldDate)).toBe(false);
|
|
});
|
|
|
|
it('should reject dates after 2100', () => {
|
|
const futureDate = new Date(2101, 0, 1);
|
|
expect(dateService.isWithinBounds(futureDate)).toBe(false);
|
|
});
|
|
|
|
it('should accept boundary dates (1900 and 2100)', () => {
|
|
const minDate = new Date(1900, 0, 1);
|
|
const maxDate = new Date(2100, 11, 31);
|
|
|
|
expect(dateService.isWithinBounds(minDate)).toBe(true);
|
|
expect(dateService.isWithinBounds(maxDate)).toBe(true);
|
|
});
|
|
|
|
it('should validate invalid Date objects', () => {
|
|
const invalidDate = new Date('invalid');
|
|
expect(dateService.isValid(invalidDate)).toBe(false);
|
|
expect(dateService.isWithinBounds(invalidDate)).toBe(false);
|
|
});
|
|
});
|
|
});
|