Skip to content

Conversation

@Marc-Spector
Copy link
Collaborator

@Marc-Spector Marc-Spector commented Oct 25, 2025

Closes #3423

Summary by CodeRabbit

  • Refactor
    • Private chat now uses live player data, improving avatar, online-status, rating and achievement displays.
    • UI now reacts to player updates directly, so info refreshes reliably when the other player’s state changes.
  • Tests
    • Tests updated to validate the new player-driven behavior and UI updates, including online player lookup and rating/achievement scenarios.

@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2025

Walkthrough

Replaces ChatChannelUser-based side-panel wiring with PlayerInfo via PlayerService: controllers now bind to an observable PlayerInfo, avatar loading and rating/achievement lookups use PlayerInfo and AvatarService, and tests are updated to mock PlayerService and use the new playerProperty model.

Changes

Cohort / File(s) Summary
Chat Tab Controller
src/main/java/com/faforever/client/chat/PrivateChatTabController.java
Adds PlayerService field and constructor parameter; replaces chat-user bindings with an online-player observable obtained from playerService.getPlayerByNameIfOnline(...); rebinds PrivatePlayerInfoController to the new player observable; updates avatar lookup to use PlayerInfo + AvatarService.
Private Player Info Controller
src/main/java/com/faforever/client/player/PrivatePlayerInfoController.java
Removes ChatChannelUser property and related setters/getters; introduces private final ObjectProperty<PlayerInfo> player and public ObjectProperty<PlayerInfo> playerProperty(); rebinds UI fields (username, country, gamesPlayed, ratings, identicon/avatar) to player; switches listener-based updates to reacting to player changes and view-show subscriptions for loading ratings and achievements.
Chat Tests
src/test/java/com/faforever/client/chat/PrivateChatTabControllerTest.java
Adds @Mock PlayerService; stubs playerService.getPlayerByNameIfOnline(...) to return Optional.of(player); replaces uses of chatUserProperty() with playerProperty() and adapts test setup to use a local playerName/player fixture.
Player Info Tests
src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java
Replaces ChatChannelUser fixtures with PlayerInfo wrapped in an ObjectProperty; introduces leaderboardRatings fixtures (random map helper) and GameStatus usage; binds controller via playerProperty; updates tests to mutate PlayerInfo (game, ratings) and verify UI/behavior; removes prior UiService/ChatChannel test wiring.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI (PrivateChatTab)
    participant TabCtrl as PrivateChatTabController
    participant PlayerSvc as PlayerService
    participant InfoCtrl as PrivatePlayerInfoController
    participant Avatar as AvatarService
    Note over TabCtrl,PlayerSvc: New flow: resolve online PlayerInfo
    UI->>TabCtrl: open private chat for playerName
    TabCtrl->>PlayerSvc: getPlayerByNameIfOnline(playerName)
    alt player online
        PlayerSvc-->>TabCtrl: Optional<PlayerInfo>
        TabCtrl->>InfoCtrl: playerProperty.set(playerInfo)
        InfoCtrl->>InfoCtrl: subscribe to playerProperty changes
        InfoCtrl->>Avatar: load avatar for playerInfo
        Avatar-->>InfoCtrl: avatar image
        InfoCtrl->>InfoCtrl: load ratings & achievements (async)
        InfoCtrl-->>UI: update side-panel UI
    else player offline/absent
        PlayerSvc-->>TabCtrl: Optional.empty
        TabCtrl->>InfoCtrl: playerProperty.set(null)
        InfoCtrl->>InfoCtrl: clear UI / show fallback
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to:
    • Constructor change in PrivateChatTabController and all instantiation call sites.
    • Player property subscription lifecycle in PrivatePlayerInfoController (subscribe/unsubscribe on show/hide).
    • Tests: ensure PlayerService mocks return consistent Optional and leaderboard ratings fixtures align with expectations.
    • Avatar loading and null/empty-player handling.

Poem

🐰
I nudge the code with tiny paws,
Swapped users for players without a pause.
Avatars fetched, ratings dance anew,
Side-panel wakes with clearer view.
Hooray — a hop, a fix, a chew! 🥕

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Fix that player info was not displayed on side window when opening a private channel" directly summarizes the main change in the changeset. The code modifications replace ChatChannelUser-based bindings with PlayerService-based bindings in PrivateChatTabController and PrivatePlayerInfoController to ensure player information is retrieved directly from the PlayerService rather than relying on the chat channel's user list. This approach addresses the root cause identified in the linked issue, where remote players are not immediately present in the channel's user list until they respond or message history is available. The title clearly communicates the bug being fixed and accurately represents the primary objective of the changes.
Linked Issues Check ✅ Passed The pull request effectively addresses the requirements from linked issue #3423, which reports that player information is not displayed in the side window when opening a private channel. The code changes implement a solution by introducing PlayerService dependency in PrivateChatTabController and replacing ChatChannelUser-based bindings with PlayerService-based bindings that call playerService.getPlayerByNameIfOnline() to retrieve player information directly. PrivatePlayerInfoController is updated to accept PlayerInfo through a new player property instead of ChatChannelUser, ensuring player data is displayed correctly. The corresponding test updates validate this new behavior. These changes directly resolve the root cause identified in issue comments: the remote player not being in the channel's user list initially, which previously prevented displaying their information in the side window.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly in scope and focused on fixing the player information display issue. PrivateChatTabController adds PlayerService dependency and uses it to retrieve player information independently from ChatChannelUser objects; PrivatePlayerInfoController replaces ChatChannelUser-based properties with PlayerInfo-based properties to align with the new data source; and test files are updated to validate the refactored behavior. No extraneous changes such as unrelated refactorings, formatting updates, or functionality modifications outside the scope of this bug fix are present. All alterations directly support the objective of ensuring player information is displayed in the side window when opening a private channel.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/show-player-info-on-side-window

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f96ff73 and b7490ff.

📒 Files selected for processing (4)
  • src/main/java/com/faforever/client/chat/PrivateChatTabController.java (4 hunks)
  • src/main/java/com/faforever/client/player/PrivatePlayerInfoController.java (3 hunks)
  • src/test/java/com/faforever/client/chat/PrivateChatTabControllerTest.java (4 hunks)
  • src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/faforever/client/player/PrivatePlayerInfoController.java
⏰ 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). (3)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: checks
  • GitHub Check: checks
🔇 Additional comments (11)
src/test/java/com/faforever/client/chat/PrivateChatTabControllerTest.java (3)

16-16: LGTM!

The new imports for PlayerService and Optional are correctly added to support the updated test implementation.

Also applies to: 37-37


81-82: LGTM!

The PlayerService mock is correctly added to support the updated PrivateChatTabController constructor signature.


100-112: LGTM!

The test setup correctly adapts to the new PlayerService-based architecture:

  • playerService.getPlayerByNameIfOnline is appropriately stubbed
  • playerProperty() binding aligns with the controller API changes
  • ChatChannelUser now correctly references the existing chatChannel instance
src/main/java/com/faforever/client/chat/PrivateChatTabController.java (3)

6-6: LGTM!

The PlayerService dependency is properly introduced as a field.

Also applies to: 24-24


34-37: LGTM!

The constructor correctly accepts and assigns the PlayerService dependency using proper constructor injection.


56-57: LGTM!

The avatar binding correctly uses flatMap to unwrap the avatarProperty, loads the image via AvatarService, and conditionally binds only when the tab is showing.

src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (5)

14-15: LGTM!

The imports for GameStatus and ObjectProperty are correctly added to support the refactored test implementation.


53-54: LGTM!

The field declarations correctly reflect the shift from ChatChannelUser to PlayerInfo-based model with an ObjectProperty wrapper.


60-66: LGTM!

The setup correctly initializes the PlayerInfo-based model and binds the controller's playerProperty to the test's observable source, enabling proper test data manipulation.

Also applies to: 69-69


96-136: LGTM!

The test methods correctly verify game info display logic using the new PlayerInfo-based model. Property mutations are properly wrapped with runOnFxThreadAndWait.


138-203: LGTM!

The remaining test methods correctly:

  • Update the bound source (player.setValue()) rather than the bound target, avoiding runtime exceptions
  • Verify achievement loading using playerInfo.getId()
  • Test null player scenarios and UI visibility

The LeaderboardRating import issue at line 140 is already flagged in a separate comment.


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

@Marc-Spector Marc-Spector force-pushed the fix/show-player-info-on-side-window branch from 876d1bc to 61f23f9 Compare October 26, 2025 17:55
@Marc-Spector
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Oct 26, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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

🧹 Nitpick comments (4)
src/test/java/com/faforever/client/chat/PrivateChatTabControllerTest.java (1)

100-107: Tighten stubbing; verify own vs peer username.

  • Prefer strict stubs to surface unintended interactions; keep lenient only where necessary (e.g., theme file URL).
  • Also consider returning a different value from chatService.getCurrentUsername() than playerName to mirror a real DM (self vs peer), unless this test relies on them matching.
src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (1)

198-201: Assert side-effect triggered by ‘when(showing)’ gating.

This test relies on player.when(showing) having fired to call getPlayerAchievements(...). If flakiness appears, explicitly ensure the node is “showing” (attach to a Scene) before verification.

src/main/java/com/faforever/client/player/PrivatePlayerInfoController.java (2)

102-108: Dispose subscription to avoid leaks across tab lifecycles.

player.when(showing).subscribe(...) isn’t retained; repeated show/hide or controller reuse can accumulate subscribers. Store and dispose in onDestroy(), or compose with a lifecycle operator (e.g., takeUntil(hiding)) if available.

Example:

@@
-    player.when(showing).subscribe((playerInfo) -> {
+    this.playerSubscription = player.when(showing).subscribe(playerInfo -> {
         if (playerInfo != null) {
           loadReceiverRatingInformation(playerInfo);
           populateUnlockedAchievementsLabel(playerInfo);
         }
     });

And:

+  private reactor.core.Disposable playerSubscription;
+
+  @Override
+  protected void onDestroy() {
+    if (playerSubscription != null) {
+      playerSubscription.dispose();
+    }
+  }

122-125: Use parameterized logging for clarity.

Close the quote and use placeholders.

-                   throwable -> log.error("Could not load achievements for player '" + player.getId(), throwable));
+                   throwable -> log.error("Could not load achievements for player '{}' ", player.getId(), throwable));
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da6d6df and 61f23f9.

📒 Files selected for processing (4)
  • src/main/java/com/faforever/client/chat/PrivateChatTabController.java (4 hunks)
  • src/main/java/com/faforever/client/player/PrivatePlayerInfoController.java (3 hunks)
  • src/test/java/com/faforever/client/chat/PrivateChatTabControllerTest.java (4 hunks)
  • src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (7 hunks)
⏰ 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: Codacy Static Code Analysis

@Marc-Spector
Copy link
Collaborator Author

@coderabbitai resolve

@Marc-Spector Marc-Spector marked this pull request as ready for review October 26, 2025 18:17
@coderabbitai
Copy link

coderabbitai bot commented Oct 26, 2025

✅ Actions performed

Comments resolved.

Copy link

@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: 0

♻️ Duplicate comments (1)
src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (1)

3-17: Add missing import for LeaderboardRating (compilation fix).

LeaderboardRating is referenced but not imported; tests won’t compile.

Apply:

 import com.faforever.client.domain.api.Leaderboard;
+import com.faforever.client.domain.api.LeaderboardRating;
 import com.faforever.client.domain.server.PlayerInfo;
🧹 Nitpick comments (4)
src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (4)

53-55: Make player field private.

Minor test hygiene: keep fields private to the class. No behavior change.

-  ObjectProperty<PlayerInfo> player;
+  private ObjectProperty<PlayerInfo> player;

72-75: Future‑proof i18n stub against method-name drift.

Controller may call getWithDefault(...) instead of getOrDefault(...). Stub both to avoid brittle tests.

-    lenient().when(i18n.getOrDefault(leaderboard.technicalName(), leaderboard.nameKey()))
-             .thenReturn(leaderboard.technicalName());
+    lenient().when(i18n.getOrDefault(leaderboard.technicalName(), leaderboard.nameKey()))
+             .thenReturn(leaderboard.technicalName());
+    lenient().when(i18n.getWithDefault(leaderboard.technicalName(), leaderboard.nameKey()))
+             .thenReturn(leaderboard.technicalName());

If getWithDefault doesn’t exist on I18n in this branch, skip this addition.


139-143: Strengthen verification to ensure the change triggers the call (not prior setup).

verify(leaderboardService).getLeaderboards() can pass due to earlier invocations. Reset/clear before the action and assert one call, or use times.

   @Test
   public void testRatingChange() {
-    runOnFxThreadAndWait(() -> playerInfo.setLeaderboardRatings(Map.of("test", Instancio.create(LeaderboardRating.class))));
-    verify(leaderboardService).getLeaderboards();
+    org.mockito.Mockito.clearInvocations(leaderboardService);
+    runOnFxThreadAndWait(() ->
+        playerInfo.setLeaderboardRatings(
+            Map.of(leaderboard.technicalName(), Instancio.create(LeaderboardRating.class))));
+    verify(leaderboardService, org.mockito.Mockito.times(1)).getLeaderboards();
   }

This also aligns the map key with the mocked leaderboard name.


195-198: Make achievement-call verify resilient to async.

Guard against timing flakiness by using Mockito’s timeout.

-    verify(achievementService).getPlayerAchievements(playerInfo.getId());
+    verify(achievementService, org.mockito.Mockito.timeout(1000))
+        .getPlayerAchievements(playerInfo.getId());

Optionally add a static import for timeout.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61f23f9 and f96ff73.

📒 Files selected for processing (1)
  • src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (1)
src/main/java/com/faforever/client/chat/PrivateUserInfoController.java (3)
  • Component (30-178)
  • leaderboards (161-176)
  • leaderboard (164-171)
⏰ 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: Codacy Static Code Analysis
🔇 Additional comments (9)
src/test/java/com/faforever/client/player/PrivatePlayerInfoControllerTest.java (9)

60-67: Good: bind once, mutate the source property.

Binding instance.playerProperty() to player and mutating player in tests avoids “bound value cannot be set” errors observed earlier.


89-93: LGTM on helper; once import is added, this builds cleanly.

LeaderboardRatingMapBuilder + Instancio usage is fine.


95-111: UI state assertions for idle player look solid.

Covers visibility, labels, and values. Nice.


114-118: LGTM for game-in-progress path.

Setting GameStatus.PLAYING and asserting wrapper visibility is appropriate.


121-127: LGTM for no‑game → joins‑game transition.


130-136: LGTM for leaves‑game transition.


146-157: LGTM for null‑player visibility checks.


160-192: LGTM for null → new player rebind flow.

Good coverage of visibility and label content after rebind.


200-203: LGTM getRoot test.

Copy link
Member

@Sheikah45 Sheikah45 left a comment

Choose a reason for hiding this comment

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

Do you know why the player property in chatUser was unreliable?

@Marc-Spector
Copy link
Collaborator Author

The player property works reliably.
The problem is exactly how private chat works. When you open the chat (for example, with qwerty player), you are completely alone in the list of users.

private final ObservableMap<String, ChatChannelUser> usernameToChatUser = FXCollections.synchronizedObservableMap(
FXCollections.observableHashMap());
private final ObservableList<ChatChannelUser> users = JavaFxUtil.attachListToMap(
FXCollections.synchronizedObservableList(FXCollections.observableArrayList(
item -> new Observable[]{item.categoryProperty(), item.colorProperty(), item.typingProperty()})),
usernameToChatUser);

The qwerty player name is not on the map or in the list. That's why you don't get the ChatChannelUser object.

ObservableValue<ChatChannelUser> chatUser = chatChannel.flatMap(
channel -> channelName.map(chanName -> channel.getUser(chanName).orElse(null)));

public Optional<ChatChannelUser> getUser(String username) {
return Optional.ofNullable(usernameToChatUser.get(username));
}

The player will be on the list when he responds to your messages or you have a message history (I have not tested this case)

@Sheikah45 Sheikah45 force-pushed the fix/show-player-info-on-side-window branch from f96ff73 to b7490ff Compare November 2, 2025 14:23
@Sheikah45 Sheikah45 enabled auto-merge (squash) November 2, 2025 14:23
@Sheikah45 Sheikah45 merged commit d44f97d into develop Nov 2, 2025
3 of 4 checks passed
@Sheikah45 Sheikah45 deleted the fix/show-player-info-on-side-window branch November 2, 2025 14:25
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.

[Bug]: No information about a player on side window in private channel

3 participants