Files
cinny/src/app/utils/callRejoin.test.ts
T
jaredandClaude Opus 5 dc0d524989 feat(calls): rejoin the voice room after a crash, update or reload (#118)
While joined, a device-local record {roomId, deviceId, joinedAt,
lastSeen, mic, video} is written and refreshed every 30 s; a deliberate
hangup (HangupCall/Close) and logout clear it. On the next start, once
the first sync is in and the room's MatrixRTC session has reported its
members (waited for up to 10 s — it fills a moment after sync), a fresh
record (< 10 min) for this device with someone still in the call yields
either a sticky "Rejoin voice?" toast (tap to rejoin) or an automatic
rejoin, per the new Settings → Calls → After a Restart (Ask / Rejoin
automatically / Do nothing; default Ask). Skipped when our own membership
is already live from another device, or only our stale one is left.
Mic/camera state comes from the record (camera still gated by
cameraOnJoin); PTT is applied by startCall as usual.

Pure decision in utils/callRejoin.ts with tests. Verified headless:
reload mid-call → toast → tap → back in the call; hangup → reload → no
toast; auto mode → back in without a prompt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-20 14:50:57 -04:00

57 lines
1.9 KiB
TypeScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { CALL_REJOIN_MAX_AGE_MS, decideRejoin, RejoinContext } from './callRejoin';
const base: RejoinContext = {
record: {
roomId: '!r',
deviceId: 'DEV',
joinedAt: 900_000,
lastSeen: 1_000_000,
microphone: true,
video: false,
},
mode: 'ask',
now: 1_060_000,
myDeviceId: 'DEV',
roomJoined: true,
ownMemberDevices: [],
memberCount: 2,
};
describe('decideRejoin', () => {
it('asks by default when the call is still going', () => {
assert.deepEqual(decideRejoin(base), { action: 'ask', roomId: '!r' });
assert.deepEqual(decideRejoin({ ...base, mode: 'auto' }), { action: 'auto', roomId: '!r' });
});
it('does nothing when off, stale, another device, or no record', () => {
assert.deepEqual(decideRejoin({ ...base, mode: 'off' }), { action: 'none' });
assert.deepEqual(decideRejoin({ ...base, record: undefined }), { action: 'none' });
assert.deepEqual(decideRejoin({ ...base, now: 1_000_000 + CALL_REJOIN_MAX_AGE_MS + 1 }), {
action: 'none',
});
assert.deepEqual(decideRejoin({ ...base, myDeviceId: 'OTHER' }), { action: 'none' });
assert.deepEqual(decideRejoin({ ...base, roomJoined: false }), { action: 'none' });
});
it('skips when we already rejoined from another device', () => {
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: ['PHONE'], memberCount: 2 }), {
action: 'none',
});
});
it('skips when only our own stale membership is left', () => {
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: ['DEV'], memberCount: 1 }), {
action: 'none',
});
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: [], memberCount: 0 }), {
action: 'none',
});
assert.deepEqual(decideRejoin({ ...base, ownMemberDevices: ['DEV'], memberCount: 2 }), {
action: 'ask',
roomId: '!r',
});
});
});