Skip to content

test: cover handleOpen navigate sub-cases + VoIP-fail completion#7307

Open
diegolmello wants to merge 4 commits into
developfrom
tests/deeplinking-navigate
Open

test: cover handleOpen navigate sub-cases + VoIP-fail completion#7307
diegolmello wants to merge 4 commits into
developfrom
tests/deeplinking-navigate

Conversation

@diegolmello
Copy link
Copy Markdown
Member

@diegolmello diegolmello commented May 7, 2026

Proposed changes

Extends the deep linking saga harness (introduced on fix/deeplink-auth-room-nav) with the URL-shape → navigation contract and the VoIP-accept-failed recovery path. No production code changes — tests only.

Coverage added on top of the slice-03 harness (app/sagas/__tests__/deepLinking.test.ts):

  • Group G — navigate() sub-cases (G1–G11) driven via the D1 path (same-server, already connected):
    • G1 channel/generalt:'c', name 'general'
    • G2 direct/alicet:'d', roomUserId from getUidDirectMessage
    • G3 group/secrett:'p'
    • G4 channels/livechat-1t:'l'
    • G5 group/foo/thread/abc123jumpToThreadId:'abc123'
    • G6 params.messageId='msg42'jumpToMessageId:'msg42'
    • G7 invite/xyz789 → no goRoom, dispatches inviteLinksRequest('xyz789')
    • G8 params.rid only → goRoom invoked with the room spread
    • G9 canOpenRoom returns null → no goRoom, still dispatches appStart(ROOT_INSIDE)
    • G10 tablet (isMasterDetail=true) → goRoom invoked with isMasterDetail:true
    • G11 canOpenRoom throws → propagates (locks down current behavior)
  • Group H — handleVoipAcceptFailed (H1–H4) driven via the C1 no-host + voipAcceptFailed=true branch:
    • H1 callId + usernameresetVoipState, RNCallKeep.endCall(callId), waitForNavigationReady, navigate to direct/${username}, showToast('VoIP_Call_Issue')
    • H2 no callIdendCall NOT called, other steps unchanged
    • H3 no username but params.path set → uses params.path for navigation
    • H4 navigate throws → caught by handleVoipAcceptFailed's try/catch, showToast not reached
  • Group F2 — unknown server getServerInfo fail with voipAcceptFailed=true → routed through the F2 fallback branch (not C1).

Implementation notes:

  • All scaffolding and factories from slice 03 are reused; new mocks added only for the surfaces the navigate / VoIP-fail paths actually touch (showToast helper, InteractionManager.runAfterInteractions via jest.spyOn per beforeEach).
  • New setupStoreWithCollector helper captures saga put() effects via a custom middleware. Plain store.dispatch overrides don't see saga puts because saga middleware short-circuits its own emissions before they reach store.dispatch. Used by G7 to assert inviteLinksRequest was dispatched.
  • File grew 355 → 746 lines, kept as a single deepLinking.test.ts (under the 800-line split threshold called out in the issue).
  • No edits to app/sagas/deepLinking.js. No new dependencies.

Issue(s)

Extends the test coverage added in #7304. Base will be re-targeted to develop after #7304 merges.

How to test or reproduce

TZ=UTC yarn test app/sagas/__tests__/deepLinking.test.ts

All 21 tests should pass (5 from slice-03's F4 regression race + 16 added here: G1–G11, H1–H4, F2). Local run completes in ~1.5s.

Screenshots

N/A (test-only change).

Types of changes

  • Improvement (non-breaking change which improves a current function) — additional test coverage

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

Sibling PR to slice-04 (open separately) — both extend slice-03's harness and will need to reconcile when both merge into fix/deeplink-auth-room-nav. Slice-04 introduces a similar setupStoreWithCollector pattern; the two helpers can be deduplicated downstream.

Summary by CodeRabbit

  • Tests
    • Expanded saga test coverage for deep-link and navigation flows, adding many deep-link shapes (channels, DMs, groups, threads, invites, message/rid cases).
    • Added recovery and edge-case tests for VOIP accept failures, ensuring call termination, VOIP state reset, toast notifications, and navigation fallbacks when metadata is missing.
    • Introduced a collector-based helper to assert saga-dispatched actions not visible via normal dispatch.

…stack

When a deep link with userId/token/path lands on an unknown server, the
saga waited for LOGIN.SUCCESS and then immediately called goRoom while
app.root was still ROOT_OUTSIDE. The login saga's appStart(ROOT_INSIDE)
would only fire later inside handleLoginSuccess, after the deeplink
saga had already dispatched navigation into the still-mounted
OutsideStack — losing it when InsideStack later mounted at its default
RoomsListView.

Wait for the APP.START with root=ROOT_INSIDE before navigating so that
goRoom dispatches into the correct stack.
Establishes integration-style test harness for app/sagas/deepLinking.js
with boundary mocks, fake timers, and manual cross-saga dispatch. Locks
down the take-based ordering between LOGIN.SUCCESS and APP.START(ROOT_INSIDE)
that PR #7304 introduced.
Extends the deep linking saga harness with URL-shape navigation
contract coverage (channel/direct/group/livechat/thread/invite/messageId)
and the VoIP-accept-failed recovery path (incl. group F2 routing).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 253f80cb-5245-481e-bd5e-25eb3a81a21f

📥 Commits

Reviewing files that changed from the base of the PR and between 0baf862 and f1bfedd.

📒 Files selected for processing (1)
  • app/sagas/__tests__/deepLinking.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/sagas/tests/deepLinking.test.ts

Walkthrough

Expands deepLinking saga tests: adds a collector middleware helper, boundary mocks/imports, and three test groups covering URL navigation shapes, VoIP accept-failed recovery, and an unknown-server VoIP fallback.

Changes

deepLinking Saga Test Expansion

Layer / File(s) Summary
Mock Setup & Imports
app/sagas/__tests__/deepLinking.test.ts
showToast mocked; InteractionManager, react-native-callkeep (real imports), resetVoipState, and getUidDirectMessage imported (lines 80–108).
Test Infrastructure
app/sagas/__tests__/deepLinking.test.ts
setupStoreWithCollector helper installs a collector middleware to capture saga put() effects (lines 127–146).
URL Navigation Tests (Group G)
app/sagas/__tests__/deepLinking.test.ts
Tests navigate() handling for multiple deep-link shapes, asserts goRoom args, INVITE_LINKS_REQUEST via collector, null canOpenRoom skip, and a throwing canOpenRoom case (lines 385–590).
VoIP Recovery Tests (Group H)
app/sagas/__tests__/deepLinking.test.ts
Tests handleVoipAcceptFailed behavior: resetVoipState, conditional RNCallKeep.endCall, navigation/path fallback, toast behavior, and swallowed navigation errors (lines 592–682).
Unknown Server + VoIP Tests (Group F2)
app/sagas/__tests__/deepLinking.test.ts
Ensures getServerInfo failure with voipAcceptFailed=true triggers handleVoipAcceptFailed (reset/endCall/toast) rather than fallback navigation dispatch (lines 684–746).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

type: chore

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 summarizes the main change: expanding test coverage for handleOpen navigate sub-cases and VoIP-fail completion in deepLinking.test.ts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (3)
  • SLICE-03: Request failed with status code 401
  • LIVECHAT-1: Request failed with status code 401
  • SLICE-04: Request failed with status code 401

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.

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

🧹 Nitpick comments (3)
app/sagas/__tests__/deepLinking.test.ts (3)

127-131: 💤 Low value

setupStoreWithCollector comment mis-describes why the approach is needed

The comment says saga middleware "short-circuits its own emissions before they reach store.dispatch", but that is not accurate. redux-saga's put() effects DO flow through the full store.dispatch chain. The real reason a post-creation store.dispatch override misses saga puts is that redux-saga captures the store's dispatch reference at middleware-init time, so reassigning store.dispatch afterwards is not visible to the saga.

The implementation is correct; only the rationale in the comment is misleading.

📝 Suggested wording
-/**
- * Like setupStore, but adds a collector middleware so dispatched actions emitted via
- * saga `put()` effects are captured. Plain `store.dispatch` overrides do NOT see saga puts
- * because saga middleware short-circuits its own emissions before they reach `store.dispatch`.
- */
+/**
+ * Like setupStore, but inserts a collector middleware that captures every dispatched action
+ * (including saga `put()` effects). Mutating `store.dispatch` after store creation does not
+ * capture saga puts because redux-saga holds the dispatch reference captured at middleware
+ * init time; this middleware approach intercepts at the correct level.
+ */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/sagas/__tests__/deepLinking.test.ts` around lines 127 - 131, The JSDoc
for setupStoreWithCollector is misleading about redux-saga behavior; update the
comment for the setupStoreWithCollector helper to state that redux-saga's put()
effects do flow through the full store.dispatch chain but that redux-saga
captures the store.dispatch reference at middleware initialization, so
overwriting store.dispatch after middleware creation will not affect
saga-produced dispatches; replace the incorrect phrase about middleware
"short-circuiting its own emissions" with this accurate explanation and keep the
rest of the implementation as-is.

655-667: ⚡ Quick win

H3 is missing a showToast assertion that H1 and H2 both make

H1 and H2 assert showToast is called with 'VoIP_Call_Issue' on every non-throw path through handleVoipAcceptFailed. H3 covers the path-fallback navigation branch but omits this assertion. If showToast is unconditional after navigation in the saga (as the H1/H2 evidence suggests), this is a coverage gap that would allow a regression where the toast is silently dropped on the path-fallback branch.

✅ Proposed addition
 	expect(jest.mocked(goRoom)).toHaveBeenCalledTimes(1);
 	const call = jest.mocked(goRoom).mock.calls[0][0];
 	expect(call.item.name).toBe('bob');
+	expect(jest.mocked(showToast)).toHaveBeenCalledWith('VoIP_Call_Issue');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/sagas/__tests__/deepLinking.test.ts` around lines 655 - 667, The H3 test
("without username but with params.path → uses params.path for navigation") is
missing an assertion that showToast was invoked like H1/H2; update that test in
deepLinking.test.ts (the H3 block) to assert that showToast was called with
'VoIP_Call_Issue' after the navigation branch (the
driveVoipFail/handleVoipAcceptFailed path), mirroring the existing H1/H2
expectations (use jest.mocked(showToast) to check the call and optionally the
call count).

525-535: ⚡ Quick win

Import the INVITE_LINKS constant to eliminate the hardcoded action type string

Replace the hardcoded string 'INVITE_LINKS_REQUEST' with INVITE_LINKS.REQUEST for compile-time safety and to prevent silent failures if the constant is ever renamed.

♻️ Proposed fix
+import { INVITE_LINKS } from '../../actions/actionsTypes';
 
 // inside G7:
-const inviteAction = dispatched.find((a: any) => a.type === 'INVITE_LINKS_REQUEST');
+const inviteAction = dispatched.find((a: any) => a.type === INVITE_LINKS.REQUEST);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/sagas/__tests__/deepLinking.test.ts` around lines 525 - 535, The test
currently asserts dispatched actions using the hardcoded string
'INVITE_LINKS_REQUEST'; import the INVITE_LINKS constant and replace that string
with INVITE_LINKS.REQUEST in the test so it checks the correct action type at
compile-time—update the import section to include INVITE_LINKS and change the
dispatched.find predicate (and any related expectations) to use
INVITE_LINKS.REQUEST when verifying the inviteAction after calling driveNavigate
in this test (the surrounding helpers are setupStoreWithCollector, driveNavigate
and the mocked goRoom).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/sagas/__tests__/deepLinking.test.ts`:
- Around line 550-559: The test currently asserts store.getState().app.root but
connectedState already initializes app.root to RootEnum.ROOT_INSIDE, making the
assertion a false positive; update the test to either use
setupStoreWithCollector(...) to capture dispatched actions and assert that
appStart(ROOT_INSIDE) appears in dispatched, or create the store with a
different preloaded root (e.g. clone connectedState and set app.root =
RootEnum.ROOT_OUTSIDE) so you can assert the state changed; keep the rest of the
flow (jest.mocked(canOpenRoom).mockResolvedValue(null), driveNavigate(...), and
verifying goRoom was not called) unchanged and reference canOpenRoom, goRoom,
driveNavigate, makeParams, setupStoreWithCollector, appStart, RootEnum, and
connectedState when locating the test to modify.

---

Nitpick comments:
In `@app/sagas/__tests__/deepLinking.test.ts`:
- Around line 127-131: The JSDoc for setupStoreWithCollector is misleading about
redux-saga behavior; update the comment for the setupStoreWithCollector helper
to state that redux-saga's put() effects do flow through the full store.dispatch
chain but that redux-saga captures the store.dispatch reference at middleware
initialization, so overwriting store.dispatch after middleware creation will not
affect saga-produced dispatches; replace the incorrect phrase about middleware
"short-circuiting its own emissions" with this accurate explanation and keep the
rest of the implementation as-is.
- Around line 655-667: The H3 test ("without username but with params.path →
uses params.path for navigation") is missing an assertion that showToast was
invoked like H1/H2; update that test in deepLinking.test.ts (the H3 block) to
assert that showToast was called with 'VoIP_Call_Issue' after the navigation
branch (the driveVoipFail/handleVoipAcceptFailed path), mirroring the existing
H1/H2 expectations (use jest.mocked(showToast) to check the call and optionally
the call count).
- Around line 525-535: The test currently asserts dispatched actions using the
hardcoded string 'INVITE_LINKS_REQUEST'; import the INVITE_LINKS constant and
replace that string with INVITE_LINKS.REQUEST in the test so it checks the
correct action type at compile-time—update the import section to include
INVITE_LINKS and change the dispatched.find predicate (and any related
expectations) to use INVITE_LINKS.REQUEST when verifying the inviteAction after
calling driveNavigate in this test (the surrounding helpers are
setupStoreWithCollector, driveNavigate and the mocked goRoom).
🪄 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: fab0327c-8092-4040-8def-3b59567bb3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 0772eac and 0baf862.

📒 Files selected for processing (1)
  • app/sagas/__tests__/deepLinking.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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/sagas/__tests__/deepLinking.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{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/sagas/__tests__/deepLinking.test.ts
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • app/sagas/__tests__/deepLinking.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • app/sagas/__tests__/deepLinking.test.ts
app/sagas/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place side effects in 'app/sagas/' directory (init, login, rooms, messages, encryption, deepLinking, videoConf)

Files:

  • app/sagas/__tests__/deepLinking.test.ts
🧠 Learnings (1)
📚 Learning: 2026-04-30T17:07:51.020Z
Learnt from: diegolmello
Repo: RocketChat/Rocket.Chat.ReactNative PR: 7274
File: app/lib/services/voip/MediaCallEvents.ts:0-0
Timestamp: 2026-04-30T17:07:51.020Z
Learning: In this Rocket.Chat React Native codebase, the ESLint rule `no-void: error` is enforced. When you see a promise returned from an async call that is not awaited (a “floating promise”), do not silence it with the `void somePromise()` pattern. Instead, handle the promise explicitly by attaching `.catch(...)` (or otherwise awaiting/handling the error) so unhandled-rejection risks are addressed in a way that satisfies the existing ESLint configuration.

Applied to files:

  • app/sagas/__tests__/deepLinking.test.ts

Comment thread app/sagas/__tests__/deepLinking.test.ts Outdated
@diegolmello diegolmello force-pushed the fix/deeplink-auth-room-nav branch from 46f56f9 to 9c8a9ee Compare May 7, 2026 16:37
Base automatically changed from fix/deeplink-auth-room-nav to develop May 7, 2026 16:45
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