@webamoki/web-svelte 1.2.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +3 -1
  2. package/dist/components/showcase/CodeBlock.svelte +1 -1
  3. package/dist/{components → shared/components}/form/Button.svelte +2 -2
  4. package/dist/{components → shared/components}/form/Button.svelte.d.ts +2 -2
  5. package/dist/{components → shared/components}/form/Errors.svelte +1 -1
  6. package/dist/{components → shared/components}/form/FieldWrapper.svelte +1 -1
  7. package/dist/{components → shared/components}/form/fields/ChoiceMultiField.svelte +3 -1
  8. package/dist/{components → shared/components}/form/fields/DateField.svelte +2 -2
  9. package/dist/{components → shared/components}/form/fields/MessageField.svelte +2 -2
  10. package/dist/{components → shared/components}/form/fields/NumberField.svelte +2 -2
  11. package/dist/{components → shared/components}/form/fields/PasswordField.svelte +2 -2
  12. package/dist/{components → shared/components}/form/fields/SelectField.svelte +3 -3
  13. package/dist/{components → shared/components}/form/fields/SelectMultiField.svelte +3 -3
  14. package/dist/{components → shared/components}/form/fields/TextField.svelte +2 -2
  15. package/dist/{components → shared/components}/form/fields/TextFieldNullable.svelte +2 -2
  16. package/dist/{components → shared/components}/form/fields/TimeField.svelte +2 -2
  17. package/dist/{components → shared/components}/ui/choice/ChoiceInternal.svelte +1 -1
  18. package/dist/{components → shared/components}/ui/choice/WeekdayChoice.svelte +6 -3
  19. package/dist/{components → shared/components}/ui/choice/WeekdayChoice.svelte.d.ts +1 -1
  20. package/dist/{components → shared/components}/ui/choice/WeekdayChoiceMulti.svelte +6 -3
  21. package/dist/{components → shared/components}/ui/choice/WeekdayChoiceMulti.svelte.d.ts +1 -1
  22. package/dist/{components → shared/components}/ui/context-menu/ContextMenuContent.svelte +1 -1
  23. package/dist/{components → shared/components}/ui/context-menu/ContextMenuItem.svelte +1 -1
  24. package/dist/{components → shared/components}/ui/search/SearchBar.svelte +2 -2
  25. package/dist/{utils/types → shared/utils}/arktype.d.ts +4 -12
  26. package/dist/shared/utils/arktype.js +40 -0
  27. package/dist/shared/utils/datetime/datetime.spec.d.ts +1 -0
  28. package/dist/shared/utils/datetime/datetime.spec.js +768 -0
  29. package/dist/{utils → shared/utils}/datetime/index.d.ts +22 -14
  30. package/dist/{utils → shared/utils}/datetime/index.js +44 -32
  31. package/dist/{utils → shared/utils}/email/README.md +5 -5
  32. package/dist/{utils → shared/utils}/email/ses.js +17 -9
  33. package/dist/shared/utils/email/ses.test.d.ts +1 -0
  34. package/dist/shared/utils/email/ses.test.js +335 -0
  35. package/dist/shared/utils/functional/index.d.ts +2 -0
  36. package/dist/shared/utils/functional/index.js +2 -0
  37. package/dist/shared/utils/functional/result.d.ts +72 -0
  38. package/dist/shared/utils/functional/result.js +86 -0
  39. package/dist/shared/utils/functional/result.spec.d.ts +1 -0
  40. package/dist/shared/utils/functional/result.spec.js +96 -0
  41. package/dist/shared/utils/remote.d.ts +28 -0
  42. package/dist/shared/utils/remote.js +74 -0
  43. package/dist/{utils/search.d.ts → shared/utils/string.d.ts} +1 -0
  44. package/dist/{utils/search.js → shared/utils/string.js} +13 -4
  45. package/package.json +28 -33
  46. package/dist/utils/email/aws-signer.d.ts +0 -17
  47. package/dist/utils/email/aws-signer.js +0 -83
  48. package/dist/utils/string.d.ts +0 -1
  49. package/dist/utils/string.js +0 -4
  50. package/dist/utils/types/arktype.js +0 -92
  51. package/dist/utils/types/consts.d.ts +0 -5
  52. package/dist/utils/types/consts.js +0 -5
  53. package/dist/utils/types/db.d.ts +0 -57
  54. package/dist/utils/types/db.js +0 -34
  55. /package/dist/{components → shared/components}/form/Errors.svelte.d.ts +0 -0
  56. /package/dist/{components → shared/components}/form/FieldWrapper.svelte.d.ts +0 -0
  57. /package/dist/{components → shared/components}/form/Form.svelte +0 -0
  58. /package/dist/{components → shared/components}/form/Form.svelte.d.ts +0 -0
  59. /package/dist/{components → shared/components}/form/IconInputWrapper.svelte +0 -0
  60. /package/dist/{components → shared/components}/form/IconInputWrapper.svelte.d.ts +0 -0
  61. /package/dist/{components → shared/components}/form/fields/ChoiceField.svelte +0 -0
  62. /package/dist/{components → shared/components}/form/fields/ChoiceField.svelte.d.ts +0 -0
  63. /package/dist/{components → shared/components}/form/fields/ChoiceMultiField.svelte.d.ts +0 -0
  64. /package/dist/{components → shared/components}/form/fields/DateField.svelte.d.ts +0 -0
  65. /package/dist/{components → shared/components}/form/fields/HexColorField.svelte +0 -0
  66. /package/dist/{components → shared/components}/form/fields/HexColorField.svelte.d.ts +0 -0
  67. /package/dist/{components → shared/components}/form/fields/MessageField.svelte.d.ts +0 -0
  68. /package/dist/{components → shared/components}/form/fields/NumberField.svelte.d.ts +0 -0
  69. /package/dist/{components → shared/components}/form/fields/PasswordField.svelte.d.ts +0 -0
  70. /package/dist/{components → shared/components}/form/fields/SelectField.svelte.d.ts +0 -0
  71. /package/dist/{components → shared/components}/form/fields/SelectMultiField.svelte.d.ts +0 -0
  72. /package/dist/{components → shared/components}/form/fields/TextField.svelte.d.ts +0 -0
  73. /package/dist/{components → shared/components}/form/fields/TextFieldNullable.svelte.d.ts +0 -0
  74. /package/dist/{components → shared/components}/form/fields/TimeField.svelte.d.ts +0 -0
  75. /package/dist/{components → shared/components}/form/fields/WeekdayChoiceField.svelte +0 -0
  76. /package/dist/{components → shared/components}/form/fields/WeekdayChoiceField.svelte.d.ts +0 -0
  77. /package/dist/{components → shared/components}/form/fields/WeekdayChoiceMultiField.svelte +0 -0
  78. /package/dist/{components → shared/components}/form/fields/WeekdayChoiceMultiField.svelte.d.ts +0 -0
  79. /package/dist/{components → shared/components}/form/index.d.ts +0 -0
  80. /package/dist/{components → shared/components}/form/index.js +0 -0
  81. /package/dist/{components → shared/components}/ui/choice/Choice.svelte +0 -0
  82. /package/dist/{components → shared/components}/ui/choice/Choice.svelte.d.ts +0 -0
  83. /package/dist/{components → shared/components}/ui/choice/ChoiceInternal.svelte.d.ts +0 -0
  84. /package/dist/{components → shared/components}/ui/choice/ChoiceMulti.svelte +0 -0
  85. /package/dist/{components → shared/components}/ui/choice/ChoiceMulti.svelte.d.ts +0 -0
  86. /package/dist/{components → shared/components}/ui/context-menu/ContextMenu.svelte +0 -0
  87. /package/dist/{components → shared/components}/ui/context-menu/ContextMenu.svelte.d.ts +0 -0
  88. /package/dist/{components → shared/components}/ui/context-menu/ContextMenuContent.svelte.d.ts +0 -0
  89. /package/dist/{components → shared/components}/ui/context-menu/ContextMenuItem.svelte.d.ts +0 -0
  90. /package/dist/{components → shared/components}/ui/context-menu/ContextMenuSeparator.svelte +0 -0
  91. /package/dist/{components → shared/components}/ui/context-menu/ContextMenuSeparator.svelte.d.ts +0 -0
  92. /package/dist/{components → shared/components}/ui/context-menu/ContextMenuTrigger.svelte +0 -0
  93. /package/dist/{components → shared/components}/ui/context-menu/ContextMenuTrigger.svelte.d.ts +0 -0
  94. /package/dist/{components → shared/components}/ui/context-menu/context-menu-state.svelte.d.ts +0 -0
  95. /package/dist/{components → shared/components}/ui/context-menu/context-menu-state.svelte.js +0 -0
  96. /package/dist/{components → shared/components}/ui/drag-drop/Draggable.svelte +0 -0
  97. /package/dist/{components → shared/components}/ui/drag-drop/Draggable.svelte.d.ts +0 -0
  98. /package/dist/{components → shared/components}/ui/drag-drop/Dropzone.svelte +0 -0
  99. /package/dist/{components → shared/components}/ui/drag-drop/Dropzone.svelte.d.ts +0 -0
  100. /package/dist/{components → shared/components}/ui/drag-drop/drag-manager.d.ts +0 -0
  101. /package/dist/{components → shared/components}/ui/drag-drop/drag-manager.js +0 -0
  102. /package/dist/{components → shared/components}/ui/index.d.ts +0 -0
  103. /package/dist/{components → shared/components}/ui/index.js +0 -0
  104. /package/dist/{components → shared/components}/ui/search/SearchBar.svelte.d.ts +0 -0
  105. /package/dist/{server → shared/server}/form-handler.d.ts +0 -0
  106. /package/dist/{server → shared/server}/form-handler.js +0 -0
  107. /package/dist/{server → shared/server}/form-processor.d.ts +0 -0
  108. /package/dist/{server → shared/server}/form-processor.js +0 -0
  109. /package/dist/{utils → shared/utils}/email/index.d.ts +0 -0
  110. /package/dist/{utils → shared/utils}/email/index.js +0 -0
  111. /package/dist/{utils → shared/utils}/email/ses.d.ts +0 -0
  112. /package/dist/{utils → shared/utils}/form/index.d.ts +0 -0
  113. /package/dist/{utils → shared/utils}/form/index.js +0 -0
  114. /package/dist/{utils → shared/utils}/form/virtual-form.d.ts +0 -0
  115. /package/dist/{utils → shared/utils}/form/virtual-form.js +0 -0
  116. /package/dist/{highlight.d.ts → utils/highlight.d.ts} +0 -0
  117. /package/dist/{highlight.js → utils/highlight.js} +0 -0
@@ -0,0 +1,768 @@
1
+ import { CalendarDate, CalendarDateTime, Time, ZonedDateTime } from '@internationalized/date';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { checkOverlap, dateDiffWeeks, datesWithin, formatAbsolute, formatDateFull, formatDateISO, formatDateNum, formatDateShort, formatDayLetter, formatDayShort, formatMonth, formatTimeEnd, formatTimeFull, formatTimeShort, getDayIndex, getDayOfDate, getLastDateOfDay, getLastDatesOfDay, getLastMonths, getNextDateOfDay, isDateDay, LocalDateF } from './index.js';
4
+ const SERVER_TIME_ZONE = 'Europe/London';
5
+ const dt = new LocalDateF(SERVER_TIME_ZONE);
6
+ describe('getDayIndex', () => {
7
+ it('returns the correct day index (0 = Monday)', () => {
8
+ // Test specific dates with known day indices
9
+ expect(getDayIndex(new CalendarDate(2023, 5, 1))).toBe(0); // May 1, 2023 was a Monday
10
+ expect(getDayIndex(new CalendarDate(2023, 5, 2))).toBe(1); // May 2, 2023 was a Tuesday
11
+ expect(getDayIndex(new CalendarDate(2023, 5, 3))).toBe(2); // May 3, 2023 was a Wednesday
12
+ expect(getDayIndex(new CalendarDate(2023, 5, 4))).toBe(3); // May 4, 2023 was a Thursday
13
+ expect(getDayIndex(new CalendarDate(2023, 5, 5))).toBe(4); // May 5, 2023 was a Friday
14
+ expect(getDayIndex(new CalendarDate(2023, 5, 6))).toBe(5); // May 6, 2023 was a Saturday
15
+ expect(getDayIndex(new CalendarDate(2023, 5, 7))).toBe(6); // May 7, 2023 was a Sunday
16
+ });
17
+ });
18
+ describe('getDayOfDate', () => {
19
+ it('returns the correct day of the week (0 = Monday)', () => {
20
+ // Test specific dates with known days of the week
21
+ expect(getDayOfDate(new CalendarDate(2023, 5, 1))).toBe('Monday'); // May 1, 2023 was a Monday
22
+ expect(getDayOfDate(new CalendarDate(2023, 5, 2))).toBe('Tuesday'); // May 2, 2023 was a Tuesday
23
+ expect(getDayOfDate(new CalendarDate(2023, 5, 3))).toBe('Wednesday'); // May 3, 2023 was a Wednesday
24
+ expect(getDayOfDate(new CalendarDate(2023, 5, 4))).toBe('Thursday'); // May 4, 2023 was a Thursday
25
+ expect(getDayOfDate(new CalendarDate(2023, 5, 5))).toBe('Friday'); // May 5, 2023 was a Friday
26
+ expect(getDayOfDate(new CalendarDate(2023, 5, 6))).toBe('Saturday'); // May 6, 2023 was a Saturday
27
+ expect(getDayOfDate(new CalendarDate(2023, 5, 7))).toBe('Sunday'); // May 7, 2023 was a Sunday
28
+ });
29
+ it('handles dates across different months and years', () => {
30
+ expect(getDayOfDate(new CalendarDate(2023, 12, 25))).toBe('Monday'); // December 25, 2023 was a Monday
31
+ expect(getDayOfDate(new CalendarDate(2024, 1, 1))).toBe('Monday'); // January 1, 2024 was a Monday
32
+ expect(getDayOfDate(new CalendarDate(2024, 2, 29))).toBe('Thursday'); // February 29, 2024 (leap year) was a Thursday
33
+ });
34
+ });
35
+ describe('isDateDay', () => {
36
+ it('correctly identifies when a date matches a specific day of week', () => {
37
+ // Using known dates for testing specific days
38
+ expect(isDateDay(new CalendarDate(2023, 5, 1), 'Monday')).toBe(true);
39
+ expect(isDateDay(new CalendarDate(2023, 5, 2), 'Tuesday')).toBe(true);
40
+ expect(isDateDay(new CalendarDate(2023, 5, 3), 'Wednesday')).toBe(true);
41
+ expect(isDateDay(new CalendarDate(2023, 5, 4), 'Thursday')).toBe(true);
42
+ expect(isDateDay(new CalendarDate(2023, 5, 5), 'Friday')).toBe(true);
43
+ expect(isDateDay(new CalendarDate(2023, 5, 6), 'Saturday')).toBe(true);
44
+ expect(isDateDay(new CalendarDate(2023, 5, 7), 'Sunday')).toBe(true);
45
+ });
46
+ it('correctly identifies when a date does not match a specific day of week', () => {
47
+ // May 1, 2023 was a Monday, check against all other days
48
+ const testDate = new CalendarDate(2023, 5, 1);
49
+ expect(isDateDay(testDate, 'Tuesday')).toBe(false);
50
+ expect(isDateDay(testDate, 'Wednesday')).toBe(false);
51
+ expect(isDateDay(testDate, 'Thursday')).toBe(false);
52
+ expect(isDateDay(testDate, 'Friday')).toBe(false);
53
+ expect(isDateDay(testDate, 'Saturday')).toBe(false);
54
+ expect(isDateDay(testDate, 'Sunday')).toBe(false);
55
+ });
56
+ it('handles dates across year boundaries', () => {
57
+ // December 31, 2023 was a Sunday
58
+ expect(isDateDay(new CalendarDate(2023, 12, 31), 'Sunday')).toBe(true);
59
+ // January 1, 2024 was a Monday
60
+ expect(isDateDay(new CalendarDate(2024, 1, 1), 'Monday')).toBe(true);
61
+ });
62
+ it('handles leap year dates correctly', () => {
63
+ // February 29, 2024 was a Thursday
64
+ expect(isDateDay(new CalendarDate(2024, 2, 29), 'Thursday')).toBe(true);
65
+ expect(isDateDay(new CalendarDate(2024, 2, 29), 'Friday')).toBe(false);
66
+ });
67
+ });
68
+ describe('isDateToday', () => {
69
+ beforeEach(() => {
70
+ // Mock current date to 2024-05-15
71
+ const currentDate = new CalendarDate(2024, 5, 15);
72
+ vi.useFakeTimers();
73
+ vi.setSystemTime(currentDate.toDate(SERVER_TIME_ZONE));
74
+ });
75
+ afterEach(() => {
76
+ vi.useRealTimers();
77
+ });
78
+ it('returns true when date is today', () => {
79
+ const today = new CalendarDate(2024, 5, 15);
80
+ expect(dt.isDateToday(today)).toBe(true);
81
+ });
82
+ it('returns false when date is in the past', () => {
83
+ const yesterday = new CalendarDate(2024, 5, 14);
84
+ expect(dt.isDateToday(yesterday)).toBe(false);
85
+ const lastMonth = new CalendarDate(2024, 4, 15);
86
+ expect(dt.isDateToday(lastMonth)).toBe(false);
87
+ const lastYear = new CalendarDate(2023, 5, 15);
88
+ expect(dt.isDateToday(lastYear)).toBe(false);
89
+ });
90
+ it('returns false when date is in the future', () => {
91
+ const tomorrow = new CalendarDate(2024, 5, 16);
92
+ expect(dt.isDateToday(tomorrow)).toBe(false);
93
+ const nextMonth = new CalendarDate(2024, 6, 15);
94
+ expect(dt.isDateToday(nextMonth)).toBe(false);
95
+ const nextYear = new CalendarDate(2025, 5, 15);
96
+ expect(dt.isDateToday(nextYear)).toBe(false);
97
+ });
98
+ it('handles date comparison at day boundaries', () => {
99
+ // Still the same day regardless of time of day
100
+ vi.setSystemTime(new CalendarDateTime(2024, 5, 15, 0, 0, 1).toDate(SERVER_TIME_ZONE));
101
+ expect(dt.isDateToday(new CalendarDate(2024, 5, 15))).toBe(true);
102
+ vi.setSystemTime(new CalendarDateTime(2024, 5, 15, 23, 59, 59).toDate(SERVER_TIME_ZONE));
103
+ expect(dt.isDateToday(new CalendarDate(2024, 5, 15))).toBe(true);
104
+ });
105
+ });
106
+ describe('ageFromDob', () => {
107
+ beforeEach(() => {
108
+ // Mock current date to 2025-03-30
109
+ const mockDate = new CalendarDate(2025, 3, 30);
110
+ vi.useFakeTimers();
111
+ vi.setSystemTime(mockDate.toDate(SERVER_TIME_ZONE));
112
+ });
113
+ afterEach(() => {
114
+ vi.useRealTimers();
115
+ });
116
+ it('should calculate age correctly for a normal case', () => {
117
+ const dob = new CalendarDate(2000, 3, 30);
118
+ expect(dt.ageFromDob(dob)).toBe(25);
119
+ });
120
+ it('should calculate age correctly when birthday is today', () => {
121
+ const dob = new CalendarDate(2000, 3, 30);
122
+ expect(dt.ageFromDob(dob)).toBe(25);
123
+ });
124
+ it('should calculate age correctly for someone born yesterday', () => {
125
+ const dob = new CalendarDate(2000, 3, 29);
126
+ expect(dt.ageFromDob(dob)).toBe(25);
127
+ });
128
+ it('should calculate age correctly for someone born tomorrow', () => {
129
+ const dob = new CalendarDate(2000, 3, 31);
130
+ expect(dt.ageFromDob(dob)).toBe(24);
131
+ });
132
+ it('should handle leap year birthdays', () => {
133
+ const dob = new CalendarDate(2000, 2, 29);
134
+ expect(dt.ageFromDob(dob)).toBe(25);
135
+ });
136
+ it('should return undefined for future dates', () => {
137
+ const futureDob = new CalendarDate(2026, 1, 1);
138
+ expect(() => dt.ageFromDob(futureDob)).toThrow();
139
+ });
140
+ it('should handle month boundary cases', () => {
141
+ const dob = new CalendarDate(2000, 2, 28);
142
+ expect(dt.ageFromDob(dob)).toBe(25);
143
+ });
144
+ it('should handle year boundary cases', () => {
145
+ const dob = new CalendarDate(2000, 12, 31);
146
+ expect(dt.ageFromDob(dob)).toBe(24);
147
+ });
148
+ });
149
+ describe('getNextDateOfDay', () => {
150
+ it('returns the next date within the week', () => {
151
+ const date = new CalendarDate(2025, 8, 11); // Monday
152
+ const nextTuesday = getNextDateOfDay('Tuesday', date);
153
+ expect(formatDateISO(nextTuesday)).toBe('2025-08-12');
154
+ const nextSunday = getNextDateOfDay('Sunday', date);
155
+ expect(formatDateISO(nextSunday)).toBe('2025-08-17');
156
+ });
157
+ it('returns the next date crossing over', () => {
158
+ const date = new CalendarDate(2025, 8, 14); // Thursday
159
+ const nextMonday = getNextDateOfDay('Monday', date);
160
+ expect(formatDateISO(nextMonday)).toBe('2025-08-18');
161
+ const nextWednesday = getNextDateOfDay('Wednesday', date);
162
+ expect(formatDateISO(nextWednesday)).toBe('2025-08-20');
163
+ });
164
+ });
165
+ describe('getLastDateOfDay', () => {
166
+ const startDate = new CalendarDate(2024, 4, 10);
167
+ it('returns today when it matches the requested day', () => {
168
+ // Wednesday is day 2
169
+ expect(formatDateISO(getLastDateOfDay('Wednesday', startDate))).toBe('2024-04-10');
170
+ });
171
+ it('returns the most recent occurrence of the requested day', () => {
172
+ // Most recent Monday (day 0) was 2 days ago
173
+ expect(formatDateISO(getLastDateOfDay('Monday', startDate))).toBe('2024-04-08');
174
+ // Most recent Tuesday (day 1) was 1 day ago
175
+ expect(formatDateISO(getLastDateOfDay('Tuesday', startDate))).toBe('2024-04-09');
176
+ // Most recent Thursday (day 3) was 6 days ago
177
+ expect(formatDateISO(getLastDateOfDay('Thursday', startDate))).toBe('2024-04-04');
178
+ });
179
+ it('handles week boundaries correctly', () => {
180
+ // Most recent Sunday (day 6) was 3 days ago
181
+ expect(formatDateISO(getLastDateOfDay('Sunday', startDate))).toBe('2024-04-07');
182
+ });
183
+ });
184
+ describe('getLastDatesOfDay', () => {
185
+ const startDate = new CalendarDate(2024, 4, 10);
186
+ it('returns the most recent date for a given day of the week', () => {
187
+ // If today is Wednesday (day 2), the most recent Monday (day 0) was 2 days ago
188
+ const mondayDates = getLastDatesOfDay('Monday', 1, startDate);
189
+ expect(mondayDates).toHaveLength(1);
190
+ expect(formatDateISO(mondayDates[0])).toBe('2024-04-08');
191
+ // The most recent Tuesday (day 1) was 1 day ago
192
+ const tuesdayDates = getLastDatesOfDay('Tuesday', 1, startDate);
193
+ expect(tuesdayDates).toHaveLength(1);
194
+ expect(formatDateISO(tuesdayDates[0])).toBe('2024-04-09');
195
+ // The most recent Wednesday (day 2) is today
196
+ const wednesdayDates = getLastDatesOfDay('Wednesday', 1, startDate);
197
+ expect(wednesdayDates).toHaveLength(1);
198
+ expect(formatDateISO(wednesdayDates[0])).toBe('2024-04-10');
199
+ });
200
+ it('returns multiple dates when count > 1', () => {
201
+ // Get the last 3 Mondays (ordered from oldest to most recent)
202
+ const mondayDates = getLastDatesOfDay('Monday', 3, startDate);
203
+ expect(mondayDates).toHaveLength(3);
204
+ expect(formatDateISO(mondayDates[0])).toBe('2024-03-25'); // Oldest Monday
205
+ expect(formatDateISO(mondayDates[1])).toBe('2024-04-01'); // Middle Monday
206
+ expect(formatDateISO(mondayDates[2])).toBe('2024-04-08'); // Most recent Monday
207
+ });
208
+ it('returns empty array when count is 0 or negative', () => {
209
+ expect(getLastDatesOfDay('Monday', 0, startDate)).toEqual([]);
210
+ expect(getLastDatesOfDay('Monday', -1, startDate)).toEqual([]);
211
+ });
212
+ it('handles week boundaries correctly when looking back', () => {
213
+ // If today is Wednesday (day 2), the most recent Sunday (day 6) was 3 days ago
214
+ const sundayDates = getLastDatesOfDay('Sunday', 2, startDate);
215
+ expect(sundayDates).toHaveLength(2);
216
+ expect(formatDateISO(sundayDates[0])).toBe('2024-03-31'); // Previous Sunday (oldest)
217
+ expect(formatDateISO(sundayDates[1])).toBe('2024-04-07'); // Most recent Sunday
218
+ });
219
+ });
220
+ describe('getLastMonths', () => {
221
+ const startDate = new CalendarDate(2024, 5, 15);
222
+ it('should return the correct first day of the last 3 months', () => {
223
+ const months = getLastMonths(3, startDate);
224
+ expect(months).toHaveLength(3);
225
+ expect(formatDateISO(months[0])).toBe('2024-03-01');
226
+ expect(formatDateISO(months[1])).toBe('2024-04-01');
227
+ expect(formatDateISO(months[2])).toBe('2024-05-01');
228
+ });
229
+ it('should return the correct first day of the last month', () => {
230
+ const months = getLastMonths(1, startDate);
231
+ expect(months).toHaveLength(1);
232
+ expect(formatDateISO(months[0])).toBe('2024-05-01');
233
+ });
234
+ it('should handle year boundaries correctly', () => {
235
+ // Mock current date to 2024-02-15
236
+ const startDate = new CalendarDate(2024, 2, 15);
237
+ const months = getLastMonths(4, startDate);
238
+ expect(months).toHaveLength(4);
239
+ expect(formatDateISO(months[0])).toBe('2023-11-01');
240
+ expect(formatDateISO(months[1])).toBe('2023-12-01');
241
+ expect(formatDateISO(months[2])).toBe('2024-01-01');
242
+ expect(formatDateISO(months[3])).toBe('2024-02-01');
243
+ });
244
+ it('should return an empty array when count is 0', () => {
245
+ const months = getLastMonths(0, startDate);
246
+ expect(months).toEqual([]);
247
+ });
248
+ it('should return an empty array when count is negative', () => {
249
+ const months = getLastMonths(-2, startDate);
250
+ expect(months).toEqual([]);
251
+ });
252
+ it('should handle the start of a month correctly', () => {
253
+ const startDate = new CalendarDate(2023, 8, 1);
254
+ const months = getLastMonths(3, startDate);
255
+ expect(months).toHaveLength(3);
256
+ expect(formatDateISO(months[0])).toBe('2023-06-01');
257
+ expect(formatDateISO(months[1])).toBe('2023-07-01');
258
+ expect(formatDateISO(months[2])).toBe('2023-08-01');
259
+ });
260
+ it('should handle the end of a month correctly', () => {
261
+ // Mock current date to 2024-03-31
262
+ const startDate = new CalendarDate(2024, 3, 31);
263
+ const months = getLastMonths(2, startDate);
264
+ expect(months).toHaveLength(2);
265
+ expect(formatDateISO(months[0])).toBe('2024-02-01');
266
+ expect(formatDateISO(months[1])).toBe('2024-03-01');
267
+ });
268
+ });
269
+ describe('checkOverlap', () => {
270
+ // Helper Times
271
+ const t0900 = new Time(9, 0);
272
+ const t1000 = new Time(10, 0);
273
+ const t1100 = new Time(11, 0);
274
+ const t1200 = new Time(12, 0);
275
+ const t0000 = new Time(0, 0);
276
+ const t2359 = new Time(23, 59);
277
+ // 1. No Overlap
278
+ it('should return false when Range 1 is strictly before Range 2', () => {
279
+ // R1: 09:00 - 10:00, R2: 11:00 - 12:00
280
+ expect(checkOverlap(t0900, t1000, t1100, t1200)).toBe(false);
281
+ });
282
+ it('should return false when Range 1 is strictly after Range 2', () => {
283
+ // R1: 11:00 - 12:00, R2: 09:00 - 10:00
284
+ expect(checkOverlap(t1100, t1200, t0900, t1000)).toBe(false);
285
+ });
286
+ // 2. Partial Overlap
287
+ it('should return true when Range 1 starts before and ends within Range 2', () => {
288
+ // R1: 09:00 - 11:00, R2: 10:00 - 12:00
289
+ expect(checkOverlap(t0900, t1100, t1000, t1200)).toBe(true);
290
+ });
291
+ it('should return true when Range 2 starts before and ends within Range 1', () => {
292
+ // R1: 10:00 - 12:00, R2: 09:00 - 11:00
293
+ expect(checkOverlap(t1000, t1200, t0900, t1100)).toBe(true);
294
+ });
295
+ // 3. Complete Overlap
296
+ it('should return true when Range 2 is fully contained within Range 1 (subset)', () => {
297
+ // R1: 09:00 - 12:00, R2: 10:00 - 11:00
298
+ expect(checkOverlap(t0900, t1200, t1000, t1100)).toBe(true);
299
+ });
300
+ it('should return true when Range 1 is fully contained within Range 2 (superset)', () => {
301
+ // R1: 10:00 - 11:00, R2: 09:00 - 12:00
302
+ expect(checkOverlap(t1000, t1100, t0900, t1200)).toBe(true);
303
+ });
304
+ // 4. Boundary Conditions (Touching - not considered overlapping)
305
+ it('should return false when end time of Range 1 equals start time of Range 2', () => {
306
+ // R1: 09:00 - 10:00, R2: 10:00 - 11:00
307
+ expect(checkOverlap(t0900, t1000, t1000, t1100)).toBe(false);
308
+ });
309
+ it('should return false when start time of Range 1 equals end time of Range 2', () => {
310
+ // R1: 10:00 - 11:00, R2: 09:00 - 10:00
311
+ expect(checkOverlap(t1000, t1100, t0900, t1000)).toBe(false);
312
+ });
313
+ // 5. Identical Ranges
314
+ it('should return true when ranges are identical', () => {
315
+ // R1: 10:00 - 11:00, R2: 10:00 - 11:00
316
+ expect(checkOverlap(t1000, t1100, t1000, t1100)).toBe(true);
317
+ });
318
+ // 6. Edge Cases
319
+ it('should handle overlap involving the earliest time (00:00)', () => {
320
+ // R1: 00:00 - 09:00, R2: 08:00 - 10:00
321
+ expect(checkOverlap(t0000, t0900, new Time(8, 0), t1000)).toBe(true);
322
+ // R1: 00:00 - 01:00, R2: 01:00 - 02:00 (Boundary)
323
+ expect(checkOverlap(t0000, new Time(1, 0), new Time(1, 0), new Time(2, 0))).toBe(false);
324
+ // R1: 00:00 - 01:00, R2: 02:00 - 03:00 (No overlap)
325
+ expect(checkOverlap(t0000, new Time(1, 0), new Time(2, 0), new Time(3, 0))).toBe(false);
326
+ });
327
+ it('should handle overlap involving the latest time (23:59)', () => {
328
+ // R1: 22:00 - 23:59, R2: 23:00 - 23:59
329
+ expect(checkOverlap(new Time(22, 0), t2359, new Time(23, 0), t2359)).toBe(true);
330
+ // R1: 23:00 - 23:59, R2: 22:00 - 23:00 (Boundary)
331
+ expect(checkOverlap(new Time(23, 0), t2359, new Time(22, 0), new Time(23, 0))).toBe(false);
332
+ // R1: 23:00 - 23:59, R2: 21:00 - 22:00 (No overlap)
333
+ expect(checkOverlap(new Time(23, 0), t2359, new Time(21, 0), new Time(22, 0))).toBe(false);
334
+ });
335
+ it('should handle zero-duration ranges', () => {
336
+ // Zero-duration R1 touching start of R2
337
+ // R1: 10:00 - 10:00, R2: 10:00 - 11:00
338
+ expect(checkOverlap(t1000, t1000, t1000, t1100)).toBe(false);
339
+ // Zero-duration R1 touching end of R2
340
+ // R1: 11:00 - 11:00, R2: 10:00 - 11:00
341
+ expect(checkOverlap(t1100, t1100, t1000, t1100)).toBe(false);
342
+ // Zero-duration R1 inside R2
343
+ // R1: 10:30 - 10:30, R2: 10:00 - 11:00
344
+ const t1030 = new Time(10, 30);
345
+ expect(checkOverlap(t1030, t1030, t1000, t1100)).toBe(true);
346
+ // Zero-duration R1 outside R2
347
+ // R1: 09:00 - 09:00, R2: 10:00 - 11:00
348
+ expect(checkOverlap(t0900, t0900, t1000, t1100)).toBe(false);
349
+ // Two zero-duration ranges at the same time
350
+ // R1: 10:00 - 10:00, R2: 10:00 - 10:00
351
+ expect(checkOverlap(t1000, t1000, t1000, t1000)).toBe(false);
352
+ // Two zero-duration ranges at different times
353
+ // R1: 09:00 - 09:00, R2: 10:00 - 10:00
354
+ expect(checkOverlap(t0900, t0900, t1000, t1000)).toBe(false);
355
+ });
356
+ });
357
+ describe('datesWithin', () => {
358
+ it('should return true when dates are within the specified duration', () => {
359
+ const date1 = new CalendarDate(2024, 5, 1);
360
+ const date2 = new CalendarDate(2024, 5, 3);
361
+ // 3 days duration should include the end date
362
+ expect(datesWithin(date1, date2, { days: 3 })).toBe(true);
363
+ expect(datesWithin(date1, date2, { days: 2 })).toBe(true);
364
+ });
365
+ it('should return false when dates are outside the specified duration', () => {
366
+ const date1 = new CalendarDate(2024, 5, 1);
367
+ const date2 = new CalendarDate(2024, 5, 5);
368
+ // 3 days duration should not include date 4 days later
369
+ expect(datesWithin(date1, date2, { days: 3 })).toBe(false);
370
+ expect(datesWithin(date1, date2, { days: 1 })).toBe(false);
371
+ });
372
+ it('should handle same date as within duration', () => {
373
+ const date = new CalendarDate(2024, 5, 1);
374
+ expect(datesWithin(date, date, { days: 0 })).toBe(true);
375
+ expect(datesWithin(date, date, { days: 1 })).toBe(true);
376
+ });
377
+ it('should return false when date1 is after date2', () => {
378
+ const date1 = new CalendarDate(2024, 5, 5);
379
+ const date2 = new CalendarDate(2024, 5, 1);
380
+ expect(datesWithin(date1, date2, { days: 10 })).toBe(false);
381
+ });
382
+ it('should handle week-based durations', () => {
383
+ const date1 = new CalendarDate(2024, 5, 1);
384
+ const date2 = new CalendarDate(2024, 5, 8); // 7 days later
385
+ expect(datesWithin(date1, date2, { weeks: 1 })).toBe(true);
386
+ expect(datesWithin(date1, date2, { days: 7 })).toBe(true);
387
+ const date3 = new CalendarDate(2024, 5, 15); // 14 days later
388
+ expect(datesWithin(date1, date3, { weeks: 2 })).toBe(true);
389
+ expect(datesWithin(date1, date3, { weeks: 1 })).toBe(false);
390
+ });
391
+ it('should handle month-based durations', () => {
392
+ const date1 = new CalendarDate(2024, 5, 1);
393
+ const date2 = new CalendarDate(2024, 6, 1); // 1 month later
394
+ expect(datesWithin(date1, date2, { months: 1 })).toBe(true);
395
+ expect(datesWithin(date1, date2, { days: 30 })).toBe(false); // May has 31 days
396
+ const date3 = new CalendarDate(2024, 7, 1); // 2 months later
397
+ expect(datesWithin(date1, date3, { months: 2 })).toBe(true);
398
+ expect(datesWithin(date1, date3, { months: 1 })).toBe(false);
399
+ });
400
+ it('should handle year-based durations', () => {
401
+ const date1 = new CalendarDate(2024, 5, 1);
402
+ const date2 = new CalendarDate(2025, 5, 1); // 1 year later
403
+ expect(datesWithin(date1, date2, { years: 1 })).toBe(true);
404
+ expect(datesWithin(date1, date2, { months: 12 })).toBe(true);
405
+ const date3 = new CalendarDate(2026, 5, 1); // 2 years later
406
+ expect(datesWithin(date1, date3, { years: 2 })).toBe(true);
407
+ expect(datesWithin(date1, date3, { years: 1 })).toBe(false);
408
+ });
409
+ it('should handle boundary conditions', () => {
410
+ const date1 = new CalendarDate(2024, 5, 1);
411
+ const date2 = new CalendarDate(2024, 5, 2); // exactly 1 day later
412
+ expect(datesWithin(date1, date2, { days: 1 })).toBe(true);
413
+ const date3 = new CalendarDate(2024, 5, 1);
414
+ const date4 = new CalendarDate(2024, 5, 1); // same day
415
+ expect(datesWithin(date3, date4, { days: 0 })).toBe(true);
416
+ });
417
+ });
418
+ describe('dateDiffWeeks', () => {
419
+ it('should calculate the difference in weeks between two dates', () => {
420
+ const date1 = new CalendarDate(2024, 5, 1);
421
+ const date2 = new CalendarDate(2024, 5, 15);
422
+ expect(dateDiffWeeks(date1, date2)).toBe(2);
423
+ const date3 = new CalendarDate(2024, 5, 1);
424
+ const date4 = new CalendarDate(2024, 5, 8);
425
+ expect(dateDiffWeeks(date3, date4)).toBe(1);
426
+ const date5 = new CalendarDate(2024, 5, 1);
427
+ const date6 = new CalendarDate(2024, 5, 7);
428
+ expect(dateDiffWeeks(date5, date6)).toBe(0);
429
+ const date7 = new CalendarDate(2024, 5, 1);
430
+ const date8 = new CalendarDate(2024, 5, 6);
431
+ expect(dateDiffWeeks(date7, date8)).toBe(0);
432
+ const date9 = new CalendarDate(2024, 5, 1);
433
+ const date10 = new CalendarDate(2024, 5, 2);
434
+ expect(dateDiffWeeks(date9, date10)).toBe(0);
435
+ });
436
+ });
437
+ describe('formatDayShort', () => {
438
+ it('should format all days of the week correctly', () => {
439
+ expect(formatDayShort('Monday')).toBe('Mon');
440
+ expect(formatDayShort('Tuesday')).toBe('Tue');
441
+ expect(formatDayShort('Wednesday')).toBe('Wed');
442
+ expect(formatDayShort('Thursday')).toBe('Thu');
443
+ expect(formatDayShort('Friday')).toBe('Fri');
444
+ expect(formatDayShort('Saturday')).toBe('Sat');
445
+ expect(formatDayShort('Sunday')).toBe('Sun');
446
+ });
447
+ });
448
+ describe('formatDayLetter', () => {
449
+ it('should format all days of the week correctly', () => {
450
+ expect(formatDayLetter('Monday')).toBe('M');
451
+ expect(formatDayLetter('Tuesday')).toBe('T');
452
+ expect(formatDayLetter('Wednesday')).toBe('W');
453
+ expect(formatDayLetter('Thursday')).toBe('T');
454
+ expect(formatDayLetter('Friday')).toBe('F');
455
+ expect(formatDayLetter('Saturday')).toBe('S');
456
+ expect(formatDayLetter('Sunday')).toBe('S');
457
+ });
458
+ });
459
+ describe('formatDateShort', () => {
460
+ it('formats date in DD MMM format', () => {
461
+ // Different dates to test various month and day combinations
462
+ expect(formatDateShort(new CalendarDate(2023, 1, 5))).toBe('5 Jan');
463
+ expect(formatDateShort(new CalendarDate(2023, 5, 15))).toBe('15 May');
464
+ expect(formatDateShort(new CalendarDate(2023, 12, 25))).toBe('25 Dec');
465
+ });
466
+ it('handles single-digit days correctly', () => {
467
+ expect(formatDateShort(new CalendarDate(2023, 7, 1))).toBe('1 Jul');
468
+ expect(formatDateShort(new CalendarDate(2023, 9, 9))).toBe('9 Sept');
469
+ });
470
+ it('handles double-digit days correctly', () => {
471
+ expect(formatDateShort(new CalendarDate(2023, 3, 10))).toBe('10 Mar');
472
+ expect(formatDateShort(new CalendarDate(2023, 11, 30))).toBe('30 Nov');
473
+ });
474
+ it('formats February correctly', () => {
475
+ expect(formatDateShort(new CalendarDate(2023, 2, 14))).toBe('14 Feb');
476
+ expect(formatDateShort(new CalendarDate(2024, 2, 29))).toBe('29 Feb'); // Leap year
477
+ });
478
+ });
479
+ describe('formatDateFull', () => {
480
+ it('formats date in DD MMM YYYY format', () => {
481
+ // Test various dates to ensure consistent formatting
482
+ expect(formatDateFull(new CalendarDate(2023, 1, 5))).toBe('5 Jan 2023');
483
+ expect(formatDateFull(new CalendarDate(2023, 5, 15))).toBe('15 May 2023');
484
+ expect(formatDateFull(new CalendarDate(2023, 12, 25))).toBe('25 Dec 2023');
485
+ });
486
+ it('handles single-digit days correctly', () => {
487
+ expect(formatDateFull(new CalendarDate(2023, 7, 1))).toBe('1 Jul 2023');
488
+ expect(formatDateFull(new CalendarDate(2023, 9, 9))).toBe('9 Sept 2023');
489
+ });
490
+ it('handles double-digit days correctly', () => {
491
+ expect(formatDateFull(new CalendarDate(2023, 3, 10))).toBe('10 Mar 2023');
492
+ expect(formatDateFull(new CalendarDate(2023, 11, 30))).toBe('30 Nov 2023');
493
+ });
494
+ it('handles different years correctly', () => {
495
+ expect(formatDateFull(new CalendarDate(2020, 6, 15))).toBe('15 Jun 2020');
496
+ expect(formatDateFull(new CalendarDate(2024, 6, 15))).toBe('15 Jun 2024');
497
+ expect(formatDateFull(new CalendarDate(2025, 6, 15))).toBe('15 Jun 2025');
498
+ });
499
+ it('handles leap year dates correctly', () => {
500
+ expect(formatDateFull(new CalendarDate(2024, 2, 29))).toBe('29 Feb 2024');
501
+ expect(formatDateFull(new CalendarDate(2020, 2, 29))).toBe('29 Feb 2020');
502
+ });
503
+ it('handles year boundaries correctly', () => {
504
+ expect(formatDateFull(new CalendarDate(2023, 12, 31))).toBe('31 Dec 2023');
505
+ expect(formatDateFull(new CalendarDate(2024, 1, 1))).toBe('1 Jan 2024');
506
+ });
507
+ it('handles all months correctly', () => {
508
+ expect(formatDateFull(new CalendarDate(2023, 1, 15))).toBe('15 Jan 2023');
509
+ expect(formatDateFull(new CalendarDate(2023, 2, 15))).toBe('15 Feb 2023');
510
+ expect(formatDateFull(new CalendarDate(2023, 3, 15))).toBe('15 Mar 2023');
511
+ expect(formatDateFull(new CalendarDate(2023, 4, 15))).toBe('15 Apr 2023');
512
+ expect(formatDateFull(new CalendarDate(2023, 5, 15))).toBe('15 May 2023');
513
+ expect(formatDateFull(new CalendarDate(2023, 6, 15))).toBe('15 Jun 2023');
514
+ expect(formatDateFull(new CalendarDate(2023, 7, 15))).toBe('15 Jul 2023');
515
+ expect(formatDateFull(new CalendarDate(2023, 8, 15))).toBe('15 Aug 2023');
516
+ expect(formatDateFull(new CalendarDate(2023, 9, 15))).toBe('15 Sept 2023');
517
+ expect(formatDateFull(new CalendarDate(2023, 10, 15))).toBe('15 Oct 2023');
518
+ expect(formatDateFull(new CalendarDate(2023, 11, 15))).toBe('15 Nov 2023');
519
+ expect(formatDateFull(new CalendarDate(2023, 12, 15))).toBe('15 Dec 2023');
520
+ });
521
+ });
522
+ describe('formatDateISO', () => {
523
+ it('formats date in YYYY-MM-DD format', () => {
524
+ // Test various dates to ensure consistent ISO format
525
+ expect(formatDateISO(new CalendarDate(2023, 1, 5))).toBe('2023-01-05');
526
+ expect(formatDateISO(new CalendarDate(2023, 5, 15))).toBe('2023-05-15');
527
+ expect(formatDateISO(new CalendarDate(2023, 12, 25))).toBe('2023-12-25');
528
+ });
529
+ it('handles single-digit days with zero padding', () => {
530
+ expect(formatDateISO(new CalendarDate(2023, 7, 1))).toBe('2023-07-01');
531
+ expect(formatDateISO(new CalendarDate(2023, 9, 9))).toBe('2023-09-09');
532
+ });
533
+ it('handles single-digit months with zero padding', () => {
534
+ expect(formatDateISO(new CalendarDate(2023, 1, 15))).toBe('2023-01-15');
535
+ expect(formatDateISO(new CalendarDate(2023, 9, 15))).toBe('2023-09-15');
536
+ });
537
+ it('handles double-digit days and months correctly', () => {
538
+ expect(formatDateISO(new CalendarDate(2023, 10, 10))).toBe('2023-10-10');
539
+ expect(formatDateISO(new CalendarDate(2023, 11, 30))).toBe('2023-11-30');
540
+ });
541
+ it('handles different years correctly', () => {
542
+ expect(formatDateISO(new CalendarDate(2020, 6, 15))).toBe('2020-06-15');
543
+ expect(formatDateISO(new CalendarDate(2024, 6, 15))).toBe('2024-06-15');
544
+ expect(formatDateISO(new CalendarDate(2025, 6, 15))).toBe('2025-06-15');
545
+ });
546
+ it('handles leap year dates correctly', () => {
547
+ expect(formatDateISO(new CalendarDate(2024, 2, 29))).toBe('2024-02-29');
548
+ expect(formatDateISO(new CalendarDate(2020, 2, 29))).toBe('2020-02-29');
549
+ });
550
+ it('handles year boundaries correctly', () => {
551
+ expect(formatDateISO(new CalendarDate(2023, 12, 31))).toBe('2023-12-31');
552
+ expect(formatDateISO(new CalendarDate(2024, 1, 1))).toBe('2024-01-01');
553
+ });
554
+ it('handles all months correctly with proper zero padding', () => {
555
+ expect(formatDateISO(new CalendarDate(2023, 1, 15))).toBe('2023-01-15');
556
+ expect(formatDateISO(new CalendarDate(2023, 2, 15))).toBe('2023-02-15');
557
+ expect(formatDateISO(new CalendarDate(2023, 3, 15))).toBe('2023-03-15');
558
+ expect(formatDateISO(new CalendarDate(2023, 4, 15))).toBe('2023-04-15');
559
+ expect(formatDateISO(new CalendarDate(2023, 5, 15))).toBe('2023-05-15');
560
+ expect(formatDateISO(new CalendarDate(2023, 6, 15))).toBe('2023-06-15');
561
+ expect(formatDateISO(new CalendarDate(2023, 7, 15))).toBe('2023-07-15');
562
+ expect(formatDateISO(new CalendarDate(2023, 8, 15))).toBe('2023-08-15');
563
+ expect(formatDateISO(new CalendarDate(2023, 9, 15))).toBe('2023-09-15');
564
+ expect(formatDateISO(new CalendarDate(2023, 10, 15))).toBe('2023-10-15');
565
+ expect(formatDateISO(new CalendarDate(2023, 11, 15))).toBe('2023-11-15');
566
+ expect(formatDateISO(new CalendarDate(2023, 12, 15))).toBe('2023-12-15');
567
+ });
568
+ it('handles edge cases for date ranges', () => {
569
+ // First day of year
570
+ expect(formatDateISO(new CalendarDate(2023, 1, 1))).toBe('2023-01-01');
571
+ // Last day of year
572
+ expect(formatDateISO(new CalendarDate(2023, 12, 31))).toBe('2023-12-31');
573
+ // February non-leap year
574
+ expect(formatDateISO(new CalendarDate(2023, 2, 28))).toBe('2023-02-28');
575
+ // February leap year
576
+ expect(formatDateISO(new CalendarDate(2024, 2, 29))).toBe('2024-02-29');
577
+ });
578
+ });
579
+ describe('formatDateNum', () => {
580
+ it('formats dates in number format', () => {
581
+ expect(formatDateNum(new CalendarDate(2023, 1, 1))).toBe('01/01/2023');
582
+ expect(formatDateNum(new CalendarDate(2023, 2, 15))).toBe('15/02/2023');
583
+ expect(formatDateNum(new CalendarDate(2023, 3, 20))).toBe('20/03/2023');
584
+ expect(formatDateNum(new CalendarDate(2023, 4, 25))).toBe('25/04/2023');
585
+ expect(formatDateNum(new CalendarDate(2023, 5, 30))).toBe('30/05/2023');
586
+ expect(formatDateNum(new CalendarDate(2023, 6, 5))).toBe('05/06/2023');
587
+ expect(formatDateNum(new CalendarDate(2023, 7, 10))).toBe('10/07/2023');
588
+ expect(formatDateNum(new CalendarDate(2023, 8, 15))).toBe('15/08/2023');
589
+ expect(formatDateNum(new CalendarDate(2023, 9, 20))).toBe('20/09/2023');
590
+ expect(formatDateNum(new CalendarDate(2023, 10, 25))).toBe('25/10/2023');
591
+ expect(formatDateNum(new CalendarDate(2023, 11, 30))).toBe('30/11/2023');
592
+ expect(formatDateNum(new CalendarDate(2023, 12, 5))).toBe('05/12/2023');
593
+ });
594
+ });
595
+ describe('formatMonth', () => {
596
+ it('should format month correctly for all months', () => {
597
+ expect(formatMonth(new CalendarDate(2023, 1, 1))).toBe('Jan 23');
598
+ expect(formatMonth(new CalendarDate(2023, 2, 1))).toBe('Feb 23');
599
+ expect(formatMonth(new CalendarDate(2023, 3, 1))).toBe('Mar 23');
600
+ expect(formatMonth(new CalendarDate(2023, 4, 1))).toBe('Apr 23');
601
+ expect(formatMonth(new CalendarDate(2023, 5, 1))).toBe('May 23');
602
+ expect(formatMonth(new CalendarDate(2023, 6, 1))).toBe('Jun 23');
603
+ expect(formatMonth(new CalendarDate(2023, 7, 1))).toBe('Jul 23');
604
+ expect(formatMonth(new CalendarDate(2023, 8, 1))).toBe('Aug 23');
605
+ expect(formatMonth(new CalendarDate(2023, 9, 1))).toBe('Sept 23');
606
+ expect(formatMonth(new CalendarDate(2023, 10, 1))).toBe('Oct 23');
607
+ expect(formatMonth(new CalendarDate(2023, 11, 1))).toBe('Nov 23');
608
+ expect(formatMonth(new CalendarDate(2023, 12, 1))).toBe('Dec 23');
609
+ });
610
+ it('should return the same month regardless of the day or year', () => {
611
+ expect(formatMonth(new CalendarDate(2023, 1, 31))).toBe('Jan 23');
612
+ expect(formatMonth(new CalendarDate(2024, 1, 1))).toBe('Jan 24');
613
+ });
614
+ });
615
+ describe('formatTimeShort', () => {
616
+ it('should format time correctly with 2-digit hours and 2-digit minutes', () => {
617
+ const time = new Time(14, 30);
618
+ expect(formatTimeShort(time)).toBe('14:30');
619
+ });
620
+ it('should pad single digit hours with leading zero', () => {
621
+ const time = new Time(9, 45);
622
+ expect(formatTimeShort(time)).toBe('09:45');
623
+ });
624
+ it('should pad single digit minutes with leading zero', () => {
625
+ const time = new Time(12, 5);
626
+ expect(formatTimeShort(time)).toBe('12:05');
627
+ });
628
+ it('should pad both single digit hours and minutes with leading zeros', () => {
629
+ const time = new Time(1, 7);
630
+ expect(formatTimeShort(time)).toBe('01:07');
631
+ });
632
+ it('should format midnight correctly', () => {
633
+ const time = new Time(0, 0);
634
+ expect(formatTimeShort(time)).toBe('00:00');
635
+ });
636
+ it('should format end of day correctly', () => {
637
+ const time = new Time(23, 59);
638
+ expect(formatTimeShort(time)).toBe('23:59');
639
+ });
640
+ });
641
+ describe('formatTimeFull', () => {
642
+ it('should format time correctly with hours, minutes, and seconds', () => {
643
+ const time = new Time(14, 30, 45);
644
+ expect(formatTimeFull(time)).toBe('14:30:45');
645
+ });
646
+ it('should pad single digit hours with leading zero', () => {
647
+ const time = new Time(9, 45, 30);
648
+ expect(formatTimeFull(time)).toBe('09:45:30');
649
+ });
650
+ it('should pad single digit minutes with leading zero', () => {
651
+ const time = new Time(12, 5, 30);
652
+ expect(formatTimeFull(time)).toBe('12:05:30');
653
+ });
654
+ it('should pad single digit seconds with leading zero', () => {
655
+ const time = new Time(12, 30, 5);
656
+ expect(formatTimeFull(time)).toBe('12:30:05');
657
+ });
658
+ it('should pad all single digits with leading zeros', () => {
659
+ const time = new Time(1, 7, 9);
660
+ expect(formatTimeFull(time)).toBe('01:07:09');
661
+ });
662
+ it('should format midnight correctly', () => {
663
+ const time = new Time(0, 0, 0);
664
+ expect(formatTimeFull(time)).toBe('00:00:00');
665
+ });
666
+ it('should format end of day correctly', () => {
667
+ const time = new Time(23, 59, 59);
668
+ expect(formatTimeFull(time)).toBe('23:59:59');
669
+ });
670
+ it('should handle times without seconds specified (defaults to 0)', () => {
671
+ const time = new Time(10, 30);
672
+ expect(formatTimeFull(time)).toBe('10:30:00');
673
+ });
674
+ it('should handle times with milliseconds (ignores milliseconds)', () => {
675
+ const time = new Time(15, 45, 30, 500);
676
+ expect(formatTimeFull(time)).toBe('15:45:30');
677
+ });
678
+ });
679
+ describe('formatTimeEnd', () => {
680
+ it('should format the end time correctly for a short duration', () => {
681
+ expect(formatTimeEnd(new Time(10, 0), 30)).toBe('10:30');
682
+ });
683
+ it('should handle hour rollover correctly', () => {
684
+ expect(formatTimeEnd(new Time(9, 45), 30)).toBe('10:15');
685
+ });
686
+ it('should handle day rollover correctly', () => {
687
+ expect(formatTimeEnd(new Time(23, 30), 60)).toBe('00:30');
688
+ });
689
+ it('should handle multiple hour additions', () => {
690
+ expect(formatTimeEnd(new Time(10, 0), 120)).toBe('12:00');
691
+ });
692
+ it('should handle zero duration', () => {
693
+ expect(formatTimeEnd(new Time(15, 30), 0)).toBe('15:30');
694
+ });
695
+ it('should handle large duration across multiple days', () => {
696
+ expect(formatTimeEnd(new Time(12, 0), 1440)).toBe('12:00'); // 24 hours = 1440 minutes
697
+ });
698
+ it('should pad times correctly', () => {
699
+ expect(formatTimeEnd(new Time(9, 5), 5)).toBe('09:10');
700
+ });
701
+ it('should handle minute overflow correctly', () => {
702
+ expect(formatTimeEnd(new Time(14, 55), 10)).toBe('15:05');
703
+ expect(formatTimeEnd(new Time(23, 55), 10)).toBe('00:05');
704
+ });
705
+ it('should handle exact hour boundaries', () => {
706
+ expect(formatTimeEnd(new Time(8, 0), 60)).toBe('09:00');
707
+ expect(formatTimeEnd(new Time(23, 0), 60)).toBe('00:00');
708
+ });
709
+ it('should handle negative durations by treating them as zero', () => {
710
+ // Note: This behavior depends on the Time.add implementation
711
+ // If it doesn't handle negative values, this test might need adjustment
712
+ expect(formatTimeEnd(new Time(10, 30), 0)).toBe('10:30');
713
+ });
714
+ it('should handle very large durations', () => {
715
+ // 25 hours = 1500 minutes, should wrap around to next day + 1 hour
716
+ expect(formatTimeEnd(new Time(10, 0), 1500)).toBe('11:00');
717
+ });
718
+ });
719
+ describe('formatAbsolute', () => {
720
+ it('should format a ZonedDateTime correctly', () => {
721
+ const datetime = new ZonedDateTime(2024, 5, 15, 'Europe/London', 0, 14, 30);
722
+ expect(formatAbsolute(datetime)).toBe('15/05/2024 14:30:00');
723
+ });
724
+ it('should handle midnight correctly', () => {
725
+ const datetime = new ZonedDateTime(2024, 1, 1, 'Europe/London', 0, 0, 0);
726
+ expect(formatAbsolute(datetime)).toBe('01/01/2024 00:00:00');
727
+ });
728
+ it('should handle end of day correctly', () => {
729
+ const datetime = new ZonedDateTime(2024, 12, 31, 'Europe/London', 0, 23, 59);
730
+ expect(formatAbsolute(datetime)).toBe('31/12/2024 23:59:00');
731
+ });
732
+ it('should pad single digit days and months', () => {
733
+ const datetime = new ZonedDateTime(2024, 2, 5, 'Europe/London', 0, 9, 7);
734
+ expect(formatAbsolute(datetime)).toBe('05/02/2024 09:07:00');
735
+ });
736
+ it('should handle leap year dates', () => {
737
+ const datetime = new ZonedDateTime(2024, 2, 29, 'Europe/London', 0, 12, 0);
738
+ expect(formatAbsolute(datetime)).toBe('29/02/2024 12:00:00');
739
+ });
740
+ it('should handle different years', () => {
741
+ const datetime2020 = new ZonedDateTime(2020, 6, 15, 'Europe/London', 0, 10, 30);
742
+ expect(formatAbsolute(datetime2020)).toBe('15/06/2020 10:30:00');
743
+ const datetime2025 = new ZonedDateTime(2025, 6, 15, 'Europe/London', 0, 10, 30);
744
+ expect(formatAbsolute(datetime2025)).toBe('15/06/2025 10:30:00');
745
+ });
746
+ it('should handle all months correctly', () => {
747
+ const datetime1 = new ZonedDateTime(2024, 1, 15, 'Europe/London', 0, 12, 0);
748
+ expect(formatAbsolute(datetime1)).toBe('15/01/2024 12:00:00');
749
+ const datetime12 = new ZonedDateTime(2024, 12, 15, 'Europe/London', 0, 12, 0);
750
+ expect(formatAbsolute(datetime12)).toBe('15/12/2024 12:00:00');
751
+ });
752
+ it('should handle different time zones consistently', () => {
753
+ // Note: The function converts to CalendarDate and Time, so timezone shouldn't affect the output format
754
+ const datetimeUTC = new ZonedDateTime(2024, 5, 15, 'UTC', 0, 14, 30);
755
+ const datetimeNY = new ZonedDateTime(2024, 5, 15, 'America/New_York', 0, 14, 30);
756
+ // Both should format the same since we're using the same local date/time components
757
+ expect(formatAbsolute(datetimeUTC)).toBe('15/05/2024 14:30:00');
758
+ expect(formatAbsolute(datetimeNY)).toBe('15/05/2024 14:30:00');
759
+ });
760
+ it('should include seconds in the time portion', () => {
761
+ const datetime = new ZonedDateTime(2024, 5, 15, 'Europe/London', 0, 14, 30, 45);
762
+ expect(formatAbsolute(datetime)).toBe('15/05/2024 14:30:45');
763
+ });
764
+ it('should handle times with milliseconds (ignores milliseconds)', () => {
765
+ const datetime = new ZonedDateTime(2024, 5, 15, 'Europe/London', 0, 14, 30, 45, 500);
766
+ expect(formatAbsolute(datetime)).toBe('15/05/2024 14:30:45');
767
+ });
768
+ });