@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,505 @@
1
+ /**
2
+ * Schedule Manager Tests
3
+ *
4
+ * Tests for campaign support in schedule manager
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } 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
+ describe('ScheduleManager - Campaigns', () => {
18
+ let manager;
19
+
20
+ beforeEach(() => {
21
+ manager = new ScheduleManager();
22
+ });
23
+
24
+ describe('Campaign Priority', () => {
25
+ it('should prioritize campaign over standalone layout when priority is higher', () => {
26
+ manager.setSchedule({
27
+ default: '0',
28
+ layouts: [
29
+ { file: '100', priority: 5, fromdt: dateStr(-1), todt: dateStr(1) }
30
+ ],
31
+ campaigns: [
32
+ {
33
+ id: '1',
34
+ priority: 10,
35
+ fromdt: dateStr(-1),
36
+ todt: dateStr(1),
37
+ layouts: [
38
+ { file: '200' },
39
+ { file: '201' },
40
+ { file: '202' }
41
+ ]
42
+ }
43
+ ]
44
+ });
45
+
46
+ const layouts = manager.getCurrentLayouts();
47
+
48
+ expect(layouts).toHaveLength(3);
49
+ expect(layouts[0]).toBe('200');
50
+ expect(layouts[1]).toBe('201');
51
+ expect(layouts[2]).toBe('202');
52
+ });
53
+
54
+ it('should include all layouts from multiple campaigns at same priority', () => {
55
+ manager.setSchedule({
56
+ default: '0',
57
+ layouts: [],
58
+ campaigns: [
59
+ {
60
+ id: '1',
61
+ priority: 10,
62
+ fromdt: dateStr(-1),
63
+ todt: dateStr(1),
64
+ layouts: [
65
+ { file: '100' },
66
+ { file: '101' }
67
+ ]
68
+ },
69
+ {
70
+ id: '2',
71
+ priority: 10,
72
+ fromdt: dateStr(-1),
73
+ todt: dateStr(1),
74
+ layouts: [
75
+ { file: '200' },
76
+ { file: '201' }
77
+ ]
78
+ }
79
+ ]
80
+ });
81
+
82
+ const layouts = manager.getCurrentLayouts();
83
+
84
+ expect(layouts).toHaveLength(4);
85
+ expect(layouts).toContain('100');
86
+ expect(layouts).toContain('101');
87
+ expect(layouts).toContain('200');
88
+ expect(layouts).toContain('201');
89
+ });
90
+
91
+ it('should include both campaign and standalone layouts at same priority', () => {
92
+ manager.setSchedule({
93
+ default: '0',
94
+ layouts: [
95
+ { file: '100', priority: 10, fromdt: dateStr(-1), todt: dateStr(1) },
96
+ { file: '101', priority: 10, fromdt: dateStr(-1), todt: dateStr(1) }
97
+ ],
98
+ campaigns: [
99
+ {
100
+ id: '1',
101
+ priority: 10,
102
+ fromdt: dateStr(-1),
103
+ todt: dateStr(1),
104
+ layouts: [
105
+ { file: '200' },
106
+ { file: '201' }
107
+ ]
108
+ }
109
+ ]
110
+ });
111
+
112
+ const layouts = manager.getCurrentLayouts();
113
+
114
+ expect(layouts).toHaveLength(4);
115
+ expect(layouts).toContain('100');
116
+ expect(layouts).toContain('101');
117
+ expect(layouts).toContain('200');
118
+ expect(layouts).toContain('201');
119
+ });
120
+ });
121
+
122
+ describe('Campaign Time Windows', () => {
123
+ it('should ignore campaign outside time window', () => {
124
+ manager.setSchedule({
125
+ default: '0',
126
+ layouts: [
127
+ { file: '100', priority: 5, fromdt: dateStr(-1), todt: dateStr(1) }
128
+ ],
129
+ campaigns: [
130
+ {
131
+ id: '1',
132
+ priority: 10,
133
+ fromdt: dateStr(-10), // Started 10 hours ago
134
+ todt: dateStr(-5), // Ended 5 hours ago
135
+ layouts: [
136
+ { file: '200' },
137
+ { file: '201' }
138
+ ]
139
+ }
140
+ ]
141
+ });
142
+
143
+ const layouts = manager.getCurrentLayouts();
144
+
145
+ expect(layouts).toHaveLength(1);
146
+ expect(layouts[0]).toBe('100');
147
+ });
148
+ });
149
+
150
+ describe('Default Layout', () => {
151
+ it('should return default layout when no schedules active', () => {
152
+ manager.setSchedule({
153
+ default: '999',
154
+ layouts: [],
155
+ campaigns: []
156
+ });
157
+
158
+ const layouts = manager.getCurrentLayouts();
159
+
160
+ expect(layouts).toHaveLength(1);
161
+ expect(layouts[0]).toBe('999');
162
+ });
163
+ });
164
+
165
+ describe('Campaign Layout Order', () => {
166
+ it('should preserve layout order within campaign', () => {
167
+ manager.setSchedule({
168
+ default: '0',
169
+ layouts: [],
170
+ campaigns: [
171
+ {
172
+ id: '1',
173
+ priority: 10,
174
+ fromdt: dateStr(-1),
175
+ todt: dateStr(1),
176
+ layouts: [
177
+ { file: '205' },
178
+ { file: '203' },
179
+ { file: '204' },
180
+ { file: '201' },
181
+ { file: '202' }
182
+ ]
183
+ }
184
+ ]
185
+ });
186
+
187
+ const layouts = manager.getCurrentLayouts();
188
+
189
+ expect(layouts).toHaveLength(5);
190
+ expect(layouts[0]).toBe('205');
191
+ expect(layouts[1]).toBe('203');
192
+ expect(layouts[2]).toBe('204');
193
+ expect(layouts[3]).toBe('201');
194
+ expect(layouts[4]).toBe('202');
195
+ });
196
+ });
197
+ });
198
+
199
+ describe('ScheduleManager - Actions and Commands', () => {
200
+ let manager;
201
+
202
+ beforeEach(() => {
203
+ manager = new ScheduleManager();
204
+ });
205
+
206
+ describe('getActiveActions()', () => {
207
+ it('should return actions within time window', () => {
208
+ manager.setSchedule({
209
+ default: '0',
210
+ layouts: [],
211
+ campaigns: [],
212
+ actions: [
213
+ {
214
+ actionType: 'navLayout',
215
+ triggerCode: 'trigger1',
216
+ layoutCode: '123',
217
+ fromdt: dateStr(-1),
218
+ todt: dateStr(1),
219
+ priority: 1,
220
+ scheduleId: '1'
221
+ }
222
+ ],
223
+ commands: []
224
+ });
225
+
226
+ const actions = manager.getActiveActions();
227
+
228
+ expect(actions).toHaveLength(1);
229
+ expect(actions[0].triggerCode).toBe('trigger1');
230
+ expect(actions[0].actionType).toBe('navLayout');
231
+ });
232
+
233
+ it('should exclude actions outside time window', () => {
234
+ manager.setSchedule({
235
+ default: '0',
236
+ layouts: [],
237
+ campaigns: [],
238
+ actions: [
239
+ {
240
+ actionType: 'navLayout',
241
+ triggerCode: 'expired',
242
+ layoutCode: '123',
243
+ fromdt: dateStr(-10),
244
+ todt: dateStr(-5),
245
+ priority: 1,
246
+ scheduleId: '2'
247
+ }
248
+ ],
249
+ commands: []
250
+ });
251
+
252
+ const actions = manager.getActiveActions();
253
+
254
+ expect(actions).toHaveLength(0);
255
+ });
256
+
257
+ it('should return multiple active actions', () => {
258
+ manager.setSchedule({
259
+ default: '0',
260
+ layouts: [],
261
+ campaigns: [],
262
+ actions: [
263
+ {
264
+ actionType: 'navLayout',
265
+ triggerCode: 'trigger1',
266
+ layoutCode: '100',
267
+ fromdt: dateStr(-1),
268
+ todt: dateStr(1),
269
+ priority: 1,
270
+ scheduleId: '1'
271
+ },
272
+ {
273
+ actionType: 'command',
274
+ triggerCode: 'trigger2',
275
+ commandCode: 'restart',
276
+ fromdt: dateStr(-2),
277
+ todt: dateStr(2),
278
+ priority: 5,
279
+ scheduleId: '2'
280
+ }
281
+ ],
282
+ commands: []
283
+ });
284
+
285
+ const actions = manager.getActiveActions();
286
+
287
+ expect(actions).toHaveLength(2);
288
+ });
289
+
290
+ it('should return empty array when no actions exist', () => {
291
+ manager.setSchedule({
292
+ default: '0',
293
+ layouts: [],
294
+ campaigns: []
295
+ });
296
+
297
+ const actions = manager.getActiveActions();
298
+
299
+ expect(actions).toEqual([]);
300
+ });
301
+
302
+ it('should return empty array when schedule is null', () => {
303
+ const actions = manager.getActiveActions();
304
+
305
+ expect(actions).toEqual([]);
306
+ });
307
+
308
+ it('should filter mixed active and expired actions', () => {
309
+ manager.setSchedule({
310
+ default: '0',
311
+ layouts: [],
312
+ campaigns: [],
313
+ actions: [
314
+ {
315
+ actionType: 'navLayout',
316
+ triggerCode: 'active1',
317
+ layoutCode: '100',
318
+ fromdt: dateStr(-1),
319
+ todt: dateStr(1),
320
+ priority: 1,
321
+ scheduleId: '1'
322
+ },
323
+ {
324
+ actionType: 'navLayout',
325
+ triggerCode: 'expired1',
326
+ layoutCode: '200',
327
+ fromdt: dateStr(-10),
328
+ todt: dateStr(-5),
329
+ priority: 1,
330
+ scheduleId: '2'
331
+ },
332
+ {
333
+ actionType: 'command',
334
+ triggerCode: 'active2',
335
+ commandCode: 'collectNow',
336
+ fromdt: dateStr(-2),
337
+ todt: dateStr(2),
338
+ priority: 1,
339
+ scheduleId: '3'
340
+ }
341
+ ],
342
+ commands: []
343
+ });
344
+
345
+ const actions = manager.getActiveActions();
346
+
347
+ expect(actions).toHaveLength(2);
348
+ expect(actions.map(a => a.triggerCode)).toContain('active1');
349
+ expect(actions.map(a => a.triggerCode)).toContain('active2');
350
+ expect(actions.map(a => a.triggerCode)).not.toContain('expired1');
351
+ });
352
+ });
353
+
354
+ describe('findActionByTrigger()', () => {
355
+ it('should find matching action by trigger code', () => {
356
+ manager.setSchedule({
357
+ default: '0',
358
+ layouts: [],
359
+ campaigns: [],
360
+ actions: [
361
+ {
362
+ actionType: 'navLayout',
363
+ triggerCode: 'trigger1',
364
+ layoutCode: '123',
365
+ fromdt: dateStr(-1),
366
+ todt: dateStr(1),
367
+ priority: 1,
368
+ scheduleId: '1'
369
+ },
370
+ {
371
+ actionType: 'command',
372
+ triggerCode: 'trigger2',
373
+ commandCode: 'restart',
374
+ fromdt: dateStr(-1),
375
+ todt: dateStr(1),
376
+ priority: 2,
377
+ scheduleId: '2'
378
+ }
379
+ ],
380
+ commands: []
381
+ });
382
+
383
+ const action = manager.findActionByTrigger('trigger2');
384
+
385
+ expect(action).not.toBeNull();
386
+ expect(action.triggerCode).toBe('trigger2');
387
+ expect(action.actionType).toBe('command');
388
+ });
389
+
390
+ it('should return null when no matching action found', () => {
391
+ manager.setSchedule({
392
+ default: '0',
393
+ layouts: [],
394
+ campaigns: [],
395
+ actions: [
396
+ {
397
+ actionType: 'navLayout',
398
+ triggerCode: 'trigger1',
399
+ layoutCode: '123',
400
+ fromdt: dateStr(-1),
401
+ todt: dateStr(1),
402
+ priority: 1,
403
+ scheduleId: '1'
404
+ }
405
+ ],
406
+ commands: []
407
+ });
408
+
409
+ const action = manager.findActionByTrigger('nonexistent');
410
+
411
+ expect(action).toBeNull();
412
+ });
413
+
414
+ it('should not find expired action even if trigger matches', () => {
415
+ manager.setSchedule({
416
+ default: '0',
417
+ layouts: [],
418
+ campaigns: [],
419
+ actions: [
420
+ {
421
+ actionType: 'navLayout',
422
+ triggerCode: 'trigger1',
423
+ layoutCode: '123',
424
+ fromdt: dateStr(-10),
425
+ todt: dateStr(-5),
426
+ priority: 1,
427
+ scheduleId: '1'
428
+ }
429
+ ],
430
+ commands: []
431
+ });
432
+
433
+ const action = manager.findActionByTrigger('trigger1');
434
+
435
+ expect(action).toBeNull();
436
+ });
437
+
438
+ it('should return null when schedule has no actions', () => {
439
+ manager.setSchedule({
440
+ default: '0',
441
+ layouts: [],
442
+ campaigns: []
443
+ });
444
+
445
+ const action = manager.findActionByTrigger('trigger1');
446
+
447
+ expect(action).toBeNull();
448
+ });
449
+ });
450
+
451
+ describe('getCommands()', () => {
452
+ it('should return command list', () => {
453
+ manager.setSchedule({
454
+ default: '0',
455
+ layouts: [],
456
+ campaigns: [],
457
+ actions: [],
458
+ commands: [
459
+ { code: 'collectNow', date: '2026-02-11' },
460
+ { code: 'reboot', date: '2026-02-12' }
461
+ ]
462
+ });
463
+
464
+ const commands = manager.getCommands();
465
+
466
+ expect(commands).toHaveLength(2);
467
+ expect(commands[0].code).toBe('collectNow');
468
+ expect(commands[0].date).toBe('2026-02-11');
469
+ expect(commands[1].code).toBe('reboot');
470
+ expect(commands[1].date).toBe('2026-02-12');
471
+ });
472
+
473
+ it('should return empty array when no commands', () => {
474
+ manager.setSchedule({
475
+ default: '0',
476
+ layouts: [],
477
+ campaigns: [],
478
+ actions: [],
479
+ commands: []
480
+ });
481
+
482
+ const commands = manager.getCommands();
483
+
484
+ expect(commands).toEqual([]);
485
+ });
486
+
487
+ it('should return empty array when commands property is missing', () => {
488
+ manager.setSchedule({
489
+ default: '0',
490
+ layouts: [],
491
+ campaigns: []
492
+ });
493
+
494
+ const commands = manager.getCommands();
495
+
496
+ expect(commands).toEqual([]);
497
+ });
498
+
499
+ it('should return empty array when schedule is null', () => {
500
+ const commands = manager.getCommands();
501
+
502
+ expect(commands).toEqual([]);
503
+ });
504
+ });
505
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'jsdom'
7
+ }
8
+ });