my-sea voice: guard before NVM disconnects voice + harden mute (honor pre-stream mute) — TDD
Phase 4 of the my-sea voice batch (user-spec 2026-05-29). ── Voice-disconnect guard (item 6, the achievable slice) ── Every my-sea navigation is a full page reload, which tears the WebRTC mesh down — so voice can't literally persist across a reload without an SPA-style no-reload nav (a separate, larger refactor, deferred). What ships now: the gear-menu NVM warns before dropping the call. - _my_sea_gear.html: the NVM routes through an inline, dependency-free `mySeaGuardedNav(event, url)`. When voice is LIVE (VoiceRoom.localStream) AND the target leaves my_sea (`url` has no `/my-sea`), it pops the shared guard portal — "Leave the Sea? You'll disconnect from voice." — before navigating; confirm proceeds, dismiss stays. NVMs that stay within my_sea, or any nav with no live mic, go straight through. Covers all NVMs (owner my_sea, the gatekeeper, the spectator) since they all include this partial. ── Bug B: desktop mute (mute robustness) ── - voice-mesh.js: extracted `_applyMute()` and call it from join (post- getUserMedia) as well as toggleMute. On desktop the first join pops a mic- permission prompt; a mute toggled while that prompt is open used to be lost because the stream didn't exist yet — re-applying after it resolves makes the mute stick. Teardown resets `muted=false` so a rejoin starts clean. - voice-glow.js: setVoiceState now syncs the voice btn's own flags (.in-call / .muted / dataset.inCall) to the mesh truth, so a rejoin starts clean and the glow's DOM fallback can't get stuck "live". Tests: +5 VoiceMeshSpec mute specs (incl. the pre-stream-mute Bug-B case); +2 NVM-guard FTs (warn when live / pass through when not); 3 gear-NVM ITs updated for the mySeaGuardedNav markup. 286 gameboard ITs + 433 Jasmine specs green. Note: true cross-view voice persistence (no-reload within-my_sea nav) + the desktop-mute live confirmation remain to verify on staging w. Redis up. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,3 +40,56 @@ describe('voice-mesh tuneOpus', function () {
|
||||
expect(window.tuneOpus('')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
// Mute path — the one deterministic slice of the mesh we can unit-test. The
|
||||
// _applyMute extraction fixes Bug B (2026-05-29): a mute toggled while the
|
||||
// desktop getUserMedia permission prompt is open must stick once the stream
|
||||
// resolves (join re-applies it post-stream).
|
||||
describe('voice-mesh mute', function () {
|
||||
var vr, trackA, trackB;
|
||||
|
||||
beforeEach(function () {
|
||||
vr = window.VoiceRoom;
|
||||
trackA = { enabled: true };
|
||||
trackB = { enabled: true };
|
||||
vr.localStream = { getAudioTracks: function () { return [trackA, trackB]; } };
|
||||
vr.muted = false;
|
||||
vr._onState = null;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
vr.localStream = null;
|
||||
vr.muted = false;
|
||||
vr._onState = null;
|
||||
});
|
||||
|
||||
it('toggleMute disables the audio tracks + returns muted', function () {
|
||||
expect(vr.toggleMute()).toBe(true);
|
||||
expect(trackA.enabled).toBe(false);
|
||||
expect(trackB.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('toggleMute twice re-enables the tracks', function () {
|
||||
vr.toggleMute();
|
||||
expect(vr.toggleMute()).toBe(false);
|
||||
expect(trackA.enabled).toBe(true);
|
||||
expect(trackB.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('_applyMute honors a mute set before the stream existed (Bug B)', function () {
|
||||
// muted toggled while the permission prompt was open (no stream yet)…
|
||||
vr.localStream = null;
|
||||
vr.muted = true;
|
||||
// …then the stream arrives and join calls _applyMute.
|
||||
vr.localStream = { getAudioTracks: function () { return [trackA]; } };
|
||||
vr._applyMute();
|
||||
expect(trackA.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('notifies subscribers with the muted flag on toggle', function () {
|
||||
var seen = null;
|
||||
vr.setOnStateChange(function (st) { seen = st; });
|
||||
vr.toggleMute();
|
||||
expect(seen.muted).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user