@xiboplayer/renderer 0.6.1 → 0.6.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/renderer",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "RendererLite - Fast, efficient XLF layout rendering engine",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -13,8 +13,9 @@
13
13
  "dependencies": {
14
14
  "nanoevents": "^9.1.0",
15
15
  "pdfjs-dist": "^4.10.38",
16
- "@xiboplayer/cache": "0.6.1",
17
- "@xiboplayer/utils": "0.6.1"
16
+ "@xiboplayer/utils": "0.6.3",
17
+ "@xiboplayer/cache": "0.6.3",
18
+ "@xiboplayer/schedule": "0.6.3"
18
19
  },
19
20
  "devDependencies": {
20
21
  "vitest": "^2.0.0",
package/src/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export interface RendererConfig {
6
6
  }
7
7
 
8
8
  export interface RendererOptions {
9
- getMediaUrl?: (fileId: number) => Promise<string>;
9
+ fileIdToSaveAs?: Map<string, string>;
10
10
  getWidgetHtml?: (widget: any) => Promise<string | { url: string; fallback?: string }>;
11
11
  logLevel?: string;
12
12
  }
@@ -33,8 +33,7 @@ describe('LayoutPool', () => {
33
33
  container,
34
34
  layout: { width: 1920, height: 1080, duration: 60, bgcolor: '#000', regions: [] },
35
35
  regions: new Map(),
36
- blobUrls: new Set(),
37
- mediaUrlCache: new Map()
36
+ blobUrls: new Set()
38
37
  };
39
38
  }
40
39
 
@@ -170,18 +169,6 @@ describe('LayoutPool', () => {
170
169
  expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:test-2');
171
170
  });
172
171
 
173
- it('should revoke media blob URLs on eviction', () => {
174
- const entry = createMockEntry(1);
175
- entry.mediaUrlCache.set(10, 'blob:media-10');
176
- entry.mediaUrlCache.set(20, 'blob:media-20');
177
-
178
- pool.add(1, entry);
179
- pool.evict(1);
180
-
181
- expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:media-10');
182
- expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:media-20');
183
- });
184
-
185
172
  it('should remove container from DOM', () => {
186
173
  const entry = createMockEntry(1);
187
174
  pool.add(1, entry);
package/src/layout.js CHANGED
@@ -303,10 +303,16 @@ window.Transitions = {
303
303
  const direction = transitionConfig.direction || 'N';
304
304
 
305
305
  switch (type) {
306
+ case 'fade':
307
+ return isIn ? this.fadeIn(element, duration) : this.fadeOut(element, duration);
306
308
  case 'fadein':
307
309
  return isIn ? this.fadeIn(element, duration) : null;
308
310
  case 'fadeout':
309
311
  return isIn ? null : this.fadeOut(element, duration);
312
+ case 'fly':
313
+ return isIn
314
+ ? this.flyIn(element, duration, direction, regionWidth, regionHeight)
315
+ : this.flyOut(element, duration, direction, regionWidth, regionHeight);
310
316
  case 'flyin':
311
317
  return isIn ? this.flyIn(element, duration, direction, regionWidth, regionHeight) : null;
312
318
  case 'flyout':
@@ -0,0 +1,203 @@
1
+ /**
2
+ * RendererLite Interactive Control (XIC) Tests
3
+ *
4
+ * Tests for the XIC event handlers: interactiveTrigger, widgetExpire,
5
+ * widgetExtendDuration, widgetSetDuration.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
9
+ import { RendererLite } from './renderer-lite.js';
10
+
11
+ /**
12
+ * Create a minimal RendererLite instance with stubbed DOM and methods.
13
+ */
14
+ function createRenderer() {
15
+ const container = document.createElement('div');
16
+ const renderer = new RendererLite(
17
+ { cmsUrl: 'http://localhost', hardwareKey: 'test' },
18
+ container,
19
+ { logLevel: 'silent' }
20
+ );
21
+
22
+ // Stub methods that touch DOM or async operations
23
+ renderer.renderWidget = vi.fn();
24
+ renderer.stopWidget = vi.fn();
25
+ renderer.checkLayoutComplete = vi.fn();
26
+ renderer._startRegionCycle = vi.fn();
27
+ renderer.navigateToWidget = vi.fn();
28
+
29
+ return renderer;
30
+ }
31
+
32
+ /**
33
+ * Populate renderer with a fake region containing widgets.
34
+ */
35
+ function addRegion(renderer, regionId, widgets) {
36
+ renderer.regions.set(regionId, {
37
+ element: document.createElement('div'),
38
+ config: { id: regionId },
39
+ widgets,
40
+ currentIndex: 0,
41
+ timer: null,
42
+ width: 100,
43
+ height: 100,
44
+ complete: false,
45
+ isDrawer: false,
46
+ widgetElements: new Map()
47
+ });
48
+ }
49
+
50
+ describe('RendererLite XIC', () => {
51
+ let renderer;
52
+
53
+ beforeEach(() => {
54
+ renderer = createRenderer();
55
+ addRegion(renderer, 'region-1', [
56
+ { id: 'w1', type: 'text', duration: 10, options: {} },
57
+ { id: 'w2', type: 'image', duration: 20, options: {} },
58
+ { id: 'w3', type: 'video', duration: 30, options: {} }
59
+ ]);
60
+ });
61
+
62
+ describe('_findRegionByWidgetId', () => {
63
+ it('should find a widget in main regions', () => {
64
+ const result = renderer._findRegionByWidgetId('w2');
65
+ expect(result).not.toBeNull();
66
+ expect(result.regionId).toBe('region-1');
67
+ expect(result.widgetIndex).toBe(1);
68
+ expect(result.widget.id).toBe('w2');
69
+ expect(result.regionMap).toBe(renderer.regions);
70
+ });
71
+
72
+ it('should return null for unknown widget', () => {
73
+ const result = renderer._findRegionByWidgetId('w-unknown');
74
+ expect(result).toBeNull();
75
+ });
76
+
77
+ it('should find a widget in overlay regions', () => {
78
+ const overlayRegions = new Map();
79
+ overlayRegions.set('overlay-r1', {
80
+ element: document.createElement('div'),
81
+ config: { id: 'overlay-r1' },
82
+ widgets: [{ id: 'ow1', type: 'text', duration: 5, options: {} }],
83
+ currentIndex: 0,
84
+ timer: null,
85
+ width: 50,
86
+ height: 50,
87
+ complete: false,
88
+ isDrawer: false,
89
+ widgetElements: new Map()
90
+ });
91
+ renderer.activeOverlays.set(100, { regions: overlayRegions });
92
+
93
+ const result = renderer._findRegionByWidgetId('ow1');
94
+ expect(result).not.toBeNull();
95
+ expect(result.regionId).toBe('overlay-r1');
96
+ expect(result.widgetIndex).toBe(0);
97
+ expect(result.regionMap).toBe(overlayRegions);
98
+ });
99
+ });
100
+
101
+ describe('_handleInteractiveTrigger', () => {
102
+ it('should call navigateToWidget when target exists', () => {
103
+ renderer.emit('interactiveTrigger', { targetId: 'w2', triggerCode: 'btn1' });
104
+ expect(renderer.navigateToWidget).toHaveBeenCalledWith('w2');
105
+ });
106
+
107
+ it('should not call navigateToWidget when target is unknown', () => {
108
+ renderer.emit('interactiveTrigger', { targetId: 'w-missing', triggerCode: 'btn1' });
109
+ expect(renderer.navigateToWidget).not.toHaveBeenCalled();
110
+ });
111
+ });
112
+
113
+ describe('_handleWidgetExpire', () => {
114
+ it('should clear timer, stop widget, and advance region', () => {
115
+ const region = renderer.regions.get('region-1');
116
+ region.timer = setTimeout(() => {}, 99999);
117
+ region.currentIndex = 0;
118
+
119
+ renderer.emit('widgetExpire', { widgetId: 'w1' });
120
+
121
+ expect(region.timer).toBeNull();
122
+ expect(renderer.stopWidget).toHaveBeenCalledWith('region-1', 0);
123
+ expect(renderer._startRegionCycle).toHaveBeenCalled();
124
+ });
125
+
126
+ it('should do nothing for unknown widget', () => {
127
+ renderer.emit('widgetExpire', { widgetId: 'w-missing' });
128
+ expect(renderer.stopWidget).not.toHaveBeenCalled();
129
+ });
130
+ });
131
+
132
+ describe('_handleWidgetExtendDuration', () => {
133
+ it('should clear existing timer and re-arm with extended duration', () => {
134
+ vi.useFakeTimers();
135
+ const region = renderer.regions.get('region-1');
136
+ region.timer = setTimeout(() => {}, 99999);
137
+
138
+ renderer.emit('widgetExtendDuration', { widgetId: 'w1', duration: 15 });
139
+
140
+ // Timer should be re-armed (not null)
141
+ expect(region.timer).not.toBeNull();
142
+ // stopWidget should NOT have been called yet (timer hasn't fired)
143
+ expect(renderer.stopWidget).not.toHaveBeenCalled();
144
+
145
+ // Advance time to fire the new timer
146
+ vi.advanceTimersByTime(15000);
147
+ expect(renderer.stopWidget).toHaveBeenCalledWith('region-1', 0);
148
+
149
+ vi.useRealTimers();
150
+ });
151
+
152
+ it('should do nothing for unknown widget', () => {
153
+ renderer.emit('widgetExtendDuration', { widgetId: 'w-missing', duration: 10 });
154
+ expect(renderer.stopWidget).not.toHaveBeenCalled();
155
+ });
156
+ });
157
+
158
+ describe('_handleWidgetSetDuration', () => {
159
+ it('should clear existing timer and set absolute duration', () => {
160
+ vi.useFakeTimers();
161
+ const region = renderer.regions.get('region-1');
162
+ region.timer = setTimeout(() => {}, 99999);
163
+
164
+ renderer.emit('widgetSetDuration', { widgetId: 'w2', duration: 5 });
165
+
166
+ expect(region.timer).not.toBeNull();
167
+ expect(renderer.stopWidget).not.toHaveBeenCalled();
168
+
169
+ vi.advanceTimersByTime(5000);
170
+ // w2 is at index 1, but the region's currentIndex determines what gets stopped
171
+ expect(renderer.stopWidget).toHaveBeenCalled();
172
+
173
+ vi.useRealTimers();
174
+ });
175
+
176
+ it('should do nothing for unknown widget', () => {
177
+ renderer.emit('widgetSetDuration', { widgetId: 'w-missing', duration: 10 });
178
+ expect(renderer.stopWidget).not.toHaveBeenCalled();
179
+ });
180
+ });
181
+
182
+ describe('_advanceRegion', () => {
183
+ it('should increment currentIndex and call _startRegionCycle', () => {
184
+ const region = renderer.regions.get('region-1');
185
+ region.currentIndex = 0;
186
+
187
+ renderer._advanceRegion('region-1', renderer.regions);
188
+
189
+ expect(region.currentIndex).toBe(1);
190
+ expect(renderer._startRegionCycle).toHaveBeenCalled();
191
+ });
192
+
193
+ it('should wrap around at end of widget list', () => {
194
+ const region = renderer.regions.get('region-1');
195
+ region.currentIndex = 2; // last widget
196
+
197
+ renderer._advanceRegion('region-1', renderer.regions);
198
+
199
+ expect(region.currentIndex).toBe(0);
200
+ expect(renderer._startRegionCycle).toHaveBeenCalled();
201
+ });
202
+ });
203
+ });