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'); });