649 lines
22 KiB
JavaScript
649 lines
22 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 loadScript(scriptPath, sandbox) {
|
|
const source = fs.readFileSync(scriptPath, 'utf8');
|
|
vm.runInNewContext(source, sandbox, { filename: scriptPath });
|
|
}
|
|
|
|
function loadHtmlScript(scriptPath, sandbox) {
|
|
const source = fs.readFileSync(scriptPath, 'utf8').match(/<script>([\s\S]*)<\/script>/);
|
|
if (!source) {
|
|
throw new Error(`Unable to extract script body from ${scriptPath}`);
|
|
}
|
|
const script = source[1].trim();
|
|
vm.runInNewContext(script, sandbox, { filename: scriptPath });
|
|
}
|
|
|
|
test('media preload cleanup releases unpromoted video elements on every early exit', () => {
|
|
for (const reason of ['empty', 'single', 'missing-slide', 'missing-renderer', 'missing-document']) {
|
|
const calls = [];
|
|
const sandbox = {
|
|
window: {},
|
|
mediaPreloadSignature: 'old-signature',
|
|
mediaPreloadElements: [{
|
|
tagName: 'VIDEO',
|
|
pause() { calls.push('pause'); },
|
|
removeAttribute(name) { calls.push('remove:' + name); },
|
|
load() { calls.push('load'); }
|
|
}],
|
|
document: {},
|
|
primeSlideMarkup() {}
|
|
};
|
|
const slides = reason === 'empty' ? [] : reason === 'single' ? [{ id: 1 }] : [{ id: 1 }, { id: 2 }];
|
|
if (reason === 'missing-renderer') delete sandbox.primeSlideMarkup;
|
|
if (reason === 'missing-document') delete sandbox.document;
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
sandbox.syncSlideMediaPreloads(slides, reason === 'missing-slide' ? 0.5 : 0);
|
|
assert.deepEqual(calls, ['pause', 'remove:src', 'load'], reason);
|
|
assert.equal(sandbox.mediaPreloadSignature, '');
|
|
assert.equal(sandbox.mediaPreloadElements.length, 0);
|
|
sandbox.syncSlideMediaPreloads([], 0);
|
|
assert.equal(calls.length, 3);
|
|
}
|
|
});
|
|
|
|
test('playlist groups rotate in order, fill every pass, and optionally shuffle', () => {
|
|
const sandbox = {
|
|
window: { pulsePlayerRenderingShared: new Proxy({}, { get() { return function () {}; } }) },
|
|
playlistPassNumber: 0
|
|
};
|
|
const renderingScriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-rendering.js');
|
|
const playlistScriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js');
|
|
loadScript(renderingScriptPath, sandbox);
|
|
loadScript(playlistScriptPath, sandbox);
|
|
|
|
const groupSlides = Array.from({ length: 5 }, function (_value, index) {
|
|
return { id: index + 1, groupKey: 'news', groupPickCount: 2 };
|
|
});
|
|
sandbox.slides = groupSlides.concat([{ id: 99 }]);
|
|
|
|
function selectedGroupIds() {
|
|
return Array.from(sandbox.getActiveSlidesFrom(sandbox.slides)
|
|
.filter(function (slide) { return slide.groupKey; })
|
|
.map(function (slide) { return slide.id; }));
|
|
}
|
|
|
|
const firstPass = selectedGroupIds();
|
|
assert.equal(firstPass.length, 2);
|
|
assert.deepEqual(selectedGroupIds(), firstPass);
|
|
sandbox.playlistPassNumber = 1;
|
|
const secondPass = selectedGroupIds();
|
|
assert.equal(secondPass.length, 2);
|
|
assert.equal(firstPass.some(function (id) { return secondPass.includes(id); }), false);
|
|
sandbox.playlistPassNumber = 2;
|
|
const thirdPass = selectedGroupIds();
|
|
assert.equal(thirdPass.length, 2);
|
|
assert.equal(new Set(firstPass.concat(secondPass, thirdPass)).size, 5);
|
|
assert.deepEqual(firstPass, [1, 2]);
|
|
assert.deepEqual(secondPass, [3, 4]);
|
|
assert.deepEqual(thirdPass, [5, 1]);
|
|
groupSlides.forEach(slide => { slide.groupShuffle = true; });
|
|
const shuffled = selectedGroupIds();
|
|
assert.equal(shuffled.length, 2);
|
|
assert.equal(new Set(shuffled).size, 2);
|
|
assert.deepEqual(selectedGroupIds(), shuffled);
|
|
const selections = new Set();
|
|
for (let pass = 0; pass < 10; pass += 1) {
|
|
sandbox.playlistPassNumber = pass;
|
|
const selection = selectedGroupIds();
|
|
assert.equal(selection.length, 2);
|
|
assert.equal(new Set(selection).size, 2);
|
|
selections.add(selection.join(','));
|
|
}
|
|
assert.ok(selections.size > 1);
|
|
sandbox.slides = groupSlides.slice(0, 3);
|
|
sandbox.slides.forEach(slide => { slide.groupShuffle = false; });
|
|
for (const [pass, expected] of [[0, [1, 2]], [1, [3, 1]], [2, [2, 3]]]) {
|
|
sandbox.playlistPassNumber = pass;
|
|
assert.deepEqual(selectedGroupIds(), expected);
|
|
}
|
|
sandbox.slides[0].scheduleRules = [{ start_datetime: '2000-01-01T00:00:00Z', end_datetime: '2000-01-02T00:00:00Z' }];
|
|
assert.deepEqual(selectedGroupIds(), [2, 3]);
|
|
sandbox.slides[1].groupPickCount = 99;
|
|
assert.equal(selectedGroupIds().length, 2);
|
|
sandbox.slides[2].scheduleRules = sandbox.slides[0].scheduleRules;
|
|
assert.deepEqual(selectedGroupIds(), [2]);
|
|
|
|
const now = new Date(2026, 9, 14, 10, 30);
|
|
const gatedSlide = {
|
|
groupKey: 'weekday',
|
|
groupScheduleRules: [{ start_time: '10:00', end_time: '11:00', days: [now.getDay()] }]
|
|
};
|
|
assert.equal(sandbox.isSlideActive(gatedSlide, now), true);
|
|
assert.equal(sandbox.isSlideActive(gatedSlide, new Date(2026, 9, 14, 12, 30)), false);
|
|
assert.equal(sandbox.isSlideActive(gatedSlide, new Date(2026, 9, 15, 10, 30)), false);
|
|
|
|
const databaseRuleSlide = {
|
|
scheduleRules: [{ start_time: '10:00', end_time: '11:00', schedule_days_json: '[3]' }]
|
|
};
|
|
assert.equal(sandbox.isSlideActive(databaseRuleSlide, now), true);
|
|
assert.equal(sandbox.isSlideActive(databaseRuleSlide, new Date(2026, 9, 15, 10, 30)), false);
|
|
|
|
const startBoundSlide = { scheduleRules: [{ start_datetime: '2026-10-14T11:00' }] };
|
|
assert.equal(sandbox.isSlideActive(startBoundSlide, now), false);
|
|
assert.equal(sandbox.isSlideActive(startBoundSlide, new Date(2026, 9, 14, 11, 0)), true);
|
|
|
|
const endBoundSlide = { scheduleRules: [{ end_datetime: '2026-10-14T10:00' }] };
|
|
assert.equal(sandbox.isSlideActive(endBoundSlide, now), false);
|
|
assert.equal(sandbox.isSlideActive(endBoundSlide, new Date(2026, 9, 14, 10, 0)), true);
|
|
});
|
|
|
|
test('repeat groups play all eligible members in order and ignore Pick and shuffle', () => {
|
|
const sandbox = {
|
|
window: { pulsePlayerRenderingShared: new Proxy({}, { get() { return function () {}; } }) },
|
|
playlistPassNumber: 0
|
|
};
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-rendering.js'), sandbox);
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
sandbox.slides = [{ id: 9 }].concat([1, 2, 3].map(id => ({
|
|
id, groupKey: 'news', groupMode: 'repeat', groupRepeatCount: 2, groupPickCount: 1, groupShuffle: true
|
|
})), [{ id: 10 }]);
|
|
const ids = () => Array.from(sandbox.getActiveSlidesFrom(sandbox.slides), slide => slide.id);
|
|
assert.deepEqual(ids(), [9, 1, 2, 3, 1, 2, 3, 10]);
|
|
sandbox.playlistPassNumber = 1;
|
|
assert.deepEqual(ids(), [9, 1, 2, 3, 1, 2, 3, 10]);
|
|
assert.equal(sandbox.hasSelectableGroupVariants(), false);
|
|
sandbox.slides[2].scheduleRules = [{ start_datetime: '2000-01-01T00:00:00Z', end_datetime: '2000-01-02T00:00:00Z' }];
|
|
assert.deepEqual(ids(), [9, 1, 3, 1, 3, 10]);
|
|
sandbox.slides.forEach(slide => { if (slide.groupKey) slide.groupRepeatCount = 1; });
|
|
assert.deepEqual(ids(), [9, 1, 3, 10]);
|
|
});
|
|
|
|
test('warmed video node is promoted into its slide instead of fetched again', () => {
|
|
const createdVideos = [];
|
|
let nextSource = '';
|
|
const holder = {
|
|
innerHTML: '',
|
|
querySelectorAll() {
|
|
return [{ tagName: 'VIDEO', getAttribute(name) { return name === 'data-pulse-video-src' ? nextSource : null; } }];
|
|
}
|
|
};
|
|
const sandbox = {
|
|
window: { innerWidth: 1920, innerHeight: 1080, location: { origin: 'http://localhost' } },
|
|
URL,
|
|
currentPlaylistSignature: 'playlist',
|
|
mediaPreloadSignature: '',
|
|
mediaPreloadElements: [],
|
|
primeSlideMarkup(slide) {
|
|
nextSource = '/media/slide-' + slide.id + '.mp4';
|
|
return '<video src="' + nextSource + '"></video>';
|
|
},
|
|
document: {
|
|
createElement(tag) {
|
|
if (tag !== 'video') return holder;
|
|
const attributes = new Map();
|
|
const video = {
|
|
tagName: 'VIDEO',
|
|
preload: '',
|
|
muted: false,
|
|
src: '',
|
|
loadCalls: 0,
|
|
attributes,
|
|
setAttribute(name, value) { attributes.set(name, String(value)); },
|
|
getAttribute(name) { return attributes.get(name) || null; },
|
|
removeAttribute(name) { attributes.delete(name); },
|
|
load() { this.loadCalls += 1; },
|
|
pause() {}
|
|
};
|
|
createdVideos.push(video);
|
|
return video;
|
|
}
|
|
}
|
|
};
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
|
|
const slides = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
sandbox.syncSlideMediaPreloads(slides, 0);
|
|
const warmedVideo = createdVideos[0];
|
|
assert.equal(warmedVideo.src, 'http://localhost/media/slide-2.mp4');
|
|
|
|
let promotedNode = null;
|
|
const activeVideo = {
|
|
tagName: 'VIDEO',
|
|
src: 'http://localhost/media/slide-2.mp4',
|
|
attributes: [{ name: 'class', value: 'video' }, { name: 'data-pulse-video-src', value: '/media/slide-2.mp4' }],
|
|
getAttribute(name) { return name === 'data-pulse-video-src' ? '/media/slide-2.mp4' : null; },
|
|
parentNode: {
|
|
replaceChild(replacement, oldNode) {
|
|
assert.equal(oldNode, activeVideo);
|
|
promotedNode = replacement;
|
|
}
|
|
}
|
|
};
|
|
const root = { querySelectorAll() { return [activeVideo]; } };
|
|
|
|
assert.equal(sandbox.promotePreloadedVideoElements(root, 2), 1);
|
|
assert.equal(promotedNode, warmedVideo);
|
|
assert.equal(warmedVideo.loadCalls, 1);
|
|
|
|
sandbox.syncSlideMediaPreloads(slides, 1);
|
|
assert.equal(createdVideos[1].src, 'http://localhost/media/slide-3.mp4');
|
|
assert.notEqual(sandbox.mediaPreloadElements[0], warmedVideo);
|
|
});
|
|
|
|
test('media preloads next-slide images without creating duplicate video loads', () => {
|
|
const calls = [];
|
|
const nextImage = {};
|
|
Object.defineProperty(nextImage, 'src', { set(value) { calls.push(value); } });
|
|
const holder = {
|
|
innerHTML: '',
|
|
querySelectorAll(selector) {
|
|
assert.equal(selector, 'img[src], video[data-pulse-video-src]');
|
|
return [{ tagName: 'IMG', getAttribute() { return '/media/next.jpg'; } }];
|
|
}
|
|
};
|
|
const sandbox = {
|
|
window: { innerWidth: 1920, innerHeight: 1080, location: { origin: 'http://localhost' } },
|
|
URL,
|
|
currentPlaylistSignature: 'playlist',
|
|
mediaPreloadSignature: '',
|
|
mediaPreloadElements: [],
|
|
primeSlideMarkup() { return '<img src="/media/next.jpg"><video src="/media/next.mp4"></video>'; },
|
|
Image: function () { return nextImage; },
|
|
document: {
|
|
createElement() { return holder; }
|
|
}
|
|
};
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
const slides = [{ id: 1 }, { id: 2 }];
|
|
sandbox.syncSlideMediaPreloads(slides, 0);
|
|
assert.deepEqual(calls, ['http://localhost/media/next.jpg']);
|
|
assert.equal(sandbox.mediaPreloadElements[0], nextImage);
|
|
sandbox.syncSlideMediaPreloads(slides, 0);
|
|
assert.equal(calls.length, 1);
|
|
});
|
|
|
|
test('media preloader keeps a warmed next-slide video node for promotion', () => {
|
|
const preloadedVideo = {
|
|
tagName: 'VIDEO',
|
|
loadCalls: 0,
|
|
load() { this.loadCalls += 1; }
|
|
};
|
|
const sourceVideo = { tagName: 'VIDEO', getAttribute() { return '/media/next.mp4'; } };
|
|
const holder = {
|
|
innerHTML: '',
|
|
querySelectorAll(selector) {
|
|
assert.equal(selector, 'img[src], video[data-pulse-video-src]');
|
|
return [sourceVideo];
|
|
}
|
|
};
|
|
const sandbox = {
|
|
window: { innerWidth: 1920, innerHeight: 1080, location: { origin: 'http://localhost' } },
|
|
URL,
|
|
currentPlaylistSignature: 'playlist',
|
|
mediaPreloadSignature: '',
|
|
mediaPreloadElements: [],
|
|
primeSlideMarkup() { return '<video src="/media/next.mp4"></video>'; },
|
|
document: {
|
|
createElement(tag) { return tag === 'video' ? preloadedVideo : holder; }
|
|
}
|
|
};
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
sandbox.syncSlideMediaPreloads([{ id: 1 }, { id: 2 }], 0);
|
|
|
|
assert.equal(preloadedVideo.src, 'http://localhost/media/next.mp4');
|
|
assert.equal(preloadedVideo.preload, 'auto');
|
|
assert.equal(preloadedVideo.loadCalls, 1);
|
|
assert.equal(sandbox.mediaPreloadElements[0], preloadedVideo);
|
|
assert.equal(preloadedVideo.__pulsePreloadSlideId, '2');
|
|
});
|
|
|
|
test('offline playlist snapshots preserve and apply the RTMP skip option', () => {
|
|
const values = new Map();
|
|
const sandbox = {
|
|
window: { localStorage: {
|
|
setItem(key, value) { values.set(key, value); },
|
|
getItem(key) { return values.get(key) || null; }
|
|
} },
|
|
playlistSnapshotStorageKey: 'snapshot',
|
|
normalizeSlide(slide) { return slide; },
|
|
slides: [],
|
|
currentPlaylistSignature: '',
|
|
currentPlaylistFadeBetweenSlides: false,
|
|
currentPlaylistSkipUnavailableRtmp: false,
|
|
currentPlaylistEtag: '',
|
|
pendingPlaylistUpdate: null,
|
|
activeSlidesCacheKey: '', activeSlidesCacheValue: [],
|
|
slideMarkupCache: {}, templateLayoutCache: {}, templateRenderPlanCache: {},
|
|
renderCacheViewportKey: '', index: 0
|
|
};
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
for (const skipUnavailableRtmp of [true, false]) {
|
|
sandbox.savePlaylistSnapshot({
|
|
slides: [{ id: 1 }], signature: 'revision', fadeBetweenSlides: true,
|
|
skipUnavailableRtmp, etag: '"etag"'
|
|
});
|
|
const snapshot = sandbox.loadPlaylistSnapshot();
|
|
assert.equal(snapshot.skipUnavailableRtmp, skipUnavailableRtmp);
|
|
assert.equal(sandbox.applyPlaylistSnapshot(snapshot), true);
|
|
assert.equal(sandbox.currentPlaylistSkipUnavailableRtmp, skipUnavailableRtmp);
|
|
assert.equal(sandbox.currentPlaylistFadeBetweenSlides, true);
|
|
assert.equal(sandbox.currentPlaylistEtag, '"etag"');
|
|
}
|
|
values.set('snapshot', JSON.stringify({ slides: [{ id: 1 }] }));
|
|
assert.equal(sandbox.loadPlaylistSnapshot().skipUnavailableRtmp, false);
|
|
});
|
|
|
|
test('webpage preloading only targets the next slide', () => {
|
|
const sandbox = {
|
|
window: null,
|
|
Date,
|
|
Array,
|
|
Number,
|
|
String,
|
|
Boolean,
|
|
Object,
|
|
Math,
|
|
console,
|
|
escapeHtml(value) {
|
|
return String(value || '');
|
|
},
|
|
currentPlaylistSignature: 'signature',
|
|
slides: [],
|
|
index: 0,
|
|
activeSlidesCacheKey: '',
|
|
activeSlidesCacheValue: [],
|
|
renderCacheViewportKey: '',
|
|
preloadSignature: '',
|
|
preloadContainer: null
|
|
};
|
|
sandbox.window = sandbox;
|
|
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
|
|
const current = { id: 1 };
|
|
const next = { id: 2 };
|
|
const later = { id: 3 };
|
|
|
|
assert.equal(sandbox.getWebpagePreloadSlides([current, next, later], 0).map((slide) => slide.id).join(','), '2');
|
|
assert.equal(sandbox.getWebpagePreloadSlides([current, next, later], 1).map((slide) => slide.id).join(','), '3');
|
|
assert.equal(sandbox.getWebpagePreloadSlides([current, next, later], 2).map((slide) => slide.id).join(','), '');
|
|
});
|
|
|
|
test('rtmp warmups only target the next slide', () => {
|
|
const sandbox = {
|
|
window: null,
|
|
Date,
|
|
Array,
|
|
Number,
|
|
String,
|
|
Boolean,
|
|
Object,
|
|
Math,
|
|
console,
|
|
fetch() {
|
|
throw new Error('not expected');
|
|
},
|
|
currentPlaylistSkipUnavailableRtmp: false,
|
|
videoRegionRenderVersion: 0,
|
|
slideMarkupCache: Object.create(null),
|
|
slides: [],
|
|
index: 0,
|
|
setTimeout,
|
|
clearTimeout,
|
|
document: {
|
|
createElement() {
|
|
return {
|
|
className: '',
|
|
setAttribute() {},
|
|
appendChild() {},
|
|
parentNode: null,
|
|
addEventListener() {}
|
|
};
|
|
},
|
|
body: {
|
|
appendChild() {}
|
|
}
|
|
},
|
|
pulsePlayerRegionTypes: {
|
|
register() {}
|
|
}
|
|
};
|
|
sandbox.window = sandbox;
|
|
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'regions', 'rtmp.js'), sandbox);
|
|
|
|
const current = { id: 1 };
|
|
const next = { id: 2 };
|
|
const later = { id: 3 };
|
|
|
|
assert.equal(sandbox.getRtmpWarmupSlides([current, next, later], 0).map((slide) => slide.id).join(','), '2');
|
|
assert.equal(sandbox.getRtmpWarmupSlides([current, next, later], 1).map((slide) => slide.id).join(','), '3');
|
|
assert.equal(sandbox.getRtmpWarmupSlides([current, next, later], 2).map((slide) => slide.id).join(','), '');
|
|
});
|
|
|
|
test('command client ids stay scoped to the screen session', () => {
|
|
const sessionStorage = (() => {
|
|
const values = new Map();
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const localStorage = (() => {
|
|
const values = new Map();
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const sandbox = {
|
|
window: null,
|
|
Date,
|
|
Array,
|
|
Number,
|
|
String,
|
|
Boolean,
|
|
Object,
|
|
Math,
|
|
console,
|
|
crypto: {
|
|
randomUUID() {
|
|
return 'tab-session-client-id';
|
|
}
|
|
},
|
|
localStorage,
|
|
sessionStorage,
|
|
commandClientId: null,
|
|
commandClientStorageKey: 'pulse-command-client-id',
|
|
currentPlaylistSignature: 'signature',
|
|
slides: [],
|
|
index: 0,
|
|
activeSlidesCacheKey: '',
|
|
activeSlidesCacheValue: [],
|
|
renderCacheViewportKey: '',
|
|
preloadSignature: '',
|
|
preloadContainer: null
|
|
};
|
|
sandbox.window = sandbox;
|
|
|
|
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
|
|
|
assert.equal(sandbox.getCommandClientId(), 'tab-session-client-id');
|
|
assert.equal(sessionStorage.getItem('pulse-command-client-id'), 'tab-session-client-id');
|
|
assert.equal(localStorage.getItem('pulse-command-client-id'), null);
|
|
});
|
|
|
|
test('player client names stay scoped to the tab session', () => {
|
|
const sessionStorage = (() => {
|
|
const values = new Map([['pulse-signage-player-client-name', 'tab-only-name']]);
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const localStorage = (() => {
|
|
const values = new Map([['pulse-signage-player-client-name', 'shared-name']]);
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const sandbox = {
|
|
window: null,
|
|
Date,
|
|
Array,
|
|
Number,
|
|
String,
|
|
Boolean,
|
|
Object,
|
|
Math,
|
|
console,
|
|
sessionStorage,
|
|
localStorage,
|
|
WebSocket: { OPEN: 1 },
|
|
sendCommandState() {}
|
|
};
|
|
sandbox.window = sandbox;
|
|
|
|
loadHtmlScript(path.join(__dirname, '..', 'src', 'player', 'player-client-name.script.html'), sandbox);
|
|
|
|
assert.equal(sandbox.getOnboardingClientName(), 'tab-only-name');
|
|
assert.equal(localStorage.getItem('pulse-signage-player-client-name'), 'shared-name');
|
|
|
|
sandbox.applyOnboardingClientName('Renamed Tab Client', null);
|
|
assert.equal(sessionStorage.getItem('pulse-signage-player-client-name'), 'Renamed Tab Client');
|
|
assert.equal(localStorage.getItem('pulse-signage-player-client-name'), 'shared-name');
|
|
});
|
|
|
|
test('onboarding device ids stay scoped to the tab session', () => {
|
|
const sessionStorage = (() => {
|
|
const values = new Map();
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const localStorage = (() => {
|
|
const values = new Map([['pulse-signage-player-device-id', 'shared-device-id']]);
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const sandbox = {
|
|
window: null,
|
|
Date,
|
|
Array,
|
|
Number,
|
|
String,
|
|
Boolean,
|
|
Object,
|
|
Math,
|
|
console,
|
|
sessionStorage,
|
|
localStorage,
|
|
crypto: {
|
|
randomUUID() {
|
|
return 'tab-device-id';
|
|
}
|
|
},
|
|
fetch() {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json() {
|
|
return Promise.resolve({ onboarded: false, screens: [] });
|
|
},
|
|
text() {
|
|
return Promise.resolve('');
|
|
}
|
|
});
|
|
},
|
|
document: {
|
|
getElementById() {
|
|
return null;
|
|
},
|
|
createElement() {
|
|
return { appendChild() {}, removeChild() {} };
|
|
}
|
|
},
|
|
location: {
|
|
replace() {}
|
|
},
|
|
setInterval() {
|
|
return 1;
|
|
},
|
|
clearInterval() {}
|
|
};
|
|
sandbox.window = sandbox;
|
|
|
|
loadHtmlScript(path.join(__dirname, '..', 'src', 'player', 'onboarding', 'player-onboarding-landing.script.html'), sandbox);
|
|
|
|
assert.equal(sessionStorage.getItem('pulse-signage-player-device-id'), 'tab-device-id');
|
|
assert.equal(localStorage.getItem('pulse-signage-player-device-id'), 'shared-device-id');
|
|
});
|
|
|
|
test('player onboarding device ids are generated when missing', () => {
|
|
const sessionStorage = (() => {
|
|
const values = new Map();
|
|
return {
|
|
getItem(key) {
|
|
return values.has(key) ? values.get(key) : null;
|
|
},
|
|
setItem(key, value) {
|
|
values.set(String(key), String(value));
|
|
}
|
|
};
|
|
})();
|
|
|
|
const sandbox = {
|
|
window: null,
|
|
Date,
|
|
Array,
|
|
Number,
|
|
String,
|
|
Boolean,
|
|
Object,
|
|
Math,
|
|
console,
|
|
sessionStorage,
|
|
localStorage: sessionStorage,
|
|
crypto: {
|
|
randomUUID() {
|
|
return 'generated-device-id';
|
|
}
|
|
},
|
|
WebSocket: { OPEN: 1 },
|
|
sendCommandState() {}
|
|
};
|
|
sandbox.window = sandbox;
|
|
|
|
loadHtmlScript(path.join(__dirname, '..', 'src', 'player', 'player-client-name.script.html'), sandbox);
|
|
|
|
assert.equal(sandbox.getOnboardingDeviceId(), 'generated-device-id');
|
|
assert.equal(sessionStorage.getItem('pulse-signage-player-device-id'), 'generated-device-id');
|
|
}); |