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'; import { CalendarConfig } from '../../src/core/CalendarConfig'; describe('NavigationManager - Edge Cases', () => { let navigationManager: NavigationManager; let eventBus: EventBus; let dateService: DateService; beforeEach(() => { eventBus = new EventBus(); const config = new CalendarConfig(); dateService = new DateService(config); const mockEventRenderer = {} as EventRenderingService; const mockGridRenderer = {} as any; const mockNavigationRenderer = {} as any; navigationManager = new NavigationManager(eventBus, mockEventRenderer, mockGridRenderer, dateService, mockNavigationRenderer); }); 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()); }); }); });