Add player keyboard controls and release 2.7.1
Publish Docker Image / build-and-push (./build/Dockerfile, git.lzstealth.com/lzstealth/pulse-signage-web, web) (push) Successful in 1m17s
Publish Docker Image / build-and-push (./build/Dockerfile.player, git.lzstealth.com/lzstealth/pulse-signage-player, player) (push) Successful in 32s

This commit is contained in:
2026-08-14 13:50:40 +01:00
parent e7ec276317
commit 75b5cb5a6b
7 changed files with 131 additions and 3 deletions
@@ -92,6 +92,7 @@ function createSandbox() {
playRegionAnimations() {
calls.playRegionAnimations += 1;
},
addEventListener() {},
pulsePlayerRegionTypes: {
list() {
return [
+85
View File
@@ -68,6 +68,7 @@ test('playlist refresh queues updates until the next slide transition', async ()
scheduleRefreshRetry() {},
syncWebpagePreloads() {},
syncRtmpWarmups() {},
addEventListener() {},
showCurrent() {
calls.showCurrent += 1;
},
@@ -209,6 +210,7 @@ test('single-slide playlists re-render the active slide instead of refreshing af
scheduleRefreshRetry() {},
syncWebpagePreloads() {},
syncRtmpWarmups() {},
addEventListener() {},
clearActiveSlidesCache() {},
showCurrent() {},
sendCommandState() {},
@@ -347,6 +349,7 @@ test('deferred playlist updates preserve the current slide index', async () => {
scheduleRefreshRetry() {},
syncWebpagePreloads() {},
syncRtmpWarmups() {},
addEventListener() {},
clearActiveSlidesCache() {},
showCurrent() {},
sendCommandState() {},
@@ -406,6 +409,87 @@ test('deferred playlist updates preserve the current slide index', async () => {
assert.equal(sandbox.index, 1);
});
test('player page arrow keys move between slides', async () => {
const handlers = Object.create(null);
const calls = [];
const sandbox = {
window: null,
console,
Array,
Object,
String,
Boolean,
Number,
Math,
Date,
JSON,
Promise,
setTimeout,
clearTimeout,
location: { origin: 'http://localhost', href: 'http://localhost/screen/test2' },
addEventListener(type, handler) {
handlers[type] = handler;
},
slides: [{ id: 1 }, { id: 2 }, { id: 3 }],
lastRenderedSlide: { id: 2 },
index: 1,
timer: null,
slideOutroTimers: [],
getCurrentActiveSlides() {
return sandbox.slides;
},
clearSlideTimer() {},
applyPendingPlaylistUpdate() {},
renderSlideAtIndex(_sourceSlides, targetIndex) {
calls.push(targetIndex);
}
};
sandbox.window = sandbox;
const scriptPath = path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-commands.js');
const script = fs.readFileSync(scriptPath, 'utf8');
vm.runInNewContext(script, sandbox, { filename: scriptPath });
assert.equal(typeof handlers.keydown, 'function');
let prevented = false;
handlers.keydown({
key: 'ArrowLeft',
target: {},
preventDefault() {
prevented = true;
}
});
assert.deepEqual(calls, [0]);
assert.equal(prevented, true);
sandbox.lastRenderedSlide = { id: 2 };
sandbox.index = 1;
prevented = false;
handlers.keydown({
key: 'ArrowRight',
target: {},
preventDefault() {
prevented = true;
}
});
assert.deepEqual(calls, [0, 2]);
assert.equal(prevented, true);
handlers.keydown({
key: 'ArrowLeft',
target: { tagName: 'INPUT' },
preventDefault() {
throw new Error('should not be called for editable targets');
}
});
assert.deepEqual(calls, [0, 2]);
});
test('removing the currently visible slide from a two-slide playlist applies the one-slide update immediately', async () => {
const calls = {
showCurrent: 0,
@@ -464,6 +548,7 @@ test('removing the currently visible slide from a two-slide playlist applies the
scheduleRefreshRetry() {},
syncWebpagePreloads() {},
syncRtmpWarmups() {},
addEventListener() {},
clearActiveSlidesCache() {},
showCurrent() {
calls.showCurrent += 1;