@xiboplayer/schedule 0.6.12 → 0.7.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/package.json +2 -2
- package/src/overlays.test.js +241 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/schedule",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Complete scheduling solution: campaigns, dayparting, interrupts, and overlays",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"./overlays": "./src/overlays.js"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@xiboplayer/utils": "0.
|
|
15
|
+
"@xiboplayer/utils": "0.7.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"vitest": "^2.0.0"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2024-2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
+
import { OverlayScheduler } from './overlays.js';
|
|
5
|
+
|
|
6
|
+
function makeOverlay(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
file: 100,
|
|
9
|
+
fromdt: '2026-01-01T00:00:00',
|
|
10
|
+
todt: '2026-12-31T23:59:59',
|
|
11
|
+
priority: 0,
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('OverlayScheduler', () => {
|
|
17
|
+
let scheduler;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
scheduler = new OverlayScheduler();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ── Constructor ────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
describe('constructor', () => {
|
|
26
|
+
it('initializes with empty overlays', () => {
|
|
27
|
+
expect(scheduler.overlays).toEqual([]);
|
|
28
|
+
expect(scheduler.displayProperties).toEqual({});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// ── setOverlays ───────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
describe('setOverlays', () => {
|
|
35
|
+
it('stores overlay list', () => {
|
|
36
|
+
scheduler.setOverlays([makeOverlay()]);
|
|
37
|
+
expect(scheduler.overlays).toHaveLength(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('handles null/undefined', () => {
|
|
41
|
+
scheduler.setOverlays(null);
|
|
42
|
+
expect(scheduler.overlays).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('replaces previous overlays', () => {
|
|
46
|
+
scheduler.setOverlays([makeOverlay({ file: 1 })]);
|
|
47
|
+
scheduler.setOverlays([makeOverlay({ file: 2 }), makeOverlay({ file: 3 })]);
|
|
48
|
+
expect(scheduler.overlays).toHaveLength(2);
|
|
49
|
+
expect(scheduler.overlays[0].file).toBe(2);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ── isTimeActive ──────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
describe('isTimeActive', () => {
|
|
56
|
+
it('returns true when now is within time window', () => {
|
|
57
|
+
const overlay = makeOverlay({
|
|
58
|
+
fromdt: '2026-03-01T00:00:00',
|
|
59
|
+
todt: '2026-03-31T23:59:59',
|
|
60
|
+
});
|
|
61
|
+
const now = new Date('2026-03-15T12:00:00');
|
|
62
|
+
expect(scheduler.isTimeActive(overlay, now)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns false when now is before fromdt', () => {
|
|
66
|
+
const overlay = makeOverlay({ fromdt: '2026-06-01T00:00:00' });
|
|
67
|
+
const now = new Date('2026-05-01T12:00:00');
|
|
68
|
+
expect(scheduler.isTimeActive(overlay, now)).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns false when now is after todt', () => {
|
|
72
|
+
const overlay = makeOverlay({ todt: '2026-01-01T00:00:00' });
|
|
73
|
+
const now = new Date('2026-06-01T12:00:00');
|
|
74
|
+
expect(scheduler.isTimeActive(overlay, now)).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns true when no time bounds set', () => {
|
|
78
|
+
const overlay = { file: 1 }; // no fromdt/todt
|
|
79
|
+
expect(scheduler.isTimeActive(overlay, new Date())).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('supports toDt (camelCase) alias', () => {
|
|
83
|
+
const overlay = { file: 1, fromDt: '2026-01-01', toDt: '2026-12-31' };
|
|
84
|
+
expect(scheduler.isTimeActive(overlay, new Date('2026-06-15'))).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ── getCurrentOverlays ────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
describe('getCurrentOverlays', () => {
|
|
91
|
+
it('returns empty array when no overlays set', () => {
|
|
92
|
+
expect(scheduler.getCurrentOverlays()).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns only overlays within time window', () => {
|
|
96
|
+
scheduler.setOverlays([
|
|
97
|
+
makeOverlay({ file: 1, fromdt: '2026-01-01', todt: '2026-06-30' }),
|
|
98
|
+
makeOverlay({ file: 2, fromdt: '2026-07-01', todt: '2026-12-31' }),
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
// Mock Date to be in Q1
|
|
102
|
+
const origDate = global.Date;
|
|
103
|
+
global.Date = class extends origDate {
|
|
104
|
+
constructor(...args) {
|
|
105
|
+
if (args.length === 0) return new origDate('2026-03-15T12:00:00');
|
|
106
|
+
return new origDate(...args);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const active = scheduler.getCurrentOverlays();
|
|
111
|
+
expect(active).toHaveLength(1);
|
|
112
|
+
expect(active[0].file).toBe(1);
|
|
113
|
+
|
|
114
|
+
global.Date = origDate;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('sorts by priority descending', () => {
|
|
118
|
+
scheduler.setOverlays([
|
|
119
|
+
makeOverlay({ file: 1, priority: 5 }),
|
|
120
|
+
makeOverlay({ file: 2, priority: 10 }),
|
|
121
|
+
makeOverlay({ file: 3, priority: 1 }),
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
// All overlays are within 2026 time window
|
|
125
|
+
const origDate = global.Date;
|
|
126
|
+
global.Date = class extends origDate {
|
|
127
|
+
constructor(...args) {
|
|
128
|
+
if (args.length === 0) return new origDate('2026-06-15T12:00:00');
|
|
129
|
+
return new origDate(...args);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const active = scheduler.getCurrentOverlays();
|
|
134
|
+
expect(active[0].priority).toBe(10);
|
|
135
|
+
expect(active[1].priority).toBe(5);
|
|
136
|
+
expect(active[2].priority).toBe(1);
|
|
137
|
+
|
|
138
|
+
global.Date = origDate;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('defaults priority to 0 when not set', () => {
|
|
142
|
+
scheduler.setOverlays([
|
|
143
|
+
makeOverlay({ file: 1 }), // no priority
|
|
144
|
+
makeOverlay({ file: 2, priority: 5 }),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const origDate = global.Date;
|
|
148
|
+
global.Date = class extends origDate {
|
|
149
|
+
constructor(...args) {
|
|
150
|
+
if (args.length === 0) return new origDate('2026-06-15T12:00:00');
|
|
151
|
+
return new origDate(...args);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const active = scheduler.getCurrentOverlays();
|
|
156
|
+
expect(active[0].file).toBe(2); // priority 5 first
|
|
157
|
+
expect(active[1].file).toBe(1); // priority 0
|
|
158
|
+
|
|
159
|
+
global.Date = origDate;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('filters by geo-fence when isGeoAware', () => {
|
|
163
|
+
const mockScheduleManager = {
|
|
164
|
+
isWithinGeoFence: () => false,
|
|
165
|
+
};
|
|
166
|
+
scheduler.setScheduleManager(mockScheduleManager);
|
|
167
|
+
|
|
168
|
+
scheduler.setOverlays([
|
|
169
|
+
makeOverlay({ file: 1, isGeoAware: true, geoLocation: { lat: 41, lng: 2 } }),
|
|
170
|
+
makeOverlay({ file: 2 }), // not geo-aware
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
const origDate = global.Date;
|
|
174
|
+
global.Date = class extends origDate {
|
|
175
|
+
constructor(...args) {
|
|
176
|
+
if (args.length === 0) return new origDate('2026-06-15T12:00:00');
|
|
177
|
+
return new origDate(...args);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const active = scheduler.getCurrentOverlays();
|
|
182
|
+
expect(active).toHaveLength(1);
|
|
183
|
+
expect(active[0].file).toBe(2);
|
|
184
|
+
|
|
185
|
+
global.Date = origDate;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ── getOverlayByFile ──────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
describe('getOverlayByFile', () => {
|
|
192
|
+
it('finds overlay by file ID', () => {
|
|
193
|
+
scheduler.setOverlays([makeOverlay({ file: 42 })]);
|
|
194
|
+
expect(scheduler.getOverlayByFile(42)).not.toBeNull();
|
|
195
|
+
expect(scheduler.getOverlayByFile(42).file).toBe(42);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('returns null for unknown file', () => {
|
|
199
|
+
expect(scheduler.getOverlayByFile(999)).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ── shouldCheckOverlays ───────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
describe('shouldCheckOverlays', () => {
|
|
206
|
+
it('returns true when no last check', () => {
|
|
207
|
+
expect(scheduler.shouldCheckOverlays(null)).toBe(true);
|
|
208
|
+
expect(scheduler.shouldCheckOverlays(undefined)).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('returns true after 60+ seconds', () => {
|
|
212
|
+
const lastCheck = Date.now() - 61000;
|
|
213
|
+
expect(scheduler.shouldCheckOverlays(lastCheck)).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('returns false within 60 seconds', () => {
|
|
217
|
+
const lastCheck = Date.now() - 30000;
|
|
218
|
+
expect(scheduler.shouldCheckOverlays(lastCheck)).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ── clear / processOverlays ───────────────────────────────────
|
|
223
|
+
|
|
224
|
+
describe('clear', () => {
|
|
225
|
+
it('removes all overlays', () => {
|
|
226
|
+
scheduler.setOverlays([makeOverlay()]);
|
|
227
|
+
scheduler.clear();
|
|
228
|
+
expect(scheduler.overlays).toHaveLength(0);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('processOverlays', () => {
|
|
233
|
+
it('sets overlays and returns layouts unchanged', () => {
|
|
234
|
+
const layouts = [{ id: 1 }, { id: 2 }];
|
|
235
|
+
const overlays = [makeOverlay({ file: 10 })];
|
|
236
|
+
const result = scheduler.processOverlays(layouts, overlays);
|
|
237
|
+
expect(result).toBe(layouts);
|
|
238
|
+
expect(scheduler.overlays).toHaveLength(1);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|