Files
pulse-signage/test/player-announcement-marquee.test.js
T
lzstealth c3299abaac
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s
Release v2.14.1
2026-10-11 18:54:43 +01:00

187 lines
5.2 KiB
JavaScript

const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const test = require('node:test');
const assert = require('node:assert/strict');
function loadAnnouncementTemplateHelpers() {
const sharedScriptPath = path.join(__dirname, '..', 'src', 'player', 'rendering-shared.js');
const scriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-announcement-templates.js');
const sharedScript = fs.readFileSync(sharedScriptPath, 'utf8');
const script = fs.readFileSync(scriptPath, 'utf8');
const sandbox = {
window: {},
console
};
sandbox.window = sandbox;
vm.runInNewContext(sharedScript, sandbox, { filename: sharedScriptPath });
vm.runInNewContext(script, sandbox, { filename: scriptPath });
return sandbox;
}
function createTextNode(width) {
return {
width: width,
attributes: {},
cloneNode() {
return createTextNode(width);
},
setAttribute(name, value) {
this.attributes[name] = String(value);
},
getBoundingClientRect() {
return { width: this.width };
}
};
}
test('announcement marquee helper repeats enough content and measures a real cycle width', () => {
const sandbox = loadAnnouncementTemplateHelpers();
assert.equal(typeof sandbox.initPlayerAnnouncementMarquee, 'function');
const textNodes = [createTextNode(100), createTextNode(100)];
const track = {
dataset: {},
style: {
values: {},
setProperty(name, value) {
this.values[name] = String(value);
}
},
attributes: { 'data-announcement-marquee-rate': '50' },
parentElement: null,
appended: [],
getAttribute(name) {
return this.attributes[name];
},
querySelectorAll() {
return textNodes.slice();
},
appendChild(node) {
this.appended.push(node);
textNodes.push(node);
return node;
},
getBoundingClientRect() {
const gap = 20;
const width = textNodes.length * 100 + Math.max(0, textNodes.length - 1) * gap;
return { width: width };
}
};
const messageContainer = {
getBoundingClientRect() {
return { width: 420 };
}
};
sandbox.window.getComputedStyle = function () {
return { columnGap: '20px', gap: '20px' };
};
sandbox.initPlayerAnnouncementMarquee(track, { container: messageContainer });
assert.equal(track.dataset.marqueeMeasured, 'true');
assert.equal(track.style.values['--announcement-scroll-distance'], '120.00px');
assert.equal(track.style.values['--announcement-scroll-duration'], '8.00s');
assert.equal(track.style.animationDuration, '8.00s');
assert.ok(track.appended.length >= 3);
});
test('announcement markup strips line breaks from the message', () => {
const sandbox = loadAnnouncementTemplateHelpers();
sandbox.playerAnnouncementTemplates = {
topBanner: '<div class="message">{{MESSAGE}}</div>'
};
const markup = sandbox.renderPlayerAnnouncementMarkup({
announcement_type: 'top-banner',
message: 'First line\nSecond line\r\nThird line',
short_label: 'Breaking News',
color_key: 'primary',
icon_key: 'info-circle'
});
assert.match(markup, /First line Second line Third line/);
assert.doesNotMatch(markup, /<br\s*\/?\s*>/i);
});
test('announcement fallback polling stops when the websocket opens and resumes when it closes', () => {
const intervals = new Map();
const listeners = new Map();
let nextTimerId = 1;
let announcementSocket = null;
let announcementRequests = 0;
class MockWebSocket {
constructor() {
this.readyState = MockWebSocket.CONNECTING;
announcementSocket = this;
}
open() {
this.readyState = MockWebSocket.OPEN;
if (this.onopen) this.onopen();
}
close() {
this.readyState = MockWebSocket.CLOSED;
if (this.onclose) this.onclose();
}
}
MockWebSocket.CONNECTING = 0;
MockWebSocket.OPEN = 1;
MockWebSocket.CLOSED = 3;
class MockXhr {
open() {}
setRequestHeader() {}
send() { announcementRequests += 1; }
}
const window = {
slug: 'test',
location: { origin: 'http://localhost:8081' },
WebSocket: MockWebSocket,
setInterval(callback, delay) {
const id = nextTimerId++;
intervals.set(id, { callback: callback, delay: delay });
return id;
},
clearInterval(id) { intervals.delete(id); },
setTimeout() { return nextTimerId++; },
clearTimeout() {},
addEventListener() {}
};
const sandbox = {
window: window,
document: {
readyState: 'loading',
addEventListener(name, callback) { listeners.set(name, callback); }
},
WebSocket: MockWebSocket,
XMLHttpRequest: MockXhr,
URL,
Date,
JSON,
String,
Array,
Math
};
const scriptPath = path.join(__dirname, '..', 'src', 'player', 'player-page-announcements.js');
vm.runInNewContext(fs.readFileSync(scriptPath, 'utf8'), sandbox, { filename: scriptPath });
listeners.get('DOMContentLoaded')();
assert.equal(announcementRequests, 0);
assert.equal(intervals.size, 1);
assert.equal(Array.from(intervals.values())[0].delay, 15000);
announcementSocket.open();
assert.equal(announcementRequests, 1);
assert.equal(intervals.size, 0);
announcementSocket.close();
assert.equal(intervals.size, 1);
});