Refactors dependency injection and configuration management
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
This commit is contained in:
parent
fb48e410ea
commit
8bbb2f05d3
30 changed files with 365 additions and 559 deletions
|
|
@ -16,16 +16,20 @@
|
|||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { EventStackManager } from '../../src/managers/EventStackManager';
|
||||
import { EventLayoutCoordinator } from '../../src/managers/EventLayoutCoordinator';
|
||||
import { calendarConfig } from '../../src/core/CalendarConfig';
|
||||
import { CalendarConfig } from '../../src/core/CalendarConfig';
|
||||
import { PositionUtils } from '../../src/utils/PositionUtils';
|
||||
import { DateService } from '../../src/utils/DateService';
|
||||
|
||||
describe('EventStackManager - Flexbox & Nested Stacking (3-Phase Algorithm)', () => {
|
||||
let manager: EventStackManager;
|
||||
let thresholdMinutes: number;
|
||||
let config: CalendarConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new EventStackManager();
|
||||
config = new CalendarConfig();
|
||||
manager = new EventStackManager(config);
|
||||
// Get threshold from config - tests should work with any value
|
||||
thresholdMinutes = calendarConfig.getGridSettings().gridStartThresholdMinutes;
|
||||
thresholdMinutes = config.getGridSettings().gridStartThresholdMinutes;
|
||||
});
|
||||
|
||||
// ============================================
|
||||
|
|
@ -1128,7 +1132,9 @@ describe('EventStackManager - Flexbox & Nested Stacking (3-Phase Algorithm)', ()
|
|||
];
|
||||
|
||||
// Use EventLayoutCoordinator to test column allocation
|
||||
const coordinator = new EventLayoutCoordinator();
|
||||
const dateService = new DateService(config);
|
||||
const positionUtils = new PositionUtils(dateService, config);
|
||||
const coordinator = new EventLayoutCoordinator(manager, config, positionUtils);
|
||||
|
||||
if (thresholdMinutes >= 30) {
|
||||
// Calculate layout
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ 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;
|
||||
|
|
@ -11,9 +12,12 @@ describe('NavigationManager - Edge Cases', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
eventBus = new EventBus();
|
||||
const config = new CalendarConfig();
|
||||
dateService = new DateService(config);
|
||||
const mockEventRenderer = {} as EventRenderingService;
|
||||
navigationManager = new NavigationManager(eventBus, mockEventRenderer);
|
||||
dateService = new DateService('Europe/Copenhagen');
|
||||
const mockGridRenderer = {} as any;
|
||||
const mockNavigationRenderer = {} as any;
|
||||
navigationManager = new NavigationManager(eventBus, mockEventRenderer, mockGridRenderer, dateService, mockNavigationRenderer);
|
||||
});
|
||||
|
||||
describe('Week 53 Navigation', () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { DateService } from '../../src/utils/DateService';
|
||||
import { CalendarConfig } from '../../src/core/CalendarConfig';
|
||||
|
||||
describe('DateService - Edge Cases', () => {
|
||||
const dateService = new DateService('Europe/Copenhagen');
|
||||
const config = new CalendarConfig();
|
||||
const dateService = new DateService(config);
|
||||
|
||||
describe('Leap Year Handling', () => {
|
||||
it('should handle February 29 in leap year (2024)', () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { DateService } from '../../src/utils/DateService';
|
||||
import { CalendarConfig } from '../../src/core/CalendarConfig';
|
||||
|
||||
describe('DateService', () => {
|
||||
let dateService: DateService;
|
||||
|
||||
beforeEach(() => {
|
||||
dateService = new DateService('Europe/Copenhagen');
|
||||
const config = new CalendarConfig();
|
||||
dateService = new DateService(config);
|
||||
});
|
||||
|
||||
describe('Core Conversions', () => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { DateService } from '../../src/utils/DateService';
|
||||
import { CalendarConfig } from '../../src/core/CalendarConfig';
|
||||
|
||||
describe('DateService - Validation', () => {
|
||||
const dateService = new DateService('Europe/Copenhagen');
|
||||
const config = new CalendarConfig();
|
||||
const dateService = new DateService(config);
|
||||
|
||||
describe('isValid() - Basic Date Validation', () => {
|
||||
it('should validate normal dates', () => {
|
||||
|
|
|
|||
|
|
@ -13,274 +13,99 @@ describe('TimeFormatter', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('UTC to Local Time Conversion', () => {
|
||||
it('should convert UTC time to Copenhagen time (winter time, UTC+1)', () => {
|
||||
// January 15, 2025 10:00:00 UTC = 11:00:00 CET (UTC+1)
|
||||
let utcDate = new Date('2025-01-15T10:00:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
let hours = localDate.getHours();
|
||||
let expectedHours = 11;
|
||||
|
||||
expect(hours).toBe(expectedHours);
|
||||
});
|
||||
|
||||
it('should convert UTC time to Copenhagen time (summer time, UTC+2)', () => {
|
||||
// July 15, 2025 10:00:00 UTC = 12:00:00 CEST (UTC+2)
|
||||
let utcDate = new Date('2025-07-15T10:00:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
let hours = localDate.getHours();
|
||||
let expectedHours = 12;
|
||||
|
||||
expect(hours).toBe(expectedHours);
|
||||
});
|
||||
|
||||
it('should handle midnight UTC correctly in winter', () => {
|
||||
// January 15, 2025 00:00:00 UTC = 01:00:00 CET
|
||||
let utcDate = new Date('2025-01-15T00:00:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
let hours = localDate.getHours();
|
||||
let expectedHours = 1;
|
||||
|
||||
expect(hours).toBe(expectedHours);
|
||||
});
|
||||
|
||||
it('should handle midnight UTC correctly in summer', () => {
|
||||
// July 15, 2025 00:00:00 UTC = 02:00:00 CEST
|
||||
let utcDate = new Date('2025-07-15T00:00:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
let hours = localDate.getHours();
|
||||
let expectedHours = 2;
|
||||
|
||||
expect(hours).toBe(expectedHours);
|
||||
});
|
||||
|
||||
it('should handle date crossing midnight when converting from UTC', () => {
|
||||
// January 14, 2025 23:30:00 UTC = January 15, 2025 00:30:00 CET
|
||||
let utcDate = new Date('2025-01-14T23:30:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
let day = localDate.getDate();
|
||||
let hours = localDate.getHours();
|
||||
let minutes = localDate.getMinutes();
|
||||
|
||||
let expectedDay = 15;
|
||||
let expectedHours = 0;
|
||||
let expectedMinutes = 30;
|
||||
|
||||
expect(day).toBe(expectedDay);
|
||||
expect(hours).toBe(expectedHours);
|
||||
expect(minutes).toBe(expectedMinutes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Time Formatting', () => {
|
||||
it('should format time in 24-hour format', () => {
|
||||
let date = new Date('2025-01-15T10:30:00Z');
|
||||
let formatted = TimeFormatter.format24Hour(date);
|
||||
|
||||
let formatted = TimeFormatter.formatTime(date);
|
||||
|
||||
// Should be 11:30 in Copenhagen (UTC+1 in winter)
|
||||
// Always use colon separator
|
||||
expect(formatted).toBe('11:30');
|
||||
});
|
||||
|
||||
it('should format time in 12-hour format', () => {
|
||||
let date = new Date('2025-01-15T13:30:00Z');
|
||||
let formatted = TimeFormatter.format12Hour(date);
|
||||
|
||||
// Should be 2:30 PM in Copenhagen (14:30 CET = 2:30 PM)
|
||||
// 12-hour format can use locale formatting with AM/PM
|
||||
// Note: locale may use dot separator and space: "2.30 PM"
|
||||
expect(formatted).toMatch(/2[.:\s]+30/);
|
||||
expect(formatted).toMatch(/PM/i);
|
||||
});
|
||||
it('should format time at midnight', () => {
|
||||
let date = new Date('2025-01-15T23:00:00Z');
|
||||
let formatted = TimeFormatter.formatTime(date);
|
||||
|
||||
it('should format time from minutes correctly', () => {
|
||||
// 540 minutes = 9:00 AM
|
||||
let formatted = TimeFormatter.formatTimeFromMinutes(540);
|
||||
|
||||
// Always use colon separator
|
||||
expect(formatted).toBe('09:00');
|
||||
// Should be 00:00 next day in Copenhagen
|
||||
expect(formatted).toBe('00:00');
|
||||
});
|
||||
|
||||
it('should format time range correctly', () => {
|
||||
let startDate = new Date('2025-01-15T08:00:00Z');
|
||||
let endDate = new Date('2025-01-15T10:00:00Z');
|
||||
let formatted = TimeFormatter.formatTimeRange(startDate, endDate);
|
||||
|
||||
|
||||
// 08:00 UTC = 09:00 CET, 10:00 UTC = 11:00 CET
|
||||
// Always use colon separator
|
||||
expect(formatted).toBe('09:00 - 11:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Technical Date/Time Formatting', () => {
|
||||
it('should format date in technical format yyyy-mm-dd', () => {
|
||||
let date = new Date('2025-01-15T10:00:00Z');
|
||||
let formatted = TimeFormatter.formatDateTechnical(date);
|
||||
|
||||
expect(formatted).toMatch(/2025-01-15/);
|
||||
});
|
||||
it('should format time range across midnight', () => {
|
||||
let startDate = new Date('2025-01-15T22:00:00Z');
|
||||
let endDate = new Date('2025-01-16T01:00:00Z');
|
||||
let formatted = TimeFormatter.formatTimeRange(startDate, endDate);
|
||||
|
||||
it('should format time in technical format hh:mm', () => {
|
||||
let date = new Date('2025-01-15T08:30:00Z');
|
||||
let formatted = TimeFormatter.formatTimeTechnical(date, false);
|
||||
|
||||
// 08:30 UTC = 09:30 CET
|
||||
expect(formatted).toMatch(/09:30/);
|
||||
});
|
||||
|
||||
it('should format time in technical format hh:mm:ss when includeSeconds is true', () => {
|
||||
let date = new Date('2025-01-15T08:30:45Z');
|
||||
let formatted = TimeFormatter.formatTimeTechnical(date, true);
|
||||
|
||||
// 08:30:45 UTC = 09:30:45 CET
|
||||
expect(formatted).toMatch(/09:30:45/);
|
||||
});
|
||||
|
||||
it('should format datetime in technical format yyyy-mm-dd hh:mm', () => {
|
||||
TimeFormatter.configure({ showSeconds: false });
|
||||
let date = new Date('2025-01-15T08:30:00Z');
|
||||
let formatted = TimeFormatter.formatDateTimeTechnical(date);
|
||||
|
||||
// 08:30 UTC = 09:30 CET on same day
|
||||
expect(formatted).toMatch(/2025-01-15 09:30/);
|
||||
});
|
||||
|
||||
it('should format datetime with seconds when configured', () => {
|
||||
TimeFormatter.configure({ showSeconds: true });
|
||||
let date = new Date('2025-01-15T08:30:45Z');
|
||||
let formatted = TimeFormatter.formatDateTimeTechnical(date);
|
||||
|
||||
expect(formatted).toMatch(/2025-01-15 09:30:45/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Timezone Information', () => {
|
||||
it('should detect daylight saving time correctly', () => {
|
||||
let winterDate = new Date('2025-01-15T12:00:00Z');
|
||||
let summerDate = new Date('2025-07-15T12:00:00Z');
|
||||
|
||||
let isWinterDST = TimeFormatter.isDaylightSavingTime(winterDate);
|
||||
let isSummerDST = TimeFormatter.isDaylightSavingTime(summerDate);
|
||||
|
||||
// Copenhagen: Winter = no DST (CET), Summer = DST (CEST)
|
||||
// Note: The implementation might not work correctly in all environments
|
||||
// Skip this test for now as DST detection is complex
|
||||
expect(isWinterDST).toBe(false);
|
||||
// Summer DST detection may vary by environment
|
||||
expect(typeof isSummerDST).toBe('boolean');
|
||||
});
|
||||
|
||||
it('should get correct timezone abbreviation', () => {
|
||||
let winterDate = new Date('2025-01-15T12:00:00Z');
|
||||
let summerDate = new Date('2025-07-15T12:00:00Z');
|
||||
|
||||
let winterAbbr = TimeFormatter.getTimezoneAbbreviation(winterDate);
|
||||
let summerAbbr = TimeFormatter.getTimezoneAbbreviation(summerDate);
|
||||
|
||||
// Copenhagen uses CET in winter, CEST in summer
|
||||
expect(winterAbbr).toMatch(/CET|GMT\+1/);
|
||||
expect(summerAbbr).toMatch(/CEST|GMT\+2/);
|
||||
// 22:00 UTC = 23:00 CET, 01:00 UTC = 02:00 CET next day
|
||||
expect(formatted).toBe('23:00 - 02:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configuration', () => {
|
||||
it('should use configured timezone', () => {
|
||||
// Note: convertToLocalTime doesn't actually use the configured timezone
|
||||
// It just converts UTC to browser's local time
|
||||
// This is a limitation of the current implementation
|
||||
it('should respect timezone configuration', () => {
|
||||
TimeFormatter.configure({ timezone: 'America/New_York' });
|
||||
|
||||
let utcDate = new Date('2025-01-15T10:00:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
// The conversion happens but timezone config isn't used in convertToLocalTime
|
||||
// Just verify it returns a valid date
|
||||
expect(localDate).toBeInstanceOf(Date);
|
||||
expect(localDate.getTime()).toBeGreaterThan(0);
|
||||
|
||||
let date = new Date('2025-01-15T10:00:00Z');
|
||||
let formatted = TimeFormatter.formatTime(date);
|
||||
|
||||
// 10:00 UTC = 05:00 EST (UTC-5 in winter)
|
||||
expect(formatted).toBe('05:00');
|
||||
});
|
||||
|
||||
it('should respect 24-hour format setting', () => {
|
||||
TimeFormatter.configure({ use24HourFormat: true });
|
||||
let date = new Date('2025-01-15T13:00:00Z');
|
||||
let formatted = TimeFormatter.formatTime(date);
|
||||
|
||||
// Always use colon separator for 24-hour format
|
||||
expect(formatted).toBe('14:00'); // 14:00 CET
|
||||
});
|
||||
it('should respect showSeconds configuration', () => {
|
||||
TimeFormatter.configure({ showSeconds: true });
|
||||
|
||||
it('should respect 12-hour format setting', () => {
|
||||
TimeFormatter.configure({ use24HourFormat: false });
|
||||
let date = new Date('2025-01-15T13:00:00Z');
|
||||
let date = new Date('2025-01-15T10:30:45Z');
|
||||
let formatted = TimeFormatter.formatTime(date);
|
||||
|
||||
// 12-hour format can use locale formatting with AM/PM
|
||||
// Note: locale may use dot separator and space: "2.00 PM"
|
||||
expect(formatted).toMatch(/2[.:\s]+00/); // 2:00 PM CET
|
||||
expect(formatted).toMatch(/PM/i);
|
||||
|
||||
// Should include seconds
|
||||
expect(formatted).toBe('11:30:45');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle DST transition correctly (spring forward)', () => {
|
||||
// March 30, 2025 01:00:00 UTC is when Copenhagen springs forward
|
||||
// 01:00 UTC = 02:00 CET, but at 02:00 CET clocks jump to 03:00 CEST
|
||||
let beforeDST = new Date('2025-03-30T00:59:00Z');
|
||||
let afterDST = new Date('2025-03-30T01:01:00Z');
|
||||
|
||||
let beforeLocal = TimeFormatter.convertToLocalTime(beforeDST);
|
||||
let afterLocal = TimeFormatter.convertToLocalTime(afterDST);
|
||||
|
||||
let beforeHours = beforeLocal.getHours();
|
||||
let afterHours = afterLocal.getHours();
|
||||
|
||||
|
||||
let beforeFormatted = TimeFormatter.formatTime(beforeDST);
|
||||
let afterFormatted = TimeFormatter.formatTime(afterDST);
|
||||
|
||||
// Before: 00:59 UTC = 01:59 CET
|
||||
// After: 01:01 UTC = 03:01 CEST (jumped from 02:00 to 03:00)
|
||||
expect(beforeHours).toBe(1);
|
||||
expect(afterHours).toBe(3);
|
||||
expect(beforeFormatted).toBe('01:59');
|
||||
expect(afterFormatted).toBe('03:01');
|
||||
});
|
||||
|
||||
it('should handle DST transition correctly (fall back)', () => {
|
||||
// October 26, 2025 01:00:00 UTC is when Copenhagen falls back
|
||||
// 01:00 UTC = 03:00 CEST, but at 03:00 CEST clocks fall back to 02:00 CET
|
||||
let beforeDST = new Date('2025-10-26T00:59:00Z');
|
||||
let afterDST = new Date('2025-10-26T01:01:00Z');
|
||||
|
||||
let beforeLocal = TimeFormatter.convertToLocalTime(beforeDST);
|
||||
let afterLocal = TimeFormatter.convertToLocalTime(afterDST);
|
||||
|
||||
let beforeHours = beforeLocal.getHours();
|
||||
let afterHours = afterLocal.getHours();
|
||||
|
||||
|
||||
let beforeFormatted = TimeFormatter.formatTime(beforeDST);
|
||||
let afterFormatted = TimeFormatter.formatTime(afterDST);
|
||||
|
||||
// Before: 00:59 UTC = 02:59 CEST
|
||||
// After: 01:01 UTC = 02:01 CET (fell back from 03:00 to 02:00)
|
||||
expect(beforeHours).toBe(2);
|
||||
expect(afterHours).toBe(2);
|
||||
expect(beforeFormatted).toBe('02:59');
|
||||
expect(afterFormatted).toBe('02:01');
|
||||
});
|
||||
|
||||
it('should handle year boundary correctly', () => {
|
||||
// December 31, 2024 23:30:00 UTC = January 1, 2025 00:30:00 CET
|
||||
let utcDate = new Date('2024-12-31T23:30:00Z');
|
||||
let localDate = TimeFormatter.convertToLocalTime(utcDate);
|
||||
|
||||
let year = localDate.getFullYear();
|
||||
let month = localDate.getMonth();
|
||||
let day = localDate.getDate();
|
||||
let hours = localDate.getHours();
|
||||
|
||||
let expectedYear = 2025;
|
||||
let expectedMonth = 0; // January
|
||||
let expectedDay = 1;
|
||||
let expectedHours = 0;
|
||||
|
||||
expect(year).toBe(expectedYear);
|
||||
expect(month).toBe(expectedMonth);
|
||||
expect(day).toBe(expectedDay);
|
||||
expect(hours).toBe(expectedHours);
|
||||
let date = new Date('2024-12-31T23:30:00Z');
|
||||
let formatted = TimeFormatter.formatTime(date);
|
||||
|
||||
expect(formatted).toBe('00:30');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue