cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

release/v0.1.0-pins
goq / portal / session-state.test.mjs
session-state.test.mjs 3.5K
1import test from 'node:test';2import assert from 'node:assert/strict';34import { createSessionState } from './session-state.mjs';56function snapshot(revision, focus = { state: 'vacant', slot: 0 }) {7 return {8 revision,9 self_presence_id: 'viewer-1',10 viewers: [{ presence_id: 'viewer-1', session_id: 7, input_capable: true, you: true }],11 focus,12 transition_reason: 'initial',13 media: { generation_id: 7, broadcast_name: 'sigil/session/7/video' },14 };15}1617test('accepts revision gaps and ignores stale or native-generation-mismatched snapshots', () => {18 const runtime = createSessionState();19 runtime.acceptConnection({20 nativeGeneration: 3,21 controlProtocol: 'control-v2',22 initialSnapshot: snapshot(1),23 });24 assert.equal(runtime.applyNativeSnapshot({ native_generation: 4, snapshot: snapshot(2) }), false);25 assert.equal(runtime.applyNativeSnapshot({ native_generation: 3, snapshot: snapshot(8) }), true);26 assert.equal(runtime.revision, 8);27 assert.equal(runtime.applyNativeSnapshot({ native_generation: 3, snapshot: snapshot(7) }), false);28});2930test('mirrors complete multi-viewer roster replacements without changing local identity', () => {31 const runtime = createSessionState();32 runtime.acceptConnection({33 nativeGeneration: 3,34 controlProtocol: 'control-v2',35 initialSnapshot: snapshot(1),36 });37 const joined = snapshot(5, {38 state: 'held', slot: 0, holder: 'viewer-2', session_id: 8, focus_generation: 2,39 });40 joined.viewers.push({41 presence_id: 'viewer-2', session_id: 8, input_capable: true, you: false,42 });43 assert.equal(runtime.applyNativeSnapshot({ native_generation: 3, snapshot: joined }), true);44 assert.equal(runtime.snapshot().snapshot.viewers.length, 2);45 assert.equal(runtime.focused, false);46 assert.equal(runtime.eligible, true);47});4849test('publishes focus teardown before exposing self-focus loss', () => {50 const order = [];51 const runtime = createSessionState({52 onFocusLoss: () => order.push(`teardown:${runtime.focused}`),53 onChange: (state) => order.push(`publish:${state.focused}`),54 });55 runtime.acceptConnection({56 nativeGeneration: 2,57 controlProtocol: 'control-v2',58 initialSnapshot: snapshot(1, {59 state: 'held', slot: 0, holder: 'viewer-1', session_id: 7, focus_generation: 4,60 }),61 });62 order.length = 0;63 runtime.applyNativeSnapshot({ native_generation: 2, snapshot: snapshot(2) });64 assert.deepEqual(order, ['teardown:true', 'publish:false']);65});6667test('separates eligibility, host focus possession, and local activation', () => {68 const runtime = createSessionState();69 runtime.acceptConnection({70 nativeGeneration: 1,71 controlProtocol: 'control-v2',72 initialSnapshot: snapshot(1),73 });74 assert.equal(runtime.eligible, true);75 assert.equal(runtime.focused, false);76 assert.equal(runtime.setLocallyActive(true), false);77 runtime.applyNativeSnapshot({78 native_generation: 1,79 snapshot: snapshot(2, {80 state: 'held', slot: 0, holder: 'viewer-1', session_id: 7, focus_generation: 9,81 }),82 });83 assert.equal(runtime.focused, true);84 assert.equal(runtime.locallyActive, false);85 assert.equal(runtime.setLocallyActive(true), true);86 assert.equal(runtime.locallyActive, true);87});8889test('legacy mode retains explicit implicit-focus behavior', () => {90 const runtime = createSessionState();91 runtime.acceptConnection({92 nativeGeneration: 5,93 controlProtocol: 'legacy-v1',94 initialSnapshot: null,95 eligible: true,96 });97 assert.equal(runtime.mode, 'legacy-v1');98 assert.equal(runtime.focused, true);99});