test(client): happy-path tests for voice mutators (#414)#464
Merged
intendednull merged 1 commit intoApr 28, 2026
Merged
Conversation
Voice mutation API in crates/client/src/mutations.rs had zero test coverage at any tier. Added crates/client/src/tests/voice.rs with one happy-path test per mutator using the test_client() harness — tier 2 per CLAUDE.md (lowest tier covering behaviour, no DOM needed). Coverage: - join_voice: active channel set, local peer in participants - leave_voice: active channel cleared, local peer removed - toggle_mute: returns new value, is_voice_muted reflects, round-trip - toggle_deafen: same shape as toggle_mute - voice_peer_joined: peer inserted into participants, ClientEvent::VoiceJoined fired - voice_peer_left: peer removed, ClientEvent::VoiceLeft fired Tradeoffs: - voice_peer_joined / voice_peer_left arrive in production via the gossip listener unpacking WireMessage::VoiceJoin / VoiceLeave. Driving them end-to-end through a fake wire message would re-test serialisation; the mutators themselves are best exercised directly with a synthetic peer id (mirrors what record_typing does in multi_peer_sync.rs). - join_voice / leave_voice's broadcast_on_topic call no-ops without a subscribed topic, so the test_client harness suffices — no MemHub needed. Wire delivery between peers is exercised in the listener tests already. Refs #414
intendednull
added a commit
that referenced
this pull request
Apr 28, 2026
Adds five tests in crates/client/src/tests/governance.rs covering
the mutators previously only Playwright-covered (or with zero
coverage anywhere):
* propose_grant_admin -> Propose { GrantAdmin }
* propose_revoke_admin -> Propose { RevokeAdmin }
* propose_kick_member -> Propose { KickMember }
* propose_set_threshold -> Propose { SetVoteThreshold }
* delete_role -> DeleteRole
Each test drives the mutator against a `test_client()` fixture
(genesis author = owner = automatic admin) and inspects the
managed DAG to assert the expected EventKind variant and payload.
Downstream materialisation (vote tally, role removal) is tier-1
state-machine territory and stays out of scope, mirroring the
convention from voice.rs (#464).
Refs #416
Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Apr 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
what + why
Voice mutation API in
crates/client/src/mutations.rshad zero test coverage at any tier. New filecrates/client/src/tests/voice.rsadds one happy-path test per mutator. Tier 2 (client) per CLAUDE.md — lowest tier covering behaviour, no DOM needed.Six mutators, six tests:
join_voice_sets_active_channel_and_inserts_self—active_voice_channel()flips to the joined id,voice_participants()contains the local peer.leave_voice_clears_active_channel_and_removes_self— after join+leave,active_voice_channel()isNone, local peer no longer in participants.toggle_mute_flips_state_and_returns_new_value— first call returnstrueandis_voice_muted()is true; second call returnsfalseand round-trips.toggle_deafen_flips_state_and_returns_new_value— same shape as toggle_mute againstis_voice_deafened().voice_peer_joined_inserts_peer_and_emits_event— synthetic peer id; participants set updated;ClientEvent::VoiceJoinedsurfaces on the broker.voice_peer_left_removes_peer_and_emits_event— seed viavoice_peer_joined, drain its event, then callvoice_peer_left; participants set updated;ClientEvent::VoiceLeftsurfaces.verification
cargo test -p willow-client tests_voice— 6 passed.cargo test -p willow-client— 293 passed (was 287, +6).cargo fmt --check— clean.cargo clippy --workspace --all-targets -- -D warnings— zero warnings.cargo test --workspace— all green.cargo check --target wasm32-unknown-unknown -p willow-client— clean.(
justnot available in sandbox — used rawcargoper fallback.)tradeoffs
voice_peer_joined/voice_peer_leftarrive in production via the gossip listener unpackingWireMessage::VoiceJoin/VoiceLeave. Driving them through a fake wire message would re-test serialisation; the mutators themselves are best exercised directly with a synthetic peer id. Mirrors howmulti_peer_sync::typing_indicator_updates_peer_statecallsrecord_typingdirectly instead of synthesising aWireMessage::TypingIndicator.join_voice/leave_voicecallbroadcast_on_topic, which no-ops if no topic is subscribed. Thetest_client()harness has no network → no topic → broadcast drops silently, but the voiceStateActormutation still runs and that's what the UI binds to. Wire-delivery between peers is exercised in the existing listener / multi-peer tests, not duplicated here.mod tests_voice;registration inlib.rs.Refs #414
Generated by Claude Code