Skip to content

RG-T117 Unit chat fix - #266

Merged
ucswift merged 2 commits into
masterfrom
develop
Aug 12, 2026
Merged

RG-T117 Unit chat fix#266
ucswift merged 2 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Aug 12, 2026

Copy link
Copy Markdown
Member

PR Description

This pull request addresses chat-related fixes and improvements (RG-T117) with the following changes:

Push Notification Suppression for Active Chat Channel

The chat store now notifies the SignalR hub when a channel becomes active (or is cleared) via a new SetActiveChannel invocation. This allows the server to suppress chat push notifications for the conversation currently displayed on screen, preventing duplicate notifications for messages the user is already viewing. The active-channel marker is also re-asserted after a hub reconnection to ensure push suppression survives connection drops.

Optional Group Name for New Conversations

When creating a new group conversation, a group name is no longer required. The submit button and validation now only require at least one member to be selected. When no name is provided, the server auto-names the group based on its members.

Test Infrastructure Improvements

  • Added mocks for react-native-safe-area-context in the global Jest setup to prevent crashes in suites that use minimal React Native stubs.
  • Added mocks for expo-router and @/lib/navigation in navigation and push notification tests to avoid import chain failures.
  • Updated the SignalR lifecycle and chat hub invocation tests to cover the new chat hub connection methods and SetActiveChannel behavior.

Summary by CodeRabbit

  • New Features

    • Group conversations can now be created without entering a name; names may be assigned automatically.
    • Updated group-name labels across supported languages to clarify that naming is optional.
  • Bug Fixes

    • Improved active-channel synchronization after reconnects, including reliable clearing and retry handling.
    • Prevented redundant channel updates after successful synchronization.
  • Tests

    • Expanded coverage for channel synchronization and improved test stability through safer navigation and platform mocks.

@Resgrid-Bot

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change permits group creation without a name, updates localized labels, synchronizes active channels with the Chat hub across reconnects, and adds Jest mocks for native and navigation dependencies.

Changes

Chat updates

Layer / File(s) Summary
Optional group names
src/components/chat/new-conversation-sheet.tsx, src/translations/*.json
Group creation requires selected members but not a group name. Localized labels identify the name as optional.
Active-channel hub synchronization
src/stores/chat/store.ts, src/stores/chat/__tests__/hub-invoke-args.test.ts
setActiveChannel sends channel and unit identifiers to SetActiveChannel, including null when clearing. Pending markers retry after failures, and reconnection restores confirmed or pending markers.
Jest runtime dependency mocks
jest-setup.ts, src/hooks/__tests__/use-signalr-lifecycle.test.tsx, src/lib/__tests__/navigation.test.ts, src/services/__tests__/push-notification.test.ts
Tests use mocks for safe-area, Chat hub, Expo Router, and navigation dependencies.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatStore
  participant ChatHub
  participant ChatConnection
  ChatStore->>ChatHub: SetActiveChannel(channelId, unitId)
  ChatConnection->>ChatStore: reconnect notification
  ChatStore->>ChatHub: reassert active-channel marker
Loading

Possibly related PRs

  • Resgrid/Unit#260: The change extends chat functionality introduced in this PR.
  • Resgrid/Unit#262: The change is related to Chat hub SignalR lifecycle behavior and connection gating.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the related unit chat fixes covered by the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/stores/chat/store.ts (1)

860-861: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Wait for joinChannel before invoking SetActiveChannel

handleChatConnected invokes both operations concurrently. Chain SetActiveChannel after joinChannel so marker execution does not depend on the hub's MaximumParallelInvocationsPerClient setting. Add a regression test that keeps JoinChannel pending.

🤖 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 `@src/stores/chat/store.ts` around lines 860 - 861, Update handleChatConnected
to await the joinChannel operation before invoking SetActiveChannel, rather than
starting both concurrently; preserve the active-channel and unit arguments for
SetActiveChannel. Add a regression test that keeps JoinChannel pending and
verifies SetActiveChannel is not invoked until JoinChannel completes.

Source: MCP tools

🤖 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 `@jest-setup.ts`:
- Around line 8-12: Replace the any-typed props in SafeAreaView and
SafeAreaProvider with a shared MockChildrenProps interface defining optional
children as ReactNode, and use that interface for both mock component
parameters.
- Around line 5-18: Add SafeAreaInsetsContext to the
react-native-safe-area-context mock in jest.mock, exposing a Consumer compatible
with the zero-inset mock values so Expo Router can render safely. Preserve the
existing SafeAreaView, SafeAreaProvider, hook, and initialWindowMetrics
behavior.

In `@src/hooks/__tests__/use-signalr-lifecycle.test.tsx`:
- Around line 20-21: Update the mockConnectChatHub and mockDisconnectChatHub
Jest mocks to use mockResolvedValue(undefined), preserving the production
Promise<void> contract and enabling async completion or rejection tests.

---

Nitpick comments:
In `@src/stores/chat/store.ts`:
- Around line 860-861: Update handleChatConnected to await the joinChannel
operation before invoking SetActiveChannel, rather than starting both
concurrently; preserve the active-channel and unit arguments for
SetActiveChannel. Add a regression test that keeps JoinChannel pending and
verifies SetActiveChannel is not invoked until JoinChannel completes.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f37a2984-ef0c-47e9-b23a-3c775d5a35cc

📥 Commits

Reviewing files that changed from the base of the PR and between 136025c and df5cef1.

📒 Files selected for processing (16)
  • jest-setup.ts
  • src/components/chat/new-conversation-sheet.tsx
  • src/hooks/__tests__/use-signalr-lifecycle.test.tsx
  • src/lib/__tests__/navigation.test.ts
  • src/services/__tests__/push-notification.test.ts
  • src/stores/chat/__tests__/hub-invoke-args.test.ts
  • src/stores/chat/store.ts
  • src/translations/ar.json
  • src/translations/de.json
  • src/translations/en.json
  • src/translations/es.json
  • src/translations/fr.json
  • src/translations/it.json
  • src/translations/pl.json
  • src/translations/sv.json
  • src/translations/uk.json

Comment thread jest-setup.ts
Comment on lines +5 to +18
jest.mock('react-native-safe-area-context', () => {
const React = require('react');

const SafeAreaView = ({ children }: any) => React.createElement(React.Fragment, null, children);

return {
SafeAreaView,
SafeAreaProvider: ({ children }: any) => children,
useSafeAreaInsets: jest.fn(() => ({ top: 0, bottom: 0, left: 0, right: 0 })),
useSafeAreaFrame: jest.fn(() => ({ x: 0, y: 0, width: 375, height: 667 })),
initialWindowMetrics: {
insets: { top: 0, bottom: 0, left: 0, right: 0 },
frame: { x: 0, y: 0, width: 375, height: 667 },
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Expect: every runtime import is provided by the mock.
rg -n -C 2 "react-native-safe-area-context" \
  --glob '*.ts' --glob '*.tsx' --glob '*.js' .

Repository: Resgrid/Unit

Length of output: 7338


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- jest-setup.ts ---'
cat -n jest-setup.ts | sed -n '1,35p'

printf '%s\n' '--- named imports from react-native-safe-area-context ---'
python3 - <<'PY'
import re
from pathlib import Path

for path in Path('.').rglob('*'):
    if not path.is_file() or path.suffix not in {'.ts', '.tsx', '.js'}:
        continue
    try:
        text = path.read_text()
    except UnicodeDecodeError:
        continue
    for match in re.finditer(
        r"""(?:import\s+(?:type\s+)?\{([^}]+)\}\s+from|require\()\s*['"]react-native-safe-area-context['"]""",
        text,
    ):
        if match.group(1) is not None:
            names = [
                re.sub(r'\s+', ' ', item.strip())
                for item in match.group(1).split(',')
                if item.strip()
            ]
            print(f'{path}: {", ".join(names)}')
        else:
            print(f'{path}: require(...)')
PY

printf '%s\n' '--- module export references in source and tests ---'
rg -n -C 1 \
  "SafeArea(View|Provider)|useSafeArea(Insets|Frame)|initialWindowMetrics|SafeAreaListener|useSafeArea" \
  --glob '*.ts' --glob '*.tsx' --glob '*.js' .

Repository: Resgrid/Unit

Length of output: 20016


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- non-type safe-area imports outside the repository source ---'
rg -n -C 2 \
  "from ['\"]react-native-safe-area-context['\"]|require\(['\"]react-native-safe-area-context['\"]\)" \
  node_modules \
  --glob '!**/*.d.ts' \
  --glob '!**/example/**' \
  | sed -n '1,240p'

printf '%s\n' '--- package resolution and Jest configuration ---'
rg -n -C 3 \
  "jest-setup|setupFiles|setupFilesAfterEnv|react-native-screens|expo-router|jest-expo" \
  package.json jest.config.* app.json app.config.* \
  --glob '!node_modules/**' 2>/dev/null || true

Repository: Resgrid/Unit

Length of output: 50369


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path

pattern = re.compile(
    r"""(?:import\s+(?:(?:type\s+)?\{([^}]+)\}|([^;\n]+?))\s+from|require\()\s*['"]react-native-safe-area-context['"]"""
)
seen = set()

for path in Path('node_modules').rglob('*'):
    if not path.is_file() or path.suffix in {'.map', '.d.ts'}:
        continue
    try:
        text = path.read_text(errors='ignore')
    except OSError:
        continue
    for line_no, line in enumerate(text.splitlines(), 1):
        if 'react-native-safe-area-context' not in line:
            continue
        match = pattern.search(line)
        if match:
            names = (match.group(1) or match.group(2) or 'require(...)').strip()
            item = (str(path), line_no, names, line.strip())
            if item not in seen:
                seen.add(item)
                print(f'{item[0]}:{item[1]}: {item[3]}')
PY

printf '%s\n' '--- Jest configuration ---'
for file in package.json jest.config.js jest.config.ts jest.config.cjs jest.config.mjs; do
  if [ -f "$file" ]; then
    printf '%s\n' "--- $file ---"
    rg -n -C 4 \
      "jest-setup|setupFiles|setupFilesAfterEnv|preset|react-native-screens|expo-router" \
      "$file" || true
  fi
done

Repository: Resgrid/Unit

Length of output: 8749


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import re
from pathlib import Path
from collections import defaultdict

uses = defaultdict(set)
for path in Path('node_modules').rglob('*'):
    if not path.is_file() or path.suffix in {'.map', '.d.ts'}:
        continue
    if 'example' in path.parts:
        continue
    try:
        text = path.read_text(errors='ignore')
    except OSError:
        continue
    if 'react-native-safe-area-context' not in text:
        continue

    for name in re.findall(r'_reactNativeSafeAreaContext\.([A-Za-z_$][\w$]*)', text):
        uses[str(path)].add(name)

    for line in text.splitlines():
        if 'react-native-safe-area-context' in line and (
            'import ' in line or 'require(' in line
        ):
            uses[str(path)].add(f'IMPORT: {line.strip()}')

for path, names in sorted(uses.items()):
    print(path)
    for name in sorted(names):
        print(f'  {name}')
PY

printf '%s\n' '--- direct references to missing candidate exports ---'
rg -n \
  "SafeAreaInsetsContext|SafeAreaFrameContext|useSafeAreaFrame|initialWindowMetrics|SafeAreaProvider|SafeAreaView|useSafeAreaInsets" \
  node_modules/expo-router/build node_modules/react-native-css/dist node_modules/@gluestack-ui/core/lib node_modules/react-native-screens/lib \
  --glob '!**/*.map' \
  --glob '!**/*.d.ts' \
  | sed -n '1,240p'

Repository: Resgrid/Unit

Length of output: 25314


Add SafeAreaInsetsContext to the global safe-area mock. Expo Router accesses SafeAreaInsetsContext.Consumer; the mock currently returns undefined, which can fail navigation suites during rendering.

🤖 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 `@jest-setup.ts` around lines 5 - 18, Add SafeAreaInsetsContext to the
react-native-safe-area-context mock in jest.mock, exposing a Consumer compatible
with the zero-inset mock values so Expo Router can render safely. Preserve the
existing SafeAreaView, SafeAreaProvider, hook, and initialWindowMetrics
behavior.

Comment thread jest-setup.ts
Comment on lines +8 to +12
const SafeAreaView = ({ children }: any) => React.createElement(React.Fragment, null, children);

return {
SafeAreaView,
SafeAreaProvider: ({ children }: any) => children,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a typed interface for mock children.

The two mock components use any. This removes type checking for children. Define one MockChildrenProps interface with children?: ReactNode and use it for both components.

As per coding guidelines, **/*.{ts,tsx} requires precise types and interfaces and forbids any.

Proposed fix
+import type { ReactNode } from 'react';
+
+interface MockChildrenProps {
+  children?: ReactNode;
+}
+
 const SafeAreaView = ({ children }: any) => React.createElement(React.Fragment, null, children);
+const SafeAreaView = ({ children }: MockChildrenProps) => React.createElement(React.Fragment, null, children);

-    SafeAreaProvider: ({ children }: any) => children,
+    SafeAreaProvider: ({ children }: MockChildrenProps) => children,
🤖 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 `@jest-setup.ts` around lines 8 - 12, Replace the any-typed props in
SafeAreaView and SafeAreaProvider with a shared MockChildrenProps interface
defining optional children as ReactNode, and use that interface for both mock
component parameters.

Source: Coding guidelines

Comment on lines +20 to +21
const mockConnectChatHub = jest.fn();
const mockDisconnectChatHub = jest.fn();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 6 '\b(connectChatHub|disconnectChatHub)\b' src/hooks src/stores

Repository: Resgrid/Unit

Length of output: 13383


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- test file ---'
cat -n src/hooks/__tests__/use-signalr-lifecycle.test.tsx | sed -n '1,190p'

printf '%s\n' '--- hook implementation ---'
cat -n src/hooks/use-signalr-lifecycle.ts | sed -n '1,155p'

printf '%s\n' '--- relevant test assertions ---'
rg -n -C 4 'mockConnectChatHub|mockDisconnectChatHub|Promise\.allSettled|reject|resolve|connectChatHub|disconnectChatHub' \
  src/hooks/__tests__/use-signalr-lifecycle.test.tsx

Repository: Resgrid/Unit

Length of output: 16623


🏁 Script executed:

node - <<'JS'
const plainMockResult = undefined;
const promiseMockResult = Promise.resolve(undefined);

Promise.allSettled([plainMockResult, promiseMockResult]).then((results) => {
  console.log(JSON.stringify(results));
});
JS

Repository: Resgrid/Unit

Length of output: 198


Make the Chat hub mocks Promise-compatible.

The hook currently handles plain undefined values with Promise.allSettled, so existing tests do not fail. However, the production methods return Promise<void>. Use jest.fn().mockResolvedValue(undefined) to preserve the async contract and support completion or rejection tests.

🤖 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 `@src/hooks/__tests__/use-signalr-lifecycle.test.tsx` around lines 20 - 21,
Update the mockConnectChatHub and mockDisconnectChatHub Jest mocks to use
mockResolvedValue(undefined), preserving the production Promise<void> contract
and enabling async completion or rejection tests.

Comment thread src/stores/chat/store.ts Outdated
// Tell the hub which conversation is on screen so it suppresses chat push
// notifications for that channel — including the unit-device push when the
// active unit id is supplied. A null channelId clears the marker.
void safeInvoke('SetActiveChannel', channelId ?? null, activeUnitIdNumber() ?? null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

WHAT: The void prefix discards the return value of safeInvoke, which (given the void) likely returns a Promise whose rejections are now unhandled. WHY: Rule [1] requires every async operation be guarded; a fire-and-forget void with no .catch() leaves rejections unhandled and can crash the app or hide failures. HOW: Either await inside a try/catch (if the caller can be async) or chain .catch(err => logger.error('SetActiveChannel failed', { channelId, err })) before discarding, or ensure safeInvoke itself swallows/logs internally and document that.

Also found in:

  • src/stores/chat/store.ts:861-861

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File src/stores/chat/store.ts:

Line 286:

WHAT: The `void` prefix discards the return value of `safeInvoke`, which (given the `void`) likely returns a Promise whose rejections are now unhandled. WHY: Rule [1] requires every async operation be guarded; a fire-and-forget `void` with no `.catch()` leaves rejections unhandled and can crash the app or hide failures. HOW: Either `await` inside a try/catch (if the caller can be async) or chain `.catch(err => logger.error('SetActiveChannel failed', { channelId, err }))` before discarding, or ensure `safeInvoke` itself swallows/logs internally and document that.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

Resgrid-Bot commented Aug 12, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/stores/chat/__tests__/hub-invoke-args.test.ts`:
- Line 175: Update the SetActiveChannel assertion in the hub invocation test to
explicitly match null and 42 instead of using broad expect.anything() matchers,
ensuring the not-called check covers the null marker case.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8604c708-742f-46e8-bde5-ed5442309806

📥 Commits

Reviewing files that changed from the base of the PR and between df5cef1 and 1eaffc7.

📒 Files selected for processing (2)
  • src/stores/chat/__tests__/hub-invoke-args.test.ts
  • src/stores/chat/store.ts

useChatStore.getState().handleChatConnected();
await flush();

expect(mockInvoke).not.toHaveBeenCalledWith('chatHub', 'SetActiveChannel', expect.anything(), expect.anything());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
file="src/stores/chat/__tests__/hub-invoke-args.test.ts"
printf '%s\n' "== file context =="
sed -n '130,185p' "$file"
printf '%s\n' "== relevant symbols and calls =="
rg -n -C 3 "SetActiveChannel|mockInvoke|expect\\.anything|reconnect|disconnect|activeChannel" "$file" src/stores/chat src --glob '*.ts' --glob '*.tsx' | head -n 240
printf '%s\n' "== Jest matcher probe =="
node - <<'JS'
const matchers = require('expect/build/matchers');
const equals = require('`@jest/expect-utils`').equals;
const matcher = matchers.toHaveBeenCalledWith;
function check(actualArgs, expectedArgs) {
  const result = matcher.call({ equals, isNot: false, promise: '' }, { mock: { calls: [actualArgs] } }, expectedArgs);
  return result.pass;
}
for (const args of [[null, 42], ['channel', 42], [undefined, 42]]) {
  console.log(JSON.stringify(args), {
    negativeAnything: !check(args, ['chatHub', 'SetActiveChannel', expectAnything(), expectAnything()]),
    negativeNull42: !check(args, ['chatHub', 'SetActiveChannel', null, 42]),
  });
}
function expectAnything() {
  return {
    asymmetricMatch(value) {
      return value !== null && value !== undefined;
    },
    toString() { return 'Anything'; },
    getExpectedType() { return 'anything'; },
  };
}
JS

Repository: Resgrid/Unit

Length of output: 21777


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' "== active-channel synchronization implementation =="
sed -n '235,330p' src/stores/chat/store.ts
printf '%s\n' "== reconnect handler references =="
rg -n -C 8 "handleChatConnected|pendingActiveChannelSync|syncActiveChannelMarker" src/stores/chat/store.ts src/stores/chat --glob '*.ts' --glob '*.tsx'

Repository: Resgrid/Unit

Length of output: 22712


🌐 Web query:

Jest expect.anything null undefined toHaveBeenCalledWith documentation

💡 Result:

In Jest, the asymmetric matcher expect.anything is explicitly designed to match any value except for null or undefined [1][2]. Because null and undefined are considered "nothing" in this context, expect.anything acts as a validator that an argument is present and defined [1][3]. Regarding toHaveBeenCalledWith (and its alias toBeenCalledWith): 1. Null and Undefined Behavior: When using toHaveBeenCalledWith, these matchers utilize equality logic similar to toEqual rather than strict equality (toStrictEqual) [4][5][6]. As a result, certain comparisons may behave in ways that seem non-strict regarding undefined properties or arguments [4][5][7]. 2. Limitations: Because expect.anything specifically excludes null and undefined, it cannot be used to match those values [1][3]. If you need to match a parameter that could be null, undefined, or any other value, you cannot use expect.anything for that position [3]. 3. Intended Usage: The primary purpose of expect.anything is to assert that a mock function was called with a valid, non-null, and non-undefined argument [1][8]. If you find that your assertions are failing or passing unexpectedly due to the handling of null or undefined, consider using toStrictEqual if testing values directly, or creating a custom matcher if you require specific behavior that handles these values differently than the built-in Jest matchers [4][5][3].

Citations:


Match the null marker explicitly

expect.anything() excludes null, so this assertion passes when SetActiveChannel(null, 42) is called. Replace the broad matcher with null and 42.

🤖 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 `@src/stores/chat/__tests__/hub-invoke-args.test.ts` at line 175, Update the
SetActiveChannel assertion in the hub invocation test to explicitly match null
and 42 instead of using broad expect.anything() matchers, ensuring the
not-called check covers the null marker case.

Source: Coding guidelines

Comment thread src/stores/chat/store.ts
// Only clear if no newer marker superseded this one while in flight.
if (pendingActiveChannelSync === marker) pendingActiveChannelSync = null;
} catch (error) {
logger.debug({ message: 'chat: invoke SetActiveChannel skipped', context: { error } });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Non-compliant error log in the catch block embeds the operation name in the message string and omits relevant identifiers (channelId, unitId). Rule [3] requires error logs to include the operation name and relevant identifiers as structured fields for searchable telemetry; restructure to logger.error('SetActiveChannel failed', { op: 'SetActiveChannel', channelId, error }); or include channelId in the context object.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File src/stores/chat/store.ts:

Line 261:

Non-compliant error log in the catch block embeds the operation name in the message string and omits relevant identifiers (channelId, unitId). Rule [3] requires error logs to include the operation name and relevant identifiers as structured fields for searchable telemetry; restructure to logger.error('SetActiveChannel failed', { op: 'SetActiveChannel', channelId, error }); or include channelId in the context object.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@ucswift

ucswift commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift
ucswift merged commit 7e1ddc0 into master Aug 12, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants