DUNGEONS-34: keep host screen awake while streaming

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-09-06 18:47:42 +05:00
parent ae3a3c5def
commit 1b55a86ba5
12 changed files with 307 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createScreenWakeLock } from '../utils/screenWakeLock.ts';
const settle = () => new Promise(resolve => setImmediate(resolve));
class Lock extends EventTarget {
released = false;
async release() {
this.released = true;
this.dispatchEvent(new Event('release'));
}
}
function fixture(request) {
const locks = [];
const state = { visible: true, status: undefined, calls: 0 };
const controller = createScreenWakeLock({
visible: () => state.visible,
status: value => { state.status = value; },
request: request === null ? undefined : async () => {
state.calls++;
if (request) return request();
const lock = new Lock();
locks.push(lock);
return lock;
},
});
return { controller, locks, state };
}
test('only holds a lock when enabled and hosting; stop releases it', async () => {
const { controller, locks, state } = fixture();
controller.update(false);
assert.equal(state.calls, 0);
controller.update(true);
controller.refresh();
await settle();
assert.equal(state.calls, 1);
assert.equal(state.status, 'active');
controller.update(false);
assert.equal(locks[0].released, true);
assert.equal(state.status, 'off');
});
test('releases when hidden and reacquires on return while still hosting', async () => {
const { controller, locks, state } = fixture();
controller.update(true);
await settle();
state.visible = false;
controller.refresh();
assert.equal(locks[0].released, true);
assert.equal(state.status, 'waiting');
state.visible = true;
controller.refresh();
await settle();
assert.equal(state.calls, 2);
assert.equal(state.status, 'active');
controller.dispose();
assert.equal(locks[1].released, true);
});
for (const action of ['stop', 'dispose']) {
test(`late acquisition is released after ${action}`, async () => {
let resolve;
const lock = new Lock();
const { controller, state } = fixture(() => new Promise(done => { resolve = done; }));
controller.update(true);
if (action === 'stop') controller.update(false); else controller.dispose();
resolve(lock);
await settle();
assert.equal(lock.released, true);
assert.equal(state.calls, 1);
assert.notEqual(state.status, 'active');
});
}
test('off/on during acquisition discards stale lock and acquires only one new lock', async () => {
const resolvers = [];
const { controller, state } = fixture(() => new Promise(done => resolvers.push(done)));
controller.update(true);
controller.update(false);
controller.update(true);
const old = new Lock();
resolvers.shift()(old);
await settle();
assert.equal(old.released, true);
assert.equal(state.calls, 2);
const current = new Lock();
resolvers.shift()(current);
await settle();
assert.equal(state.status, 'active');
controller.dispose();
assert.equal(current.released, true);
});
test('denial leaves streaming alone and can be retried without an automatic loop', async () => {
let denied = true;
const { controller, state } = fixture(async () => {
if (denied) throw new Error('NotAllowedError');
return new Lock();
});
controller.update(true);
await settle();
assert.equal(state.status, 'unavailable');
assert.equal(state.calls, 1);
denied = false;
controller.refresh();
await settle();
assert.equal(state.status, 'active');
controller.dispose();
});
test('system revocation reports loss without repeatedly requesting another lock', async () => {
const { controller, state, locks } = fixture();
controller.update(true);
await settle();
await locks[0].release();
await settle();
assert.equal(state.status, 'unavailable');
assert.equal(state.calls, 1);
controller.dispose();
});
test('unsupported browser is reported without throwing', () => {
const { controller, state } = fixture(null);
controller.update(true);
assert.equal(state.status, 'unsupported');
controller.update(false);
assert.equal(state.status, 'off');
});