Calendar/test/managers/NavigationManager.edge-cases.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

295 lines
11 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { NavigationManager } from '../../src/managers/NavigationManager';
import { EventBus } from '../../src/core/EventBus';
import { EventRenderingService } from '../../src/renderers/EventRendererManager';
import { DateService } from '../../src/utils/DateService';
describe('NavigationManager - Edge Cases', () => {
let navigationManager: NavigationManager;
let eventBus: EventBus;
let dateService: DateService;
beforeEach(() => {
eventBus = new EventBus();
const mockEventRenderer = {} as EventRenderingService;
navigationManager = new NavigationManager(eventBus, mockEventRenderer);
dateService = new DateService('Europe/Copenhagen');
});
describe('Week 53 Navigation', () => {
it('should correctly navigate to week 53 (year 2020)', () => {
// Dec 28, 2020 is start of week 53
const week53Start = new Date(2020, 11, 28);
const weekNum = dateService.getWeekNumber(week53Start);
expect(weekNum).toBe(53);
const weekBounds = dateService.getWeekBounds(week53Start);
expect(weekBounds.start.getDate()).toBe(28);
expect(weekBounds.start.getMonth()).toBe(11); // December
expect(weekBounds.start.getFullYear()).toBe(2020);
});
it('should navigate from week 53 to week 1 of next year', () => {
const week53 = new Date(2020, 11, 28); // Week 53, 2020
// Add 1 week should go to week 1 of 2021
const nextWeek = dateService.addWeeks(week53, 1);
const nextWeekNum = dateService.getWeekNumber(nextWeek);
expect(nextWeek.getFullYear()).toBe(2021);
expect(nextWeekNum).toBe(1);
});
it('should navigate from week 1 back to week 53 of previous year', () => {
const week1_2021 = new Date(2021, 0, 4); // Monday Jan 4, 2021 (week 1)
// Subtract 1 week should go to week 53 of 2020
const prevWeek = dateService.addWeeks(week1_2021, -1);
const prevWeekNum = dateService.getWeekNumber(prevWeek);
expect(prevWeek.getFullYear()).toBe(2020);
expect(prevWeekNum).toBe(53);
});
it('should handle years without week 53 (2021)', () => {
const dec27_2021 = new Date(2021, 11, 27); // Monday Dec 27, 2021
const weekNum = dateService.getWeekNumber(dec27_2021);
expect(weekNum).toBe(52); // No week 53 in 2021
const nextWeek = dateService.addWeeks(dec27_2021, 1);
const nextWeekNum = dateService.getWeekNumber(nextWeek);
// ISO week logic: Adding 1 week from Dec 27 gives Jan 3, which is week 1 of 2022
expect(nextWeekNum).toBe(1); // Week 1 of 2022
// Jan 3, 2022 is indeed week 1
const jan3_2022 = new Date(2022, 0, 3);
expect(dateService.getWeekNumber(jan3_2022)).toBe(1);
});
it('should correctly identify week 53 in 2026', () => {
const dec28_2026 = new Date(2026, 11, 28); // Monday Dec 28, 2026
const weekNum = dateService.getWeekNumber(dec28_2026);
expect(weekNum).toBe(53);
});
});
describe('Year Boundary Navigation', () => {
it('should navigate across year boundary (Dec -> Jan)', () => {
const lastWeekDec = new Date(2024, 11, 23); // Dec 23, 2024
const firstWeekJan = dateService.addWeeks(lastWeekDec, 1);
// Adding 1 week gives Dec 30, which is in week 1 of 2025 (ISO week logic)
expect(firstWeekJan.getMonth()).toBe(11); // Still December
const weekNum = dateService.getWeekNumber(firstWeekJan);
// Week number can be 1 (of next year) or 52 depending on ISO week rules
expect(weekNum).toBeGreaterThanOrEqual(1);
});
it('should navigate across year boundary (Jan -> Dec)', () => {
const firstWeekJan = new Date(2024, 0, 1);
const lastWeekDec = dateService.addWeeks(firstWeekJan, -1);
expect(lastWeekDec.getFullYear()).toBe(2023);
const weekNum = dateService.getWeekNumber(lastWeekDec);
expect(weekNum).toBeGreaterThanOrEqual(52);
});
it('should get correct week bounds at year start', () => {
const jan1_2024 = new Date(2024, 0, 1); // Monday Jan 1, 2024
const weekBounds = dateService.getWeekBounds(jan1_2024);
// Week should start on Monday
const startDayOfWeek = weekBounds.start.getDay();
expect(startDayOfWeek).toBe(1); // Monday = 1
expect(weekBounds.start.getDate()).toBe(1);
expect(weekBounds.start.getMonth()).toBe(0); // January
});
it('should get correct week bounds at year end', () => {
const dec31_2024 = new Date(2024, 11, 31); // Tuesday Dec 31, 2024
const weekBounds = dateService.getWeekBounds(dec31_2024);
// Week should start on Monday (Dec 30, 2024)
expect(weekBounds.start.getDate()).toBe(30);
expect(weekBounds.start.getMonth()).toBe(11);
expect(weekBounds.start.getFullYear()).toBe(2024);
// Week should end on Sunday (Jan 5, 2025)
expect(weekBounds.end.getDate()).toBe(5);
expect(weekBounds.end.getMonth()).toBe(0); // January
expect(weekBounds.end.getFullYear()).toBe(2025);
});
});
describe('DST Transition Navigation', () => {
it('should navigate across spring DST transition (March 2024)', () => {
// Spring DST: March 31, 2024, 02:00 -> 03:00
const beforeDST = new Date(2024, 2, 25); // Week before DST
const duringDST = dateService.addWeeks(beforeDST, 1);
expect(duringDST.getMonth()).toBe(3); // April
expect(dateService.isValid(duringDST)).toBe(true);
const weekBounds = dateService.getWeekBounds(duringDST);
expect(weekBounds.start.getMonth()).toBeGreaterThanOrEqual(2); // March or April
});
it('should navigate across fall DST transition (October 2024)', () => {
// Fall DST: October 27, 2024, 03:00 -> 02:00
const beforeDST = new Date(2024, 9, 21); // Week before DST
const duringDST = dateService.addWeeks(beforeDST, 1);
expect(duringDST.getMonth()).toBe(9); // October
expect(dateService.isValid(duringDST)).toBe(true);
const weekBounds = dateService.getWeekBounds(duringDST);
expect(weekBounds.end.getMonth()).toBeLessThanOrEqual(10); // October or November
});
it('should maintain week integrity across DST', () => {
const beforeDST = new Date(2024, 2, 25, 12, 0);
const afterDST = dateService.addWeeks(beforeDST, 1);
// Week bounds should still give 7-day span
const weekBounds = dateService.getWeekBounds(afterDST);
const daysDiff = (weekBounds.end.getTime() - weekBounds.start.getTime()) / (1000 * 60 * 60 * 24);
// Should be close to 7 days (accounting for DST hour change)
expect(daysDiff).toBeGreaterThanOrEqual(6.9);
expect(daysDiff).toBeLessThanOrEqual(7.1);
});
});
describe('Month Boundary Week Navigation', () => {
it('should handle week spanning month boundary', () => {
const endOfMonth = new Date(2024, 0, 29); // Jan 29, 2024 (Monday)
const weekBounds = dateService.getWeekBounds(endOfMonth);
// Week should span into February
expect(weekBounds.end.getMonth()).toBe(1); // February
expect(weekBounds.end.getDate()).toBe(4);
});
it('should navigate to next week across month boundary', () => {
const lastWeekJan = new Date(2024, 0, 29);
const firstWeekFeb = dateService.addWeeks(lastWeekJan, 1);
expect(firstWeekFeb.getMonth()).toBe(1); // February
expect(firstWeekFeb.getDate()).toBe(5);
});
it('should handle February-March boundary in leap year', () => {
const lastWeekFeb = new Date(2024, 1, 26); // Feb 26, 2024 (leap year)
const weekBounds = dateService.getWeekBounds(lastWeekFeb);
// Week should span from Feb into March
expect(weekBounds.start.getMonth()).toBe(1); // February
expect(weekBounds.end.getMonth()).toBe(2); // March
});
});
describe('Invalid Date Navigation', () => {
it('should reject navigation to invalid date', () => {
const invalidDate = new Date('invalid');
const validation = dateService.validateDate(invalidDate);
expect(validation.valid).toBe(false);
expect(validation.error).toBeDefined();
});
it('should reject navigation to out-of-bounds date', () => {
const outOfBounds = new Date(2150, 0, 1);
const validation = dateService.validateDate(outOfBounds);
expect(validation.valid).toBe(false);
expect(validation.error).toContain('bounds');
});
it('should accept valid date within bounds', () => {
const validDate = new Date(2024, 6, 15);
const validation = dateService.validateDate(validDate);
expect(validation.valid).toBe(true);
expect(validation.error).toBeUndefined();
});
});
describe('Week Number Edge Cases', () => {
it('should handle first day of year in previous year\'s week', () => {
// Jan 1, 2023 is a Sunday, part of week 52 of 2022
const jan1_2023 = new Date(2023, 0, 1);
const weekNum = dateService.getWeekNumber(jan1_2023);
expect(weekNum).toBe(52); // Part of 2022's last week
});
it('should handle last day of year in next year\'s week', () => {
// Dec 31, 2023 is a Sunday, part of week 52 of 2023
const dec31_2023 = new Date(2023, 11, 31);
const weekNum = dateService.getWeekNumber(dec31_2023);
expect(weekNum).toBe(52);
});
it('should correctly number weeks in leap year', () => {
const dates2024 = [
new Date(2024, 0, 1), // Week 1
new Date(2024, 6, 1), // Mid-year
new Date(2024, 11, 31) // Last week
];
dates2024.forEach(date => {
const weekNum = dateService.getWeekNumber(date);
expect(weekNum).toBeGreaterThanOrEqual(1);
expect(weekNum).toBeLessThanOrEqual(53);
});
});
});
describe('Navigation Continuity', () => {
it('should maintain continuity over multiple forward navigations', () => {
let currentWeek = new Date(2024, 0, 1);
for (let i = 0; i < 60; i++) { // Navigate 60 weeks forward
currentWeek = dateService.addWeeks(currentWeek, 1);
expect(dateService.isValid(currentWeek)).toBe(true);
}
// Should be in 2025
expect(currentWeek.getFullYear()).toBe(2025);
});
it('should maintain continuity over multiple backward navigations', () => {
let currentWeek = new Date(2024, 11, 31);
for (let i = 0; i < 60; i++) { // Navigate 60 weeks backward
currentWeek = dateService.addWeeks(currentWeek, -1);
expect(dateService.isValid(currentWeek)).toBe(true);
}
// Should be in 2023
expect(currentWeek.getFullYear()).toBe(2023);
});
it('should return to same week after forward+backward navigation', () => {
const originalWeek = new Date(2024, 6, 15);
const weekBoundsOriginal = dateService.getWeekBounds(originalWeek);
// Navigate 10 weeks forward, then 10 weeks back
const forward = dateService.addWeeks(originalWeek, 10);
const backAgain = dateService.addWeeks(forward, -10);
const weekBoundsBack = dateService.getWeekBounds(backAgain);
expect(weekBoundsBack.start.getTime()).toBe(weekBoundsOriginal.start.getTime());
expect(weekBoundsBack.end.getTime()).toBe(weekBoundsOriginal.end.getTime());
});
});
});