@xiboplayer/schedule 0.1.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.
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Schedule Manager Dayparting Tests
3
+ *
4
+ * Tests for dayparting (recurring schedule) support
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
8
+ import { ScheduleManager } from './schedule.js';
9
+
10
+ // Helper to create date strings
11
+ function dateStr(hoursOffset = 0) {
12
+ const d = new Date();
13
+ d.setHours(d.getHours() + hoursOffset);
14
+ return d.toISOString();
15
+ }
16
+
17
+ // Helper to create time string for today at specific hour
18
+ function timeStr(hour, minute = 0) {
19
+ const d = new Date();
20
+ d.setHours(hour, minute, 0, 0);
21
+ return d.toISOString();
22
+ }
23
+
24
+ // Helper to get current ISO day of week (1=Monday, 7=Sunday)
25
+ function getCurrentIsoDayOfWeek() {
26
+ const day = new Date().getDay();
27
+ return day === 0 ? 7 : day;
28
+ }
29
+
30
+ // Helper to get a different day than today
31
+ function getDifferentDay() {
32
+ const today = getCurrentIsoDayOfWeek();
33
+ return today === 1 ? 2 : 1;
34
+ }
35
+
36
+ // Helper to mock Date at specific time
37
+ function mockTimeAt(targetDate) {
38
+ const RealDate = Date;
39
+ vi.spyOn(global, 'Date').mockImplementation((...args) => {
40
+ if (args.length === 0) {
41
+ return new RealDate(targetDate);
42
+ }
43
+ return new RealDate(...args);
44
+ });
45
+ }
46
+
47
+ describe('ScheduleManager - Dayparting', () => {
48
+ let manager;
49
+ let originalDate;
50
+
51
+ beforeEach(() => {
52
+ manager = new ScheduleManager();
53
+ originalDate = global.Date;
54
+ });
55
+
56
+ afterEach(() => {
57
+ // Restore Date
58
+ if (vi.isMockFunction(global.Date)) {
59
+ global.Date = originalDate;
60
+ }
61
+ });
62
+
63
+ describe('Weekday Schedules', () => {
64
+ it('should activate weekday schedule during business hours on weekday', () => {
65
+ const currentDay = getCurrentIsoDayOfWeek();
66
+
67
+ if (currentDay > 5) {
68
+ // Skip on weekends
69
+ return;
70
+ }
71
+
72
+ manager.setSchedule({
73
+ default: '0',
74
+ layouts: [
75
+ {
76
+ file: '100',
77
+ priority: 10,
78
+ fromdt: timeStr(9, 0),
79
+ todt: timeStr(17, 0),
80
+ recurrenceType: 'Week',
81
+ recurrenceRepeatsOn: '1,2,3,4,5'
82
+ }
83
+ ],
84
+ campaigns: []
85
+ });
86
+
87
+ // Mock noon on weekday
88
+ const noon = new Date();
89
+ noon.setHours(12, 0, 0, 0);
90
+ mockTimeAt(noon);
91
+
92
+ const layouts = manager.getCurrentLayouts();
93
+
94
+ expect(layouts).toHaveLength(1);
95
+ expect(layouts[0]).toBe('100');
96
+ });
97
+
98
+ it('should not activate weekday schedule outside time window', () => {
99
+ const currentDay = getCurrentIsoDayOfWeek();
100
+
101
+ if (currentDay > 5) {
102
+ return; // Skip on weekends
103
+ }
104
+
105
+ manager.setSchedule({
106
+ default: '999',
107
+ layouts: [
108
+ {
109
+ file: '100',
110
+ priority: 10,
111
+ fromdt: timeStr(9, 0),
112
+ todt: timeStr(17, 0),
113
+ recurrenceType: 'Week',
114
+ recurrenceRepeatsOn: '1,2,3,4,5'
115
+ }
116
+ ],
117
+ campaigns: []
118
+ });
119
+
120
+ // Mock 8:00 AM (before schedule)
121
+ const earlyMorning = new Date();
122
+ earlyMorning.setHours(8, 0, 0, 0);
123
+ mockTimeAt(earlyMorning);
124
+
125
+ const layouts = manager.getCurrentLayouts();
126
+
127
+ expect(layouts).toHaveLength(1);
128
+ expect(layouts[0]).toBe('999');
129
+ });
130
+ });
131
+
132
+ describe('Weekend Schedules', () => {
133
+ it('should activate weekend schedule on weekend', () => {
134
+ const currentDay = getCurrentIsoDayOfWeek();
135
+
136
+ if (currentDay < 6) {
137
+ return; // Skip on weekdays
138
+ }
139
+
140
+ manager.setSchedule({
141
+ default: '0',
142
+ layouts: [
143
+ {
144
+ file: '200',
145
+ priority: 10,
146
+ fromdt: timeStr(10, 0),
147
+ todt: timeStr(18, 0),
148
+ recurrenceType: 'Week',
149
+ recurrenceRepeatsOn: '6,7'
150
+ }
151
+ ],
152
+ campaigns: []
153
+ });
154
+
155
+ // Mock 2:00 PM on weekend
156
+ const afternoon = new Date();
157
+ afternoon.setHours(14, 0, 0, 0);
158
+ mockTimeAt(afternoon);
159
+
160
+ const layouts = manager.getCurrentLayouts();
161
+
162
+ expect(layouts).toHaveLength(1);
163
+ expect(layouts[0]).toBe('200');
164
+ });
165
+ });
166
+
167
+ describe('Day of Week Filtering', () => {
168
+ it('should not activate schedule on wrong day of week', () => {
169
+ const differentDay = getDifferentDay();
170
+
171
+ manager.setSchedule({
172
+ default: '999',
173
+ layouts: [
174
+ {
175
+ file: '300',
176
+ priority: 10,
177
+ fromdt: timeStr(9, 0),
178
+ todt: timeStr(17, 0),
179
+ recurrenceType: 'Week',
180
+ recurrenceRepeatsOn: differentDay.toString()
181
+ }
182
+ ],
183
+ campaigns: []
184
+ });
185
+
186
+ const layouts = manager.getCurrentLayouts();
187
+
188
+ expect(layouts).toHaveLength(1);
189
+ expect(layouts[0]).toBe('999');
190
+ });
191
+ });
192
+
193
+ describe('Priority with Dayparting', () => {
194
+ it('should respect priority in overlapping daypart schedules', () => {
195
+ const currentDay = getCurrentIsoDayOfWeek();
196
+
197
+ manager.setSchedule({
198
+ default: '0',
199
+ layouts: [
200
+ {
201
+ file: '100',
202
+ priority: 5,
203
+ fromdt: timeStr(9, 0),
204
+ todt: timeStr(17, 0),
205
+ recurrenceType: 'Week',
206
+ recurrenceRepeatsOn: '1,2,3,4,5,6,7'
207
+ },
208
+ {
209
+ file: '200',
210
+ priority: 10,
211
+ fromdt: timeStr(12, 0),
212
+ todt: timeStr(14, 0),
213
+ recurrenceType: 'Week',
214
+ recurrenceRepeatsOn: currentDay.toString()
215
+ }
216
+ ],
217
+ campaigns: []
218
+ });
219
+
220
+ // Mock 1:00 PM (lunch time)
221
+ const lunchTime = new Date();
222
+ lunchTime.setHours(13, 0, 0, 0);
223
+ mockTimeAt(lunchTime);
224
+
225
+ const layouts = manager.getCurrentLayouts();
226
+
227
+ expect(layouts).toHaveLength(1);
228
+ expect(layouts[0]).toBe('200');
229
+ });
230
+ });
231
+
232
+ describe('Dayparting Campaigns', () => {
233
+ it('should support campaigns with dayparting', () => {
234
+ const currentDay = getCurrentIsoDayOfWeek();
235
+
236
+ manager.setSchedule({
237
+ default: '0',
238
+ layouts: [],
239
+ campaigns: [
240
+ {
241
+ id: '1',
242
+ priority: 10,
243
+ fromdt: timeStr(9, 0),
244
+ todt: timeStr(17, 0),
245
+ recurrenceType: 'Week',
246
+ recurrenceRepeatsOn: currentDay.toString(),
247
+ layouts: [
248
+ { file: '100' },
249
+ { file: '101' },
250
+ { file: '102' }
251
+ ]
252
+ }
253
+ ]
254
+ });
255
+
256
+ // Mock noon
257
+ const noon = new Date();
258
+ noon.setHours(12, 0, 0, 0);
259
+ mockTimeAt(noon);
260
+
261
+ const layouts = manager.getCurrentLayouts();
262
+
263
+ expect(layouts).toHaveLength(3);
264
+ expect(layouts[0]).toBe('100');
265
+ expect(layouts[1]).toBe('101');
266
+ expect(layouts[2]).toBe('102');
267
+ });
268
+ });
269
+
270
+ describe('Midnight Crossing', () => {
271
+ it('should handle schedules that cross midnight', () => {
272
+ const currentDay = getCurrentIsoDayOfWeek();
273
+
274
+ manager.setSchedule({
275
+ default: '999',
276
+ layouts: [
277
+ {
278
+ file: '400',
279
+ priority: 10,
280
+ fromdt: timeStr(22, 0),
281
+ todt: timeStr(2, 0),
282
+ recurrenceType: 'Week',
283
+ recurrenceRepeatsOn: currentDay.toString()
284
+ }
285
+ ],
286
+ campaigns: []
287
+ });
288
+
289
+ // Mock 11:00 PM
290
+ const lateNight = new Date();
291
+ lateNight.setHours(23, 0, 0, 0);
292
+ mockTimeAt(lateNight);
293
+
294
+ const layouts = manager.getCurrentLayouts();
295
+
296
+ expect(layouts).toHaveLength(1);
297
+ expect(layouts[0]).toBe('400');
298
+ });
299
+ });
300
+
301
+ describe('Backward Compatibility', () => {
302
+ it('should still support non-recurring schedules', () => {
303
+ manager.setSchedule({
304
+ default: '0',
305
+ layouts: [
306
+ {
307
+ file: '500',
308
+ priority: 10,
309
+ fromdt: dateStr(-1),
310
+ todt: dateStr(1)
311
+ }
312
+ ],
313
+ campaigns: []
314
+ });
315
+
316
+ const layouts = manager.getCurrentLayouts();
317
+
318
+ expect(layouts).toHaveLength(1);
319
+ expect(layouts[0]).toBe('500');
320
+ });
321
+ });
322
+
323
+ describe('Specific Days Schedule', () => {
324
+ it('should handle specific days (Mon, Wed, Fri)', () => {
325
+ const currentDay = getCurrentIsoDayOfWeek();
326
+ const scheduledDays = [1, 3, 5];
327
+ const isScheduledDay = scheduledDays.includes(currentDay);
328
+
329
+ manager.setSchedule({
330
+ default: '999',
331
+ layouts: [
332
+ {
333
+ file: '600',
334
+ priority: 10,
335
+ fromdt: timeStr(9, 0),
336
+ todt: timeStr(17, 0),
337
+ recurrenceType: 'Week',
338
+ recurrenceRepeatsOn: '1,3,5'
339
+ }
340
+ ],
341
+ campaigns: []
342
+ });
343
+
344
+ // Mock noon
345
+ const noon = new Date();
346
+ noon.setHours(12, 0, 0, 0);
347
+ mockTimeAt(noon);
348
+
349
+ const layouts = manager.getCurrentLayouts();
350
+
351
+ expect(layouts).toHaveLength(1);
352
+ if (isScheduledDay) {
353
+ expect(layouts[0]).toBe('600');
354
+ } else {
355
+ expect(layouts[0]).toBe('999');
356
+ }
357
+ });
358
+ });
359
+
360
+ describe('Recurrence Range', () => {
361
+ it('should respect recurrenceRange end date', () => {
362
+ const currentDay = getCurrentIsoDayOfWeek();
363
+
364
+ // Create recurrence that ended yesterday
365
+ const yesterday = new Date();
366
+ yesterday.setDate(yesterday.getDate() - 1);
367
+
368
+ manager.setSchedule({
369
+ default: '999',
370
+ layouts: [
371
+ {
372
+ file: '700',
373
+ priority: 10,
374
+ fromdt: timeStr(9, 0),
375
+ todt: timeStr(17, 0),
376
+ recurrenceType: 'Week',
377
+ recurrenceRepeatsOn: currentDay.toString(),
378
+ recurrenceRange: yesterday.toISOString()
379
+ }
380
+ ],
381
+ campaigns: []
382
+ });
383
+
384
+ const layouts = manager.getCurrentLayouts();
385
+
386
+ expect(layouts).toHaveLength(1);
387
+ expect(layouts[0]).toBe('999');
388
+ });
389
+ });
390
+ });