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:
Janus C. H. Knudsen 2025-10-30 23:47:30 +01:00
parent fb48e410ea
commit 8bbb2f05d3
30 changed files with 365 additions and 559 deletions

View file

@ -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');
});
});
});
});