@xiboplayer/xmds 0.7.21 → 0.7.22

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/xmds",
3
- "version": "0.7.21",
3
+ "version": "0.7.22",
4
4
  "description": "XMDS SOAP client for Xibo CMS communication",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -12,7 +12,7 @@
12
12
  "./schedule-parser": "./src/schedule-parser.js"
13
13
  },
14
14
  "dependencies": {
15
- "@xiboplayer/utils": "0.7.21"
15
+ "@xiboplayer/utils": "0.7.22"
16
16
  },
17
17
  "devDependencies": {
18
18
  "vitest": "^4.1.2"
@@ -30,6 +30,35 @@ function parseCriteria(parentEl) {
30
30
  return criteria;
31
31
  }
32
32
 
33
+ /**
34
+ * Parse a `<tags>` child element (if present) into a flat string array.
35
+ *
36
+ * Used by the #236 sync bridge when a schedule response exposes
37
+ * per-layout tags (e.g. `<tag>xp-sync-group:NAME</tag>`). Upstream
38
+ * Xibo CMS does NOT currently emit `<tags>` inside schedule `<layout>`
39
+ * entries — the authoritative source is the XLF file itself, parsed
40
+ * by renderer-lite.parseXlf(). This helper exists so that:
41
+ * 1. A forked CMS that DOES emit per-layout `<tags>` (or future
42
+ * upstream) gets them surfaced for free.
43
+ * 2. Callers can treat `layout.tags` as a stable field (empty array
44
+ * when absent).
45
+ *
46
+ * @param {Element} parentEl - Layout/campaign-layout element
47
+ * @returns {string[]} Tags parsed from a direct <tags> child, or [].
48
+ */
49
+ function parseLayoutTags(parentEl) {
50
+ const tags = [];
51
+ for (const child of parentEl.children) {
52
+ if (child.tagName !== 'tags') continue;
53
+ for (const tagEl of child.children) {
54
+ if (tagEl.tagName !== 'tag') continue;
55
+ const text = (tagEl.textContent || '').trim();
56
+ if (text) tags.push(text);
57
+ }
58
+ }
59
+ return tags;
60
+ }
61
+
33
62
  /**
34
63
  * Parse Schedule XML response into a normalized schedule object.
35
64
  *
@@ -121,7 +150,8 @@ export function parseScheduleResponse(xml) {
121
150
  groupKey: layoutEl.getAttribute('groupKey') || null,
122
151
  playCount: parseInt(layoutEl.getAttribute('playCount') || '0'),
123
152
  dependants: depEls.length > 0 ? [...depEls].map(el => el.textContent) : [],
124
- criteria: parseCriteria(layoutEl)
153
+ criteria: parseCriteria(layoutEl),
154
+ tags: parseLayoutTags(layoutEl) // #236 sync bridge (currently no-op upstream; forks may emit)
125
155
  });
126
156
  }
127
157
 
@@ -154,7 +184,8 @@ export function parseScheduleResponse(xml) {
154
184
  recurrenceRepeatsOn: layoutEl.getAttribute('recurrenceRepeatsOn') || null,
155
185
  recurrenceRange: layoutEl.getAttribute('recurrenceRange') || null,
156
186
  dependants: depEls.length > 0 ? [...depEls].map(el => el.textContent) : [],
157
- criteria: parseCriteria(layoutEl)
187
+ criteria: parseCriteria(layoutEl),
188
+ tags: parseLayoutTags(layoutEl) // #236 sync bridge (currently no-op upstream; forks may emit)
158
189
  });
159
190
  }
160
191
 
@@ -264,6 +264,82 @@ describe('parseScheduleResponse', () => {
264
264
  expect(layout.playCount).toBe(2);
265
265
  });
266
266
 
267
+ // ── Layout tag surfacing (#236 sync bridge) ───────────────────
268
+ //
269
+ // Upstream Xibo CMS does NOT currently emit <tags> as a child of
270
+ // <layout> in Schedule XMDS responses (only <dependants>,
271
+ // <criteria>). The authoritative source for layout tags is the XLF
272
+ // file itself, parsed by renderer-lite. These tests exercise the
273
+ // defensive path for forks / future upstream that DO emit <tags>,
274
+ // and document that the default is an empty array.
275
+
276
+ describe('layout tag surfacing (#236)', () => {
277
+ it('defaults tags to empty array on standalone layouts', () => {
278
+ const xml = `<schedule>
279
+ <layout file="100.xlf" fromdt="2025-01-01 00:00:00" todt="2025-12-31 23:59:59"
280
+ scheduleid="1" priority="0"/>
281
+ </schedule>`;
282
+ const result = parseScheduleResponse(xml);
283
+ expect(result.layouts[0].tags).toEqual([]);
284
+ });
285
+
286
+ it('defaults tags to empty array on campaign layouts', () => {
287
+ const xml = `<schedule>
288
+ <campaign id="c1" priority="5" fromdt="2025-01-01 00:00:00" todt="2025-12-31 23:59:59"
289
+ scheduleid="30">
290
+ <layout file="500.xlf"/>
291
+ </campaign>
292
+ </schedule>`;
293
+ const result = parseScheduleResponse(xml);
294
+ expect(result.campaigns[0].layouts[0].tags).toEqual([]);
295
+ });
296
+
297
+ it('parses <tags> when present on standalone layouts', () => {
298
+ const xml = `<schedule>
299
+ <layout file="700.xlf" fromdt="2025-01-01 00:00:00" todt="2025-12-31 23:59:59"
300
+ scheduleid="7" priority="0">
301
+ <tags>
302
+ <tag>xp-sync-group:lobby-wall</tag>
303
+ <tag>smil-imported</tag>
304
+ </tags>
305
+ </layout>
306
+ </schedule>`;
307
+ const result = parseScheduleResponse(xml);
308
+ expect(result.layouts[0].tags).toEqual([
309
+ 'xp-sync-group:lobby-wall',
310
+ 'smil-imported',
311
+ ]);
312
+ });
313
+
314
+ it('parses <tags> when present on campaign layouts', () => {
315
+ const xml = `<schedule>
316
+ <campaign id="c1" priority="5" fromdt="2025-01-01 00:00:00" todt="2025-12-31 23:59:59"
317
+ scheduleid="30">
318
+ <layout file="500.xlf">
319
+ <tags><tag>xp-sync-group:atrium</tag></tags>
320
+ </layout>
321
+ </campaign>
322
+ </schedule>`;
323
+ const result = parseScheduleResponse(xml);
324
+ expect(result.campaigns[0].layouts[0].tags).toEqual(['xp-sync-group:atrium']);
325
+ });
326
+
327
+ it('skips empty and whitespace-only <tag> elements', () => {
328
+ const xml = `<schedule>
329
+ <layout file="800.xlf" fromdt="2025-01-01 00:00:00" todt="2025-12-31 23:59:59"
330
+ scheduleid="8" priority="0">
331
+ <tags>
332
+ <tag> xp-sync-group:lobby </tag>
333
+ <tag></tag>
334
+ <tag> </tag>
335
+ </tags>
336
+ </layout>
337
+ </schedule>`;
338
+ const result = parseScheduleResponse(xml);
339
+ expect(result.layouts[0].tags).toEqual(['xp-sync-group:lobby']);
340
+ });
341
+ });
342
+
267
343
  it('should use consistent lowercase casing for fromdt/todt/scheduleid on overlays', () => {
268
344
  const xml = `<schedule>
269
345
  <overlays>