518 lines
14 KiB
JavaScript
518 lines
14 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 pairing link uses the clients pairing permission', () => {
|
|
const html = renderDashboardPage(
|
|
{
|
|
screens: [
|
|
{
|
|
public_base_url: 'http://player.local/'
|
|
}
|
|
],
|
|
playlists: [],
|
|
clients: [],
|
|
slides: [],
|
|
connectedClientsCount: 0,
|
|
connectedPlayersCount: 0
|
|
},
|
|
'',
|
|
{ id: 1, permissions: ['screens.read', 'clients.read', 'pairing.allow'] }
|
|
);
|
|
|
|
assert.match(html, /href="\/pairing"/);
|
|
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 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, [data-mobile-client-name]' || 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], article[data-mobile-client-key]' || 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-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(clientNameInput.value, 'Lobby Client');
|
|
assert.equal(playerBaseUrlInput.value, 'http://player.local');
|
|
});
|
|
|
|
test('dashboard screen group controls stay disabled until a group is selected', () => {
|
|
const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8');
|
|
const pauseInput = { value: 'true' };
|
|
const blackoutInput = { value: 'true' };
|
|
const reloadButton = {
|
|
disabled: false,
|
|
innerHTML: '',
|
|
setAttribute() {},
|
|
classList: { add() {}, remove() {} }
|
|
};
|
|
const pauseButton = {
|
|
disabled: false,
|
|
innerHTML: '',
|
|
setAttribute() {},
|
|
classList: { add() {}, remove() {} }
|
|
};
|
|
const blackoutButton = {
|
|
disabled: false,
|
|
innerHTML: '',
|
|
setAttribute() {},
|
|
classList: { add() {}, remove() {} }
|
|
};
|
|
const forms = [
|
|
{
|
|
getAttribute(name) {
|
|
if (name === 'data-screen-command-action') {
|
|
return 'reload';
|
|
}
|
|
return '';
|
|
},
|
|
setAttribute() {},
|
|
querySelector(selector) {
|
|
if (selector === 'button[type="submit"]') {
|
|
return reloadButton;
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
{
|
|
getAttribute(name) {
|
|
if (name === 'data-screen-command-action') {
|
|
return 'pause';
|
|
}
|
|
return '';
|
|
},
|
|
setAttribute() {},
|
|
querySelector(selector) {
|
|
if (selector === 'button[type="submit"]') {
|
|
return pauseButton;
|
|
}
|
|
if (selector === 'input[name="paused"]') {
|
|
return pauseInput;
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
{
|
|
getAttribute(name) {
|
|
if (name === 'data-screen-command-action') {
|
|
return 'blackout';
|
|
}
|
|
return '';
|
|
},
|
|
setAttribute() {},
|
|
querySelector(selector) {
|
|
if (selector === 'button[type="submit"]') {
|
|
return blackoutButton;
|
|
}
|
|
if (selector === 'input[name="blackout"]') {
|
|
return blackoutInput;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
];
|
|
const select = {
|
|
value: '',
|
|
options: [
|
|
{ textContent: 'Select Screen Group', getAttribute() { return ''; } },
|
|
{ textContent: 'Lobby', getAttribute(name) { return name === 'data-screen-name' ? 'Lobby' : ''; } },
|
|
{ textContent: 'All Screens', getAttribute(name) { return name === 'data-screen-name' ? 'All screens' : ''; } }
|
|
],
|
|
selectedIndex: 0,
|
|
dataset: {},
|
|
addEventListener(type, handler) {
|
|
if (type === 'change') {
|
|
this.changeHandler = handler;
|
|
}
|
|
}
|
|
};
|
|
const context = {
|
|
document: {
|
|
body: {
|
|
classList: {
|
|
toggle() {},
|
|
add() {},
|
|
remove() {}
|
|
}
|
|
},
|
|
getElementById(id) {
|
|
if (id === 'screen-command-select') {
|
|
return select;
|
|
}
|
|
if (id === 'dashboard-clients-table') {
|
|
return { getAttribute() { return 'false'; } };
|
|
}
|
|
if (id === 'dashboard-clients-table-body') {
|
|
return null;
|
|
}
|
|
return null;
|
|
},
|
|
querySelector(selector) {
|
|
if (selector === '[data-screen-command-select]') {
|
|
return select;
|
|
}
|
|
return null;
|
|
},
|
|
querySelectorAll(selector) {
|
|
if (selector === '[data-screen-command-form]') {
|
|
return forms;
|
|
}
|
|
return [];
|
|
},
|
|
addEventListener() {}
|
|
},
|
|
window: {
|
|
location: {
|
|
search: '',
|
|
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);
|
|
|
|
assert.equal(reloadButton.disabled, true);
|
|
assert.equal(pauseButton.disabled, true);
|
|
assert.equal(blackoutButton.disabled, true);
|
|
|
|
select.value = 'lobby';
|
|
select.selectedIndex = 1;
|
|
select.changeHandler();
|
|
|
|
assert.equal(reloadButton.disabled, false);
|
|
assert.equal(pauseButton.disabled, false);
|
|
assert.equal(blackoutButton.disabled, false);
|
|
});
|