Calendar/test/managers/NavigationManager.edge-cases.test.ts
Janus C. H. Knudsen b5dfd57d9e Migrates date handling from date-fns to day.js
Replaces date-fns library with day.js to reduce bundle size and improve tree-shaking

- Centralizes all date logic in DateService
- Reduces library footprint from 576 KB to 29 KB
- Maintains 99.4% test coverage during migration
- Adds timezone and formatting plugins for day.js

Improves overall library performance and reduces dependency complexity
2025-11-12 23:51:48 +01:00

299 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';
import { createTestConfig } from '../helpers/config-helpers';
describe('NavigationManager - Edge Cases', () => {
let navigationManager: NavigationManager;
let eventBus: EventBus;
let dateService: DateService;
beforeEach(() => {
eventBus = new EventBus();
const config = createTestConfig();
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());
});
});
});