@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.
- package/docs/README.md +102 -0
- package/docs/XIBO_CAMPAIGNS_AND_PRIORITY.md +600 -0
- package/docs/advanced-features.md +425 -0
- package/docs/integration.md +284 -0
- package/docs/interrupts-implementation.md +357 -0
- package/package.json +41 -0
- package/src/criteria.js +135 -0
- package/src/criteria.test.js +376 -0
- package/src/index.js +20 -0
- package/src/integration.test.js +351 -0
- package/src/interrupts.js +298 -0
- package/src/interrupts.test.js +482 -0
- package/src/overlays.js +174 -0
- package/src/schedule.dayparting.test.js +390 -0
- package/src/schedule.js +509 -0
- package/src/schedule.test.js +505 -0
- package/vitest.config.js +8 -0
|
@@ -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
|
+
});
|