Skip to content

fix(voip): show CallKit UI when call is active in background#7128

Open
diegolmello wants to merge 3 commits intofeat.voip-lib-newfrom
feat.ios-background
Open

fix(voip): show CallKit UI when call is active in background#7128
diegolmello wants to merge 3 commits intofeat.voip-lib-newfrom
feat.ios-background

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented Apr 10, 2026

Proposed changes

When a VoIP call is active and the app is backgrounded, iOS was not showing the call in the system UI (lock screen, Control Center, Dynamic Island) due to two missing CallKit integrations.

Fix 1 — setCurrentCallActive on active state transition:
RNCallKeep.setCurrentCallActive() was only called in the local answer path (answerCall()), but never when the call transitioned to active through other paths (e.g., remote user answering an outgoing call). Added the call in handleStateChange inside setCall (useCallStore.ts) when newState === 'active'.

Fix 2 — performSetMutedCallAction not wired:
The mute button in the system UI (lock screen, Control Center, Dynamic Island) had no effect because performSetMutedCallAction was unwired. Added a listener in setupMediaCallEvents (MediaCallEvents.ts) that syncs mute state via toggleMute().

Hold (didToggleHoldCallAction) was already correctly wired — no changes needed.

Issue(s)

https://rocketchat.atlassian.net/browse/VMUX-75

How to test or reproduce

  1. Place a VoIP call and background the app — confirm the call appears in lock screen / Control Center / Dynamic Island
  2. Mute from the system UI (lock screen/Control Center) — confirm mute state syncs correctly
  3. Hold from the system UI — confirm hold state syncs correctly (already worked)

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features

    • Enhanced iOS VoIP call handling with improved synchronization between system and app mute states
    • Improved iOS CallKit integration for call state management
  • Tests

    • Added comprehensive test coverage for iOS mute event handling

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 10, 2026

Walkthrough

The changes implement iOS CallKit integration for mute state synchronization. A new listener for didPerformSetMutedCallAction is added to detect OS-initiated mute changes and reconcile them with JavaScript state. Additionally, the call store now explicitly sets the active call status in CallKit when a media call transitions to active. Supporting test infrastructure and a comprehensive test suite are included.

Changes

Cohort / File(s) Summary
iOS CallKit Mute Synchronization
app/lib/services/voip/MediaCallEvents.ts, app/lib/services/voip/MediaCallEvents.ios.test.ts
Added listener for iOS mute state changes that reconciles OS CallKit mute status with JavaScript store state by calling toggleMute() when values diverge. Includes comprehensive test coverage for listener registration and state reconciliation scenarios.
Call Store Active Call Integration
app/lib/services/voip/useCallStore.ts, app/lib/services/voip/useCallStore.test.ts
Updated call store to invoke RNCallKeep.setCurrentCallActive() when media call transitions to active state. Enhanced test mocks with explicit CallKeep API definitions including setCurrentCallActive, addEventListener, endCall, and others.

Sequence Diagram

sequenceDiagram
    actor OS as iOS CallKit<br/>(OS)
    participant Handler as didPerformSetMutedCallAction<br/>Listener
    participant Store as useCallStore
    participant App as JavaScript<br/>App State

    OS->>Handler: User toggles mute in CallKit<br/>(didPerformSetMutedCallAction event)
    Handler->>Store: getState()
    Store-->>Handler: Returns call, callId, isMuted, toggleMute
    Handler->>Handler: Validate callUUID matches active<br/>call UUID (normalized)
    alt Muted value differs from store
        Handler->>App: Call toggleMute()
        App->>Store: Update isMuted state
    else Muted value matches store
        Handler->>Handler: No action needed
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: enabling CallKit UI visibility during backgrounded active calls on iOS.
Linked Issues check ✅ Passed The changes address the core requirements: calling setCurrentCallActive on active state transition and wiring the mute action via didPerformSetMutedCallAction listener.
Out of Scope Changes check ✅ Passed All changes are directly related to enabling CallKit UI display and mute event handling during backgrounded calls; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

When a VoIP call is active and the app is backgrounded, iOS was not
showing the call in the system UI (lock screen, Control Center, Dynamic
Island) because two CallKit actions were missing.

- Call RNCallKeep.setCurrentCallActive() in handleStateChange when
  callState transitions to 'active', so iOS shows the ongoing call.
- Wire performSetMutedCallAction to toggleMute() so the mute button in
  the system UI syncs correctly with the WebRTC layer.
@diegolmello diegolmello changed the base branch from develop to feat.voip-lib-new April 10, 2026 15:07
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/lib/services/voip/MediaCallEvents.ts`:
- Around line 93-99: The handler registered on RNCallKeep for
'performSetMutedCallAction' currently ignores the incoming callUUID and may
toggle mute for a different/stale session; update the listener in
MediaCallEvents.ts to mirror the pattern used in didToggleHoldCallAction by
extracting the incoming callUUID (the callUUID/_callUUID from the event) and
comparing it to the active JS call identifier from useCallStore.getState()
(e.g., activeCall.callUUID or whatever the store exposes), and only call
toggleMute() when the UUIDs match and muted !== isMuted; keep using the existing
symbols: RNCallKeep.addEventListener('performSetMutedCallAction'),
useCallStore.getState(), toggleMute, isMuted, and the callUUID check used in
didToggleHoldCallAction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c49793ea-b288-4899-a0c4-1ed74ff319ef

📥 Commits

Reviewing files that changed from the base of the PR and between 1110468 and 251d1af.

📒 Files selected for processing (3)
  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.test.ts
  • app/lib/services/voip/useCallStore.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Configure Prettier with tabs, single quotes, 130 character width, no trailing commas, arrow parens avoid, and bracket same line

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.ts
  • app/lib/services/voip/useCallStore.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base configuration including React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.ts
  • app/lib/services/voip/useCallStore.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript with strict mode enabled and configure baseUrl to app/ for import resolution

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.ts
  • app/lib/services/voip/useCallStore.test.ts
app/lib/services/voip/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.ts
  • app/lib/services/voip/useCallStore.test.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.ts
  • app/lib/services/voip/useCallStore.test.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
Learnt from: OtavioStasiak
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6499
File: app/containers/ServerItem/index.tsx:34-36
Timestamp: 2025-12-17T15:56:22.578Z
Learning: In the Rocket.Chat React Native codebase, for radio button components on iOS, include the selection state ("Selected"/"Unselected") in the accessibilityLabel instead of using accessibilityState={{ checked: hasCheck }}, because iOS VoiceOver has known issues with accessibilityRole="radio" + accessibilityState that prevent correct state announcement.
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Applied to files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/useCallStore.ts
  • app/lib/services/voip/useCallStore.test.ts
📚 Learning: 2026-03-30T15:49:30.957Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6875
File: app/containers/RoomItem/Actions.tsx:12-12
Timestamp: 2026-03-30T15:49:30.957Z
Learning: In RocketChat/Rocket.Chat.ReactNative, `react-native-worklets` version 0.6.1 does NOT export a built-in Jest mock (e.g., no `react-native-worklets/lib/module/mock`). The correct Jest mock approach for this version is to add a manual mock in `jest.setup.js`: `jest.mock('react-native-worklets', () => ({ scheduleOnRN: jest.fn((fn, ...args) => fn(...args)) }))`.

Applied to files:

  • app/lib/services/voip/useCallStore.test.ts
🔇 Additional comments (2)
app/lib/services/voip/useCallStore.ts (1)

179-183: Good lifecycle hook for active-call promotion.

Placing this in the active state transition is the right timing to keep native UI in sync with call progression.

app/lib/services/voip/useCallStore.test.ts (1)

14-22: Test mock update is aligned with the new store behavior.

This mock expansion keeps the test environment consistent with the newly introduced call-state/native-sync paths.

Prevent a stale/other CallKit session from flipping mute on the active
JS call by mirroring the UUID check pattern used in didToggleHoldCallAction.
Also adds iOS-specific tests for the mute handler.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/lib/services/voip/MediaCallEvents.ios.test.ts`:
- Around line 6-16: The test file introduces unused imports and mock variables
causing ESLint no-unused-vars errors; prune or use them: remove unused imports
DeviceEventEmitter, RNCallKeep, DEEP_LINKING, the VoipPayload type,
NativeVoipModule, and any unused references to getInitialMediaCallEvents,
setupMediaCallEvents, useCallStore if they’re not referenced in the test, and
delete mockDispatch and mockSetNativeAcceptedCallId if not used; alternatively,
if the test should exercise those APIs, reference them in the test body (e.g.,
call setupMediaCallEvents/getInitialMediaCallEvents or wire useCallStore) so
they are used. Ensure only necessary symbols remain to satisfy
`@typescript-eslint/no-unused-vars`.

In `@app/lib/services/voip/MediaCallEvents.test.ts`:
- Around line 90-96: Remove the unused helper function getMuteHandler from the
test file: delete the entire function definition that references
mockAddEventListener and performs the find/throw/return logic (the
getMuteHandler helper is not used anywhere in MediaCallEvents.test.ts and is
flagged by ESLint), ensuring no other code references it; run the tests/lint to
confirm the warning is resolved.

In `@app/lib/services/voip/MediaCallEvents.ts`:
- Line 93: The RNCallKeep mute listener is using the wrong event name so the
handler never runs; update the addEventListener call in MediaCallEvents (the
RNCallKeep.addEventListener invocation that listens for mute) to use the correct
event name "didPerformSetMutedCallAction" instead of "performSetMutedCallAction"
so system mute actions trigger the existing handler for ({ muted, callUUID }) in
MediaCallEvents.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ed33d18-4c2f-4f6d-a6cb-2900d2cd6c30

📥 Commits

Reviewing files that changed from the base of the PR and between 251d1af and 0f40eb7.

📒 Files selected for processing (3)
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Configure Prettier with tabs, single quotes, 130 character width, no trailing commas, arrow parens avoid, and bracket same line

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base configuration including React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript with strict mode enabled and configure baseUrl to app/ for import resolution

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
app/lib/services/voip/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Applied to files:

  • app/lib/services/voip/MediaCallEvents.ts
  • app/lib/services/voip/MediaCallEvents.test.ts
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
🪛 ESLint
app/lib/services/voip/MediaCallEvents.test.ts

[error] 90-90: 'getMuteHandler' is defined but never used.

(@typescript-eslint/no-unused-vars)

app/lib/services/voip/MediaCallEvents.ios.test.ts

[error] 6-6: 'DeviceEventEmitter' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 7-7: 'RNCallKeep' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 9-9: 'DEEP_LINKING' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 10-10: 'VoipPayload' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 11-11: 'NativeVoipModule' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 12-12: 'getInitialMediaCallEvents' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 16-16: 'mockSetNativeAcceptedCallId' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🪛 GitHub Actions: Format Code with Prettier
app/lib/services/voip/MediaCallEvents.ios.test.ts

[error] 6-10: typescript-eslint/no-unused-vars: 'DeviceEventEmitter' is defined but never used.

🪛 GitHub Check: format
app/lib/services/voip/MediaCallEvents.test.ts

[failure] 90-90:
'getMuteHandler' is defined but never used

app/lib/services/voip/MediaCallEvents.ios.test.ts

[failure] 16-16:
'mockSetNativeAcceptedCallId' is assigned a value but never used


[failure] 12-12:
'getInitialMediaCallEvents' is defined but never used


[failure] 11-11:
'NativeVoipModule' is defined but never used


[failure] 10-10:
'VoipPayload' is defined but never used


[failure] 9-9:
'DEEP_LINKING' is defined but never used


[failure] 7-7:
'RNCallKeep' is defined but never used


[failure] 6-6:
'DeviceEventEmitter' is defined but never used

- Change performSetMutedCallAction → didPerformSetMutedCallAction
  in MediaCallEvents.ts so CallKit mute events actually fire
- Remove unused imports/variables from MediaCallEvents.ios.test.ts
  (DeviceEventEmitter, RNCallKeep, DEEP_LINKING, VoipPayload,
  NativeVoipModule, getInitialMediaCallEvents, mockDispatch, etc.)
- Remove unused getMuteHandler helper from MediaCallEvents.test.ts
@diegolmello diegolmello requested a deployment to approve_e2e_testing April 10, 2026 21:14 — with GitHub Actions Waiting
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/lib/services/voip/MediaCallEvents.ios.test.ts (2)

32-36: Prefer an interface-based mock shape over as object.

Using an explicit interface here improves strict typing and avoids blunt casting.

♻️ Suggested typing cleanup
+interface ActiveCallBase {
+	call: object | null;
+	callId: string | null;
+	nativeAcceptedCallId: string | null;
+}
+
-const activeCallBase = {
-	call: {} as object,
+const activeCallBase: ActiveCallBase = {
+	call: {},
 	callId: 'uuid-1',
-	nativeAcceptedCallId: null as string | null
+	nativeAcceptedCallId: null
 };

As per coding guidelines: "Prefer interfaces over type aliases for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/services/voip/MediaCallEvents.ios.test.ts` around lines 32 - 36,
Replace the blunt cast "call: {} as object" by declaring an explicit interface
for the mock shape (e.g., interface ActiveCall { call: CallShape; callId:
string; nativeAcceptedCallId: string | null }) and a separate interface/type for
the call payload (e.g., CallShape) and then type the activeCallBase constant
with that interface instead of using "as object"; update the activeCallBase
variable to use the newly declared interface for full structural typing and
remove the "as object" cast.

62-79: Add a case-insensitive UUID matching test to lock in current behavior.

MediaCallEvents.ts normalizes UUIDs to lowercase before comparison; adding one mixed-case test would prevent regressions there.

🧪 Suggested test addition
 it('drops event when callUUID does not match active call id', () => {
 	setupMediaCallEvents();
 	getMuteHandler()({ muted: true, callUUID: 'uuid-2' });
 	expect(toggleMute).not.toHaveBeenCalled();
 });
 
+it('matches UUIDs case-insensitively', () => {
+	getState.mockReturnValue({ ...activeCallBase, callId: 'UUID-1', isMuted: false, toggleMute });
+	setupMediaCallEvents();
+	getMuteHandler()({ muted: true, callUUID: 'uuid-1' });
+	expect(toggleMute).toHaveBeenCalledTimes(1);
+});
+
 it('drops event when there is no active call object even if UUIDs match', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/lib/services/voip/MediaCallEvents.ios.test.ts` around lines 62 - 79, Add
a test that verifies UUID matching is case-insensitive by invoking
setupMediaCallEvents(), then calling getMuteHandler() with a mixed-case callUUID
(e.g., 'UUID-1' or 'Uuid-1') while the active call id in getState
(activeCallBase) is 'uuid-1', and assert toggleMute is called; this locks in the
behavior in MediaCallEvents.ts where UUIDs are normalized to lowercase—use the
existing test helpers getMuteHandler, setupMediaCallEvents, activeCallBase, and
toggleMute to mirror the first test but supply a mixed-case callUUID and the
same expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/lib/services/voip/MediaCallEvents.ios.test.ts`:
- Around line 32-36: Replace the blunt cast "call: {} as object" by declaring an
explicit interface for the mock shape (e.g., interface ActiveCall { call:
CallShape; callId: string; nativeAcceptedCallId: string | null }) and a separate
interface/type for the call payload (e.g., CallShape) and then type the
activeCallBase constant with that interface instead of using "as object"; update
the activeCallBase variable to use the newly declared interface for full
structural typing and remove the "as object" cast.
- Around line 62-79: Add a test that verifies UUID matching is case-insensitive
by invoking setupMediaCallEvents(), then calling getMuteHandler() with a
mixed-case callUUID (e.g., 'UUID-1' or 'Uuid-1') while the active call id in
getState (activeCallBase) is 'uuid-1', and assert toggleMute is called; this
locks in the behavior in MediaCallEvents.ts where UUIDs are normalized to
lowercase—use the existing test helpers getMuteHandler, setupMediaCallEvents,
activeCallBase, and toggleMute to mirror the first test but supply a mixed-case
callUUID and the same expectations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d43ae4dd-1d95-4f20-8dfc-8ee85ebf6262

📥 Commits

Reviewing files that changed from the base of the PR and between 0f40eb7 and 05f6cb7.

📒 Files selected for processing (2)
  • app/lib/services/voip/MediaCallEvents.ios.test.ts
  • app/lib/services/voip/MediaCallEvents.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/lib/services/voip/MediaCallEvents.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Configure Prettier with tabs, single quotes, 130 character width, no trailing commas, arrow parens avoid, and bracket same line

Files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint with @rocket.chat/eslint-config base configuration including React, React Native, TypeScript, and Jest plugins

Files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript with strict mode enabled and configure baseUrl to app/ for import resolution

**/*.{ts,tsx}: Use TypeScript for type safety; add explicit type annotations to function parameters and return types
Prefer interfaces over type aliases for defining object shapes in TypeScript
Use enums for sets of related constants rather than magic strings or numbers

Files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
app/lib/services/voip/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use descriptive names for functions, variables, and classes that clearly convey their purpose
Write comments that explain the 'why' behind code decisions, not the 'what'
Keep functions small and focused on a single responsibility
Use const by default, let when reassignment is needed, and avoid var
Prefer async/await over .then() chains for handling asynchronous operations
Use explicit error handling with try/catch blocks for async operations
Avoid deeply nested code; refactor complex logic into helper functions

Files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate
📚 Learning: 2026-04-07T17:49:17.538Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.ReactNative PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T17:49:17.538Z
Learning: Applies to app/lib/services/voip/**/*.{ts,tsx} : Implement VoIP with WebRTC peer-to-peer audio calls in app/lib/services/voip/ using Zustand stores instead of Redux, with native CallKit (iOS) and Telecom (Android) integration; keep VoIP and VideoConf separate

Applied to files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
📚 Learning: 2026-03-30T15:49:30.957Z
Learnt from: Rohit3523
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6875
File: app/containers/RoomItem/Actions.tsx:12-12
Timestamp: 2026-03-30T15:49:30.957Z
Learning: In RocketChat/Rocket.Chat.ReactNative, `react-native-worklets` version 0.6.1 does NOT export a built-in Jest mock (e.g., no `react-native-worklets/lib/module/mock`). The correct Jest mock approach for this version is to add a manual mock in `jest.setup.js`: `jest.mock('react-native-worklets', () => ({ scheduleOnRN: jest.fn((fn, ...args) => fn(...args)) }))`.

Applied to files:

  • app/lib/services/voip/MediaCallEvents.ios.test.ts
🔇 Additional comments (1)
app/lib/services/voip/MediaCallEvents.ios.test.ts (1)

57-92: Good coverage for the new CallKit mute reconciliation path.

These cases are well-targeted and directly validate registration plus the expected toggle/no-toggle branches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant