Files
pulse-signage/test/web-dashboard-page.test.js
T
2026-08-08 13:02:31 +01:00

345 lines
9.6 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 client refresh respects the active search filter', () => {
const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8');
const tbody = {
innerHTML: '<tr><td>stale</td></tr>',
addEventListener() {}
};
const table = {
getAttribute(name) {
if (name === 'data-has-actions-column') {
return 'false';
}
return '';
}
};
const context = {
document: {
body: {
classList: {
toggle() {},
add() {},
remove() {}
}
},
getElementById(id) {
if (id === 'dashboard-clients-table') {
return table;
}
if (id === 'dashboard-clients-table-body') {
return tbody;
}
return null;
},
querySelector() {
return null;
},
querySelectorAll() {
return [];
}
},
window: {
location: {
search: '?search=beta',
protocol: 'http:',
host: 'example.test'
},
webUiHelpers: {
escapeHtml(value) { return String(value); },
formatDashboardDate(value) { return String(value); },
getClientRowKey(client) { return String(client && client.id || ''); },
getClientDisplayName(client) { return String(client && (client.client_name || client.name || client.clientId) || ''); },
setButtonVariant() {},
normalizeDisplayIp(value) { return String(value); }
},
WebSocket: null,
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() {}
};
context.window.document = context.document;
context.window.WebSocket = context.WebSocket;
vm.runInNewContext(script, context);
context.window.webHandleDashboardState({
clients: [
{ id: 'alpha', client_name: 'Alpha', clientId: 'alpha', screen_slug: 'alpha', clientIp: '10.0.0.1' },
{ id: 'beta', client_name: 'Beta', clientId: 'beta', screen_slug: 'beta', clientIp: '10.0.0.2' }
]
});
assert.match(tbody.innerHTML, /Beta/);
assert.doesNotMatch(tbody.innerHTML, /Alpha/);
});
test('dashboard move client button opens the move modal for the selected row', () => {
const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8');
const modal = {
listeners: {},
addEventListener(type, handler) {
this.listeners[type] = handler;
}
};
const form = {
dataset: {},
method: 'post',
action: '',
querySelector(selector) {
if (selector === 'button[type="submit"]') {
return { disabled: true };
}
return null;
},
addEventListener() {}
};
const connectionInput = { value: '' };
const deviceInput = { value: '' };
const clientNameInput = { value: '' };
const playerBaseUrlInput = { value: '' };
const targetSelect = { value: 'alpha' };
targetSelect.options = [
{ value: 'alpha', disabled: false },
{ value: 'beta', disabled: false }
];
const row = {
getAttribute(name) {
if (name === 'data-client-screen-slug') {
return 'alpha';
}
if (name === 'data-client-id') {
return 'conn-123';
}
if (name === 'data-client-device-id') {
return 'device-123';
}
if (name === 'data-client-player-base-url') {
return 'http://player.local';
}
return '';
},
querySelector(selector) {
if (selector === 'td[data-label="Client"] > div') {
return { textContent: 'Lobby Client' };
}
return null;
}
};
const moveButton = {
closest(selector) {
if (selector === 'button[data-action="move-screen"]') {
return this;
}
if (selector === 'tr[data-client-key]') {
return row;
}
return null;
}
};
const context = {
document: {
getElementById(id) {
if (id === 'client-move-screen-modal') {
return modal;
}
if (id === 'client-move-screen-form') {
return form;
}
if (id === 'client-move-screen-target') {
return targetSelect;
}
if (id === 'dashboard-clients-table') {
return { getAttribute() { return 'true'; } };
}
return null;
},
querySelector(selector) {
if (selector === '[data-client-move-connection-id]') {
return connectionInput;
}
if (selector === '[data-client-move-device-id]') {
return deviceInput;
}
if (selector === '[data-client-move-client-name]') {
return clientNameInput;
}
if (selector === '[data-client-move-player-base-url]') {
return playerBaseUrlInput;
}
return null;
},
querySelectorAll() {
return [];
},
addEventListener(type, handler) {
if (type === 'click') {
this.clickHandler = handler;
}
}
},
window: {
location: {
protocol: 'http:',
host: 'example.test'
},
webUiHelpers: {
escapeHtml(value) { return String(value); },
formatDashboardDate(value) { return String(value); },
getClientRowKey(client) { return String(client && client.id || ''); },
getClientDisplayName(client) { return String(client && (client.client_name || client.name || client.clientId) || ''); },
setButtonVariant() {},
normalizeDisplayIp(value) { return String(value); }
},
WebSocket: null,
setTimeout() { return 1; },
clearTimeout() {},
alert() {},
prompt() {
return null;
},
pulseModal: {
show(element) {
element.__shown = true;
}
},
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() {}
};
context.window.document = context.document;
context.window.WebSocket = context.WebSocket;
vm.runInNewContext(script, context);
assert.equal(typeof context.document.clickHandler, 'function');
context.document.clickHandler({
target: moveButton,
preventDefault() {}
});
assert.equal(modal.__shown, true);
assert.equal(form.action, '/clients/alpha/commands');
assert.equal(targetSelect.options[0].disabled, true);
assert.equal(targetSelect.options[1].disabled, false);
assert.equal(connectionInput.value, 'conn-123');
assert.equal(deviceInput.value, 'device-123');
assert.equal(clientNameInput.value, 'Lobby Client');
assert.equal(playerBaseUrlInput.value, 'http://player.local');
});