Replaces global singleton configuration with dependency injection Introduces more modular and testable approach to configuration Removes direct references to calendarConfig in multiple components Adds explicit configuration passing to constructors Improves code maintainability and reduces global state dependencies
220 lines
8.3 KiB
TypeScript
220 lines
8.3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { DateService } from '../../src/utils/DateService';
|
|
import { CalendarConfig } from '../../src/core/CalendarConfig';
|
|
|
|
describe('DateService - Edge Cases', () => {
|
|
const config = new CalendarConfig();
|
|
const dateService = new DateService(config);
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|