358 lines
10 KiB
JavaScript
358 lines
10 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const vm = require('node:vm');
|
|
|
|
require('../src/common');
|
|
|
|
const renderDashboardPage = require('../src/web/routes/signage/dashboard');
|
|
|
|
test('dashboard onboarding link uses the player base url', () => {
|
|
const html = renderDashboardPage(
|
|
{
|
|
screens: [
|
|
{
|
|
public_base_url: 'http://player.local/'
|
|
}
|
|
],
|
|
playlists: [],
|
|
clients: [],
|
|
slides: [],
|
|
connectedClientsCount: 0,
|
|
connectedPlayersCount: 0
|
|
},
|
|
'',
|
|
{ id: 1, permissions: ['screens.read', 'clients.read'] }
|
|
);
|
|
|
|
assert.match(html, /href="http:\/\/player\.local"/);
|
|
assert.doesNotMatch(html, /http:\/\/player\.local\//);
|
|
});
|
|
|
|
test('dashboard screen snapshot omits player links and duplicate connection counts', () => {
|
|
const html = renderDashboardPage(
|
|
{
|
|
screens: [
|
|
{
|
|
id: 7,
|
|
name: 'Demo Conference',
|
|
slug: 'demo-conference',
|
|
playlist_name: 'Main Loop',
|
|
player_connection_count: 3,
|
|
public_base_url: 'http://player.local/'
|
|
}
|
|
],
|
|
playlists: [],
|
|
clients: [],
|
|
slides: [],
|
|
connectedClientsCount: 3,
|
|
connectedPlayersCount: 3
|
|
},
|
|
'',
|
|
{ id: 1, permissions: ['screens.read', 'clients.read'] }
|
|
);
|
|
|
|
assert.match(html, /Screen group snapshot/);
|
|
assert.match(html, /players/);
|
|
assert.doesNotMatch(html, /dashboard-screen-link/);
|
|
assert.doesNotMatch(html, /Connections<\/dt>/);
|
|
assert.match(html, /3\s+live/);
|
|
assert.doesNotMatch(html, />3 connected/);
|
|
});
|
|
|
|
test('dashboard kiosk launcher includes connected player choices', () => {
|
|
const html = renderDashboardPage(
|
|
{
|
|
screens: [],
|
|
playlists: [],
|
|
kioskPlayers: [
|
|
{
|
|
player_identifier: 'player-alpha',
|
|
player_url: 'http://player-a.example'
|
|
},
|
|
{
|
|
player_identifier: 'player-beta',
|
|
player_url: 'http://player-b.example'
|
|
}
|
|
],
|
|
slides: [],
|
|
connectedClientsCount: 1,
|
|
connectedPlayersCount: 1
|
|
},
|
|
'',
|
|
{ id: 1, permissions: ['screens.read', 'clients.read', 'dashboard.allow'] }
|
|
);
|
|
|
|
assert.match(html, /dashboard-kiosk-launcher-player-select/);
|
|
assert.match(html, /<option value="http:\/\/player-a\.example" data-player-identifier="player-alpha">player-alpha - http:\/\/player-a\.example<\/option>/);
|
|
assert.match(html, /<option value="http:\/\/player-b\.example" data-player-identifier="player-beta">player-beta - http:\/\/player-b\.example<\/option>/);
|
|
assert.match(html, /data-kiosk-launcher-download-base="\/downloads\/kiosk\/pulse-signage-kiosk\.bat"/);
|
|
});
|
|
|
|
test('dashboard kiosk launcher requires both confirmation and a player selection', () => {
|
|
const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8');
|
|
const downloadLink = {
|
|
classList: {
|
|
classes: new Set(['disabled']),
|
|
add(name) { this.classes.add(name); },
|
|
remove(name) { this.classes.delete(name); },
|
|
contains(name) { return this.classes.has(name); }
|
|
},
|
|
attributes: {
|
|
href: '',
|
|
'data-kiosk-launcher-download-base': '/downloads/kiosk/pulse-signage-kiosk.bat',
|
|
'aria-disabled': 'true',
|
|
tabindex: '-1'
|
|
},
|
|
setAttribute(name, value) {
|
|
this.attributes[name] = String(value);
|
|
},
|
|
getAttribute(name) {
|
|
return Object.prototype.hasOwnProperty.call(this.attributes, name) ? this.attributes[name] : '';
|
|
},
|
|
removeAttribute(name) {
|
|
delete this.attributes[name];
|
|
}
|
|
};
|
|
const checkbox = {
|
|
checked: false,
|
|
listeners: {},
|
|
addEventListener(type, handler) {
|
|
this.listeners[type] = handler;
|
|
}
|
|
};
|
|
const select = {
|
|
value: '',
|
|
options: [
|
|
{ value: '', textContent: 'Select a player' },
|
|
{ value: 'http://player-a.example', textContent: 'player-alpha' }
|
|
],
|
|
listeners: {},
|
|
innerHTML: '',
|
|
addEventListener(type, handler) {
|
|
this.listeners[type] = handler;
|
|
}
|
|
};
|
|
const modal = {
|
|
querySelectorAll(selector) {
|
|
return selector === '[data-kiosk-launcher-download]' ? [downloadLink] : [];
|
|
},
|
|
querySelector(selector) {
|
|
if (selector === '[data-kiosk-launcher-confirm]') {
|
|
return checkbox;
|
|
}
|
|
if (selector === '[data-kiosk-launcher-player-select]') {
|
|
return select;
|
|
}
|
|
return null;
|
|
},
|
|
addEventListener(type, handler) {
|
|
this.listeners = this.listeners || {};
|
|
this.listeners[type] = handler;
|
|
}
|
|
};
|
|
const context = {
|
|
document: {
|
|
getElementById(id) {
|
|
if (id === 'dashboard-kiosk-launcher-modal') {
|
|
return modal;
|
|
}
|
|
return null;
|
|
},
|
|
querySelector() {
|
|
return null;
|
|
}
|
|
},
|
|
window: {
|
|
webUiHelpers: {
|
|
escapeHtml(value) { return String(value); },
|
|
formatDashboardDate(value) { return String(value); },
|
|
getClientRowKey() { return ''; },
|
|
getClientDisplayName() { return ''; },
|
|
setButtonVariant() {},
|
|
normalizeDisplayIp(value) { return String(value); }
|
|
},
|
|
WebSocket: null,
|
|
location: {
|
|
protocol: 'http:',
|
|
host: 'example.test'
|
|
},
|
|
setTimeout() { return 1; },
|
|
clearTimeout() {},
|
|
alert() {},
|
|
prompt() {
|
|
return null;
|
|
},
|
|
webHandleDashboardState() {}
|
|
},
|
|
WebSocket: function MockWebSocket() {},
|
|
JSON: JSON,
|
|
Number: Number,
|
|
String: String,
|
|
Boolean: Boolean,
|
|
Array: Array,
|
|
Object: Object,
|
|
Math: Math,
|
|
Set: Set,
|
|
URLSearchParams: URLSearchParams,
|
|
FormData: function FormData() {},
|
|
setTimeout() {},
|
|
clearTimeout() {},
|
|
console: console
|
|
};
|
|
context.window.document = context.document;
|
|
context.window.WebSocket = context.WebSocket;
|
|
|
|
vm.runInNewContext(script, context);
|
|
|
|
assert.equal(downloadLink.classList.contains('disabled'), true);
|
|
assert.equal(Object.prototype.hasOwnProperty.call(downloadLink.attributes, 'href'), false);
|
|
|
|
checkbox.checked = true;
|
|
checkbox.listeners.change();
|
|
assert.equal(downloadLink.classList.contains('disabled'), true);
|
|
assert.equal(Object.prototype.hasOwnProperty.call(downloadLink.attributes, 'href'), false);
|
|
|
|
select.value = 'http://player-a.example';
|
|
select.listeners.change();
|
|
assert.equal(downloadLink.classList.contains('disabled'), false);
|
|
assert.equal(downloadLink.attributes.href, '/downloads/kiosk/pulse-signage-kiosk.bat?playerUrl=http%3A%2F%2Fplayer-a.example');
|
|
});
|
|
|
|
test('screen controls include an all screens option and update the target summary', () => {
|
|
const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8');
|
|
const select = {
|
|
value: '__all__',
|
|
selectedIndex: 2,
|
|
options: [
|
|
{ value: 'alpha', textContent: 'Alpha', getAttribute() { return null; } },
|
|
{ value: 'beta', textContent: 'Beta', getAttribute() { return null; } },
|
|
{ value: '__all__', textContent: 'All screens', getAttribute(name) { return name === 'data-screen-target-all' ? 'true' : null; } }
|
|
],
|
|
listeners: {},
|
|
addEventListener(type, handler) {
|
|
this.listeners[type] = handler;
|
|
}
|
|
};
|
|
const commandInput = { value: '' };
|
|
const pausedInput = { value: 'true' };
|
|
const button = {
|
|
innerHTML: '',
|
|
disabled: false
|
|
};
|
|
const form = {
|
|
dataset: {},
|
|
getAttribute(name) {
|
|
if (name === 'data-screen-command-action') {
|
|
return 'pause';
|
|
}
|
|
return this[name] || '';
|
|
},
|
|
querySelector(selector) {
|
|
if (selector === 'input[name="command"]') {
|
|
return commandInput;
|
|
}
|
|
if (selector === 'input[name="paused"]') {
|
|
return pausedInput;
|
|
}
|
|
if (selector === 'button[type="submit"]') {
|
|
return button;
|
|
}
|
|
return null;
|
|
},
|
|
querySelectorAll() {
|
|
return [button, commandInput, pausedInput];
|
|
},
|
|
setAttribute(name, value) {
|
|
this[name] = value;
|
|
},
|
|
action: ''
|
|
};
|
|
const pill = { classList: { toggle() {}, add() {}, remove() {} }, textContent: '' };
|
|
const nameNode = { textContent: '' };
|
|
const metaNode = { textContent: '' };
|
|
const context = {
|
|
document: {
|
|
getElementById(id) {
|
|
if (id === 'screen-command-select') {
|
|
return select;
|
|
}
|
|
return null;
|
|
},
|
|
querySelector(selector) {
|
|
if (selector === '[data-screen-command-pill]') {
|
|
return pill;
|
|
}
|
|
if (selector === '[data-screen-command-name]') {
|
|
return nameNode;
|
|
}
|
|
if (selector === '[data-screen-command-meta]') {
|
|
return metaNode;
|
|
}
|
|
return null;
|
|
},
|
|
querySelectorAll(selector) {
|
|
return selector === '[data-screen-command-form]' ? [form] : [];
|
|
}
|
|
},
|
|
window: {
|
|
webUiHelpers: {
|
|
escapeHtml(value) { return String(value); },
|
|
formatDashboardDate(value) { return String(value); },
|
|
getClientRowKey() { return ''; },
|
|
getClientDisplayName() { return ''; },
|
|
setButtonVariant() {},
|
|
normalizeDisplayIp(value) { return String(value); }
|
|
},
|
|
WebSocket: null,
|
|
location: {
|
|
protocol: 'http:',
|
|
host: 'example.test'
|
|
},
|
|
setTimeout() { return 1; },
|
|
clearTimeout() {},
|
|
alert() {},
|
|
prompt() {
|
|
return null;
|
|
},
|
|
webHandleDashboardState() {}
|
|
},
|
|
WebSocket: function MockWebSocket() {},
|
|
JSON: JSON,
|
|
Number: Number,
|
|
String: String,
|
|
Boolean: Boolean,
|
|
Array: Array,
|
|
Object: Object,
|
|
Math: Math,
|
|
Set: Set,
|
|
URLSearchParams: URLSearchParams,
|
|
FormData: function FormData() {},
|
|
setTimeout() {},
|
|
clearTimeout() {},
|
|
console: console
|
|
};
|
|
context.window.document = context.document;
|
|
context.window.WebSocket = context.WebSocket;
|
|
|
|
vm.runInNewContext(script, context);
|
|
|
|
context.window.webHandleDashboardState({
|
|
screens: [
|
|
{ slug: 'alpha', name: 'Alpha' },
|
|
{ slug: 'beta', name: 'Beta' }
|
|
],
|
|
clients: [
|
|
{ screen_slug: 'alpha', paused: true },
|
|
{ screen_slug: 'beta', paused: true }
|
|
]
|
|
});
|
|
|
|
assert.equal(form.action, '/clients/__all__/commands');
|
|
assert.equal(nameNode.textContent, 'All screens');
|
|
assert.equal(metaNode.textContent, 'Commands sent here target every client across every screen group.');
|
|
assert.match(button.innerHTML, /Resume all screens/);
|
|
assert.equal(commandInput.value, 'pause');
|
|
assert.equal(pausedInput.value, 'false');
|
|
});
|